##// END OF EJS Templates
rust: use `logging_timer` instead of `micro_timer`...
Raphaël Gomès -
r50808:c15b415d default
parent child Browse files
Show More
@@ -1,1455 +1,1455 b''
1 1 # This file is automatically @generated by Cargo.
2 2 # It is not intended for manual editing.
3 3 version = 3
4 4
5 5 [[package]]
6 6 name = "Inflector"
7 7 version = "0.11.4"
8 8 source = "registry+https://github.com/rust-lang/crates.io-index"
9 9 checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
10 10
11 11 [[package]]
12 12 name = "adler"
13 13 version = "1.0.2"
14 14 source = "registry+https://github.com/rust-lang/crates.io-index"
15 15 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
16 16
17 17 [[package]]
18 18 name = "ahash"
19 19 version = "0.8.2"
20 20 source = "registry+https://github.com/rust-lang/crates.io-index"
21 21 checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107"
22 22 dependencies = [
23 23 "cfg-if",
24 24 "once_cell",
25 25 "version_check",
26 26 ]
27 27
28 28 [[package]]
29 29 name = "aho-corasick"
30 30 version = "0.7.19"
31 31 source = "registry+https://github.com/rust-lang/crates.io-index"
32 32 checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
33 33 dependencies = [
34 34 "memchr",
35 35 ]
36 36
37 37 [[package]]
38 38 name = "aliasable"
39 39 version = "0.1.3"
40 40 source = "registry+https://github.com/rust-lang/crates.io-index"
41 41 checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
42 42
43 43 [[package]]
44 44 name = "android_system_properties"
45 45 version = "0.1.5"
46 46 source = "registry+https://github.com/rust-lang/crates.io-index"
47 47 checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
48 48 dependencies = [
49 49 "libc",
50 50 ]
51 51
52 52 [[package]]
53 53 name = "atty"
54 54 version = "0.2.14"
55 55 source = "registry+https://github.com/rust-lang/crates.io-index"
56 56 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
57 57 dependencies = [
58 58 "hermit-abi",
59 59 "libc",
60 60 "winapi",
61 61 ]
62 62
63 63 [[package]]
64 64 name = "autocfg"
65 65 version = "1.1.0"
66 66 source = "registry+https://github.com/rust-lang/crates.io-index"
67 67 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
68 68
69 69 [[package]]
70 70 name = "bitflags"
71 71 version = "1.3.2"
72 72 source = "registry+https://github.com/rust-lang/crates.io-index"
73 73 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
74 74
75 75 [[package]]
76 76 name = "bitmaps"
77 77 version = "2.1.0"
78 78 source = "registry+https://github.com/rust-lang/crates.io-index"
79 79 checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
80 80 dependencies = [
81 81 "typenum",
82 82 ]
83 83
84 84 [[package]]
85 85 name = "block-buffer"
86 86 version = "0.9.0"
87 87 source = "registry+https://github.com/rust-lang/crates.io-index"
88 88 checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
89 89 dependencies = [
90 90 "generic-array",
91 91 ]
92 92
93 93 [[package]]
94 94 name = "block-buffer"
95 95 version = "0.10.3"
96 96 source = "registry+https://github.com/rust-lang/crates.io-index"
97 97 checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
98 98 dependencies = [
99 99 "generic-array",
100 100 ]
101 101
102 102 [[package]]
103 103 name = "bumpalo"
104 104 version = "3.11.1"
105 105 source = "registry+https://github.com/rust-lang/crates.io-index"
106 106 checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
107 107
108 108 [[package]]
109 109 name = "byteorder"
110 110 version = "1.4.3"
111 111 source = "registry+https://github.com/rust-lang/crates.io-index"
112 112 checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
113 113
114 114 [[package]]
115 115 name = "bytes-cast"
116 116 version = "0.2.0"
117 117 source = "registry+https://github.com/rust-lang/crates.io-index"
118 118 checksum = "0d434f9a4ecbe987e7ccfda7274b6f82ea52c9b63742565a65cb5e8ba0f2c452"
119 119 dependencies = [
120 120 "bytes-cast-derive",
121 121 ]
122 122
123 123 [[package]]
124 124 name = "bytes-cast-derive"
125 125 version = "0.1.1"
126 126 source = "registry+https://github.com/rust-lang/crates.io-index"
127 127 checksum = "b13e0e8ffc91021ba28dc98b2ea82099ba4ec07655279c21bfa3313ed96708fc"
128 128 dependencies = [
129 129 "proc-macro2",
130 130 "quote",
131 131 "syn",
132 132 ]
133 133
134 134 [[package]]
135 135 name = "cc"
136 136 version = "1.0.76"
137 137 source = "registry+https://github.com/rust-lang/crates.io-index"
138 138 checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
139 139 dependencies = [
140 140 "jobserver",
141 141 ]
142 142
143 143 [[package]]
144 144 name = "cfg-if"
145 145 version = "1.0.0"
146 146 source = "registry+https://github.com/rust-lang/crates.io-index"
147 147 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
148 148
149 149 [[package]]
150 150 name = "chrono"
151 151 version = "0.4.23"
152 152 source = "registry+https://github.com/rust-lang/crates.io-index"
153 153 checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
154 154 dependencies = [
155 155 "iana-time-zone",
156 156 "js-sys",
157 157 "num-integer",
158 158 "num-traits",
159 159 "time",
160 160 "wasm-bindgen",
161 161 "winapi",
162 162 ]
163 163
164 164 [[package]]
165 165 name = "clap"
166 166 version = "4.0.24"
167 167 source = "registry+https://github.com/rust-lang/crates.io-index"
168 168 checksum = "60494cedb60cb47462c0ff7be53de32c0e42a6fc2c772184554fa12bd9489c03"
169 169 dependencies = [
170 170 "atty",
171 171 "bitflags",
172 172 "clap_derive",
173 173 "clap_lex",
174 174 "once_cell",
175 175 "strsim",
176 176 "termcolor",
177 177 ]
178 178
179 179 [[package]]
180 180 name = "clap_derive"
181 181 version = "4.0.21"
182 182 source = "registry+https://github.com/rust-lang/crates.io-index"
183 183 checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
184 184 dependencies = [
185 185 "heck",
186 186 "proc-macro-error",
187 187 "proc-macro2",
188 188 "quote",
189 189 "syn",
190 190 ]
191 191
192 192 [[package]]
193 193 name = "clap_lex"
194 194 version = "0.3.0"
195 195 source = "registry+https://github.com/rust-lang/crates.io-index"
196 196 checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
197 197 dependencies = [
198 198 "os_str_bytes",
199 199 ]
200 200
201 201 [[package]]
202 202 name = "codespan-reporting"
203 203 version = "0.11.1"
204 204 source = "registry+https://github.com/rust-lang/crates.io-index"
205 205 checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
206 206 dependencies = [
207 207 "termcolor",
208 208 "unicode-width",
209 209 ]
210 210
211 211 [[package]]
212 212 name = "convert_case"
213 213 version = "0.4.0"
214 214 source = "registry+https://github.com/rust-lang/crates.io-index"
215 215 checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
216 216
217 217 [[package]]
218 218 name = "core-foundation-sys"
219 219 version = "0.8.3"
220 220 source = "registry+https://github.com/rust-lang/crates.io-index"
221 221 checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
222 222
223 223 [[package]]
224 224 name = "cpufeatures"
225 225 version = "0.2.5"
226 226 source = "registry+https://github.com/rust-lang/crates.io-index"
227 227 checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
228 228 dependencies = [
229 229 "libc",
230 230 ]
231 231
232 232 [[package]]
233 233 name = "cpython"
234 234 version = "0.7.1"
235 235 source = "registry+https://github.com/rust-lang/crates.io-index"
236 236 checksum = "3052106c29da7390237bc2310c1928335733b286287754ea85e6093d2495280e"
237 237 dependencies = [
238 238 "libc",
239 239 "num-traits",
240 240 "paste",
241 241 "python3-sys",
242 242 ]
243 243
244 244 [[package]]
245 245 name = "crc32fast"
246 246 version = "1.3.2"
247 247 source = "registry+https://github.com/rust-lang/crates.io-index"
248 248 checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
249 249 dependencies = [
250 250 "cfg-if",
251 251 ]
252 252
253 253 [[package]]
254 254 name = "crossbeam-channel"
255 255 version = "0.5.6"
256 256 source = "registry+https://github.com/rust-lang/crates.io-index"
257 257 checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
258 258 dependencies = [
259 259 "cfg-if",
260 260 "crossbeam-utils",
261 261 ]
262 262
263 263 [[package]]
264 264 name = "crossbeam-deque"
265 265 version = "0.8.2"
266 266 source = "registry+https://github.com/rust-lang/crates.io-index"
267 267 checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
268 268 dependencies = [
269 269 "cfg-if",
270 270 "crossbeam-epoch",
271 271 "crossbeam-utils",
272 272 ]
273 273
274 274 [[package]]
275 275 name = "crossbeam-epoch"
276 276 version = "0.9.11"
277 277 source = "registry+https://github.com/rust-lang/crates.io-index"
278 278 checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348"
279 279 dependencies = [
280 280 "autocfg",
281 281 "cfg-if",
282 282 "crossbeam-utils",
283 283 "memoffset",
284 284 "scopeguard",
285 285 ]
286 286
287 287 [[package]]
288 288 name = "crossbeam-utils"
289 289 version = "0.8.12"
290 290 source = "registry+https://github.com/rust-lang/crates.io-index"
291 291 checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
292 292 dependencies = [
293 293 "cfg-if",
294 294 ]
295 295
296 296 [[package]]
297 297 name = "crypto-common"
298 298 version = "0.1.6"
299 299 source = "registry+https://github.com/rust-lang/crates.io-index"
300 300 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
301 301 dependencies = [
302 302 "generic-array",
303 303 "typenum",
304 304 ]
305 305
306 306 [[package]]
307 307 name = "ctor"
308 308 version = "0.1.26"
309 309 source = "registry+https://github.com/rust-lang/crates.io-index"
310 310 checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
311 311 dependencies = [
312 312 "quote",
313 313 "syn",
314 314 ]
315 315
316 316 [[package]]
317 317 name = "cxx"
318 318 version = "1.0.81"
319 319 source = "registry+https://github.com/rust-lang/crates.io-index"
320 320 checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888"
321 321 dependencies = [
322 322 "cc",
323 323 "cxxbridge-flags",
324 324 "cxxbridge-macro",
325 325 "link-cplusplus",
326 326 ]
327 327
328 328 [[package]]
329 329 name = "cxx-build"
330 330 version = "1.0.81"
331 331 source = "registry+https://github.com/rust-lang/crates.io-index"
332 332 checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3"
333 333 dependencies = [
334 334 "cc",
335 335 "codespan-reporting",
336 336 "once_cell",
337 337 "proc-macro2",
338 338 "quote",
339 339 "scratch",
340 340 "syn",
341 341 ]
342 342
343 343 [[package]]
344 344 name = "cxxbridge-flags"
345 345 version = "1.0.81"
346 346 source = "registry+https://github.com/rust-lang/crates.io-index"
347 347 checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f"
348 348
349 349 [[package]]
350 350 name = "cxxbridge-macro"
351 351 version = "1.0.81"
352 352 source = "registry+https://github.com/rust-lang/crates.io-index"
353 353 checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704"
354 354 dependencies = [
355 355 "proc-macro2",
356 356 "quote",
357 357 "syn",
358 358 ]
359 359
360 360 [[package]]
361 361 name = "derive_more"
362 362 version = "0.99.17"
363 363 source = "registry+https://github.com/rust-lang/crates.io-index"
364 364 checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
365 365 dependencies = [
366 366 "convert_case",
367 367 "proc-macro2",
368 368 "quote",
369 369 "rustc_version",
370 370 "syn",
371 371 ]
372 372
373 373 [[package]]
374 374 name = "diff"
375 375 version = "0.1.13"
376 376 source = "registry+https://github.com/rust-lang/crates.io-index"
377 377 checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
378 378
379 379 [[package]]
380 380 name = "digest"
381 381 version = "0.9.0"
382 382 source = "registry+https://github.com/rust-lang/crates.io-index"
383 383 checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
384 384 dependencies = [
385 385 "generic-array",
386 386 ]
387 387
388 388 [[package]]
389 389 name = "digest"
390 390 version = "0.10.5"
391 391 source = "registry+https://github.com/rust-lang/crates.io-index"
392 392 checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
393 393 dependencies = [
394 394 "block-buffer 0.10.3",
395 395 "crypto-common",
396 396 ]
397 397
398 398 [[package]]
399 399 name = "either"
400 400 version = "1.8.0"
401 401 source = "registry+https://github.com/rust-lang/crates.io-index"
402 402 checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
403 403
404 404 [[package]]
405 405 name = "env_logger"
406 406 version = "0.9.3"
407 407 source = "registry+https://github.com/rust-lang/crates.io-index"
408 408 checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
409 409 dependencies = [
410 410 "atty",
411 411 "humantime",
412 412 "log",
413 413 "regex",
414 414 "termcolor",
415 415 ]
416 416
417 417 [[package]]
418 418 name = "fastrand"
419 419 version = "1.8.0"
420 420 source = "registry+https://github.com/rust-lang/crates.io-index"
421 421 checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
422 422 dependencies = [
423 423 "instant",
424 424 ]
425 425
426 426 [[package]]
427 427 name = "flate2"
428 428 version = "1.0.24"
429 429 source = "registry+https://github.com/rust-lang/crates.io-index"
430 430 checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
431 431 dependencies = [
432 432 "crc32fast",
433 433 "libz-sys",
434 434 "miniz_oxide",
435 435 ]
436 436
437 437 [[package]]
438 438 name = "format-bytes"
439 439 version = "0.3.0"
440 440 source = "registry+https://github.com/rust-lang/crates.io-index"
441 441 checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd"
442 442 dependencies = [
443 443 "format-bytes-macros",
444 444 ]
445 445
446 446 [[package]]
447 447 name = "format-bytes-macros"
448 448 version = "0.4.0"
449 449 source = "registry+https://github.com/rust-lang/crates.io-index"
450 450 checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f"
451 451 dependencies = [
452 452 "proc-macro2",
453 453 "quote",
454 454 "syn",
455 455 ]
456 456
457 457 [[package]]
458 458 name = "generic-array"
459 459 version = "0.14.6"
460 460 source = "registry+https://github.com/rust-lang/crates.io-index"
461 461 checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
462 462 dependencies = [
463 463 "typenum",
464 464 "version_check",
465 465 ]
466 466
467 467 [[package]]
468 468 name = "getrandom"
469 469 version = "0.1.16"
470 470 source = "registry+https://github.com/rust-lang/crates.io-index"
471 471 checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
472 472 dependencies = [
473 473 "cfg-if",
474 474 "libc",
475 475 "wasi 0.9.0+wasi-snapshot-preview1",
476 476 ]
477 477
478 478 [[package]]
479 479 name = "getrandom"
480 480 version = "0.2.8"
481 481 source = "registry+https://github.com/rust-lang/crates.io-index"
482 482 checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
483 483 dependencies = [
484 484 "cfg-if",
485 485 "libc",
486 486 "wasi 0.11.0+wasi-snapshot-preview1",
487 487 ]
488 488
489 489 [[package]]
490 490 name = "hashbrown"
491 491 version = "0.13.1"
492 492 source = "registry+https://github.com/rust-lang/crates.io-index"
493 493 checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
494 494 dependencies = [
495 495 "ahash",
496 496 "rayon",
497 497 ]
498 498
499 499 [[package]]
500 500 name = "heck"
501 501 version = "0.4.0"
502 502 source = "registry+https://github.com/rust-lang/crates.io-index"
503 503 checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
504 504
505 505 [[package]]
506 506 name = "hermit-abi"
507 507 version = "0.1.19"
508 508 source = "registry+https://github.com/rust-lang/crates.io-index"
509 509 checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
510 510 dependencies = [
511 511 "libc",
512 512 ]
513 513
514 514 [[package]]
515 515 name = "hex"
516 516 version = "0.4.3"
517 517 source = "registry+https://github.com/rust-lang/crates.io-index"
518 518 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
519 519
520 520 [[package]]
521 521 name = "hg-core"
522 522 version = "0.1.0"
523 523 dependencies = [
524 524 "bitflags",
525 525 "byteorder",
526 526 "bytes-cast",
527 527 "clap",
528 528 "crossbeam-channel",
529 529 "derive_more",
530 530 "flate2",
531 531 "format-bytes",
532 532 "hashbrown",
533 533 "home",
534 534 "im-rc",
535 535 "itertools",
536 536 "lazy_static",
537 537 "libc",
538 538 "log",
539 "logging_timer",
539 540 "memmap2",
540 "micro-timer",
541 541 "once_cell",
542 542 "ouroboros",
543 543 "pretty_assertions",
544 544 "rand 0.8.5",
545 545 "rand_distr",
546 546 "rand_pcg",
547 547 "rayon",
548 548 "regex",
549 549 "same-file",
550 550 "sha-1 0.10.0",
551 551 "tempfile",
552 552 "thread_local",
553 553 "twox-hash",
554 554 "zstd",
555 555 ]
556 556
557 557 [[package]]
558 558 name = "hg-cpython"
559 559 version = "0.1.0"
560 560 dependencies = [
561 561 "cpython",
562 562 "crossbeam-channel",
563 563 "env_logger",
564 564 "hg-core",
565 565 "libc",
566 566 "log",
567 567 "stable_deref_trait",
568 568 "vcsgraph",
569 569 ]
570 570
571 571 [[package]]
572 572 name = "home"
573 573 version = "0.5.4"
574 574 source = "registry+https://github.com/rust-lang/crates.io-index"
575 575 checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
576 576 dependencies = [
577 577 "winapi",
578 578 ]
579 579
580 580 [[package]]
581 581 name = "humantime"
582 582 version = "2.1.0"
583 583 source = "registry+https://github.com/rust-lang/crates.io-index"
584 584 checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
585 585
586 586 [[package]]
587 587 name = "iana-time-zone"
588 588 version = "0.1.53"
589 589 source = "registry+https://github.com/rust-lang/crates.io-index"
590 590 checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
591 591 dependencies = [
592 592 "android_system_properties",
593 593 "core-foundation-sys",
594 594 "iana-time-zone-haiku",
595 595 "js-sys",
596 596 "wasm-bindgen",
597 597 "winapi",
598 598 ]
599 599
600 600 [[package]]
601 601 name = "iana-time-zone-haiku"
602 602 version = "0.1.1"
603 603 source = "registry+https://github.com/rust-lang/crates.io-index"
604 604 checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
605 605 dependencies = [
606 606 "cxx",
607 607 "cxx-build",
608 608 ]
609 609
610 610 [[package]]
611 611 name = "im-rc"
612 612 version = "15.1.0"
613 613 source = "registry+https://github.com/rust-lang/crates.io-index"
614 614 checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe"
615 615 dependencies = [
616 616 "bitmaps",
617 617 "rand_core 0.6.4",
618 618 "rand_xoshiro",
619 619 "sized-chunks",
620 620 "typenum",
621 621 "version_check",
622 622 ]
623 623
624 624 [[package]]
625 625 name = "instant"
626 626 version = "0.1.12"
627 627 source = "registry+https://github.com/rust-lang/crates.io-index"
628 628 checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
629 629 dependencies = [
630 630 "cfg-if",
631 631 ]
632 632
633 633 [[package]]
634 634 name = "itertools"
635 635 version = "0.10.5"
636 636 source = "registry+https://github.com/rust-lang/crates.io-index"
637 637 checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
638 638 dependencies = [
639 639 "either",
640 640 ]
641 641
642 642 [[package]]
643 643 name = "jobserver"
644 644 version = "0.1.25"
645 645 source = "registry+https://github.com/rust-lang/crates.io-index"
646 646 checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
647 647 dependencies = [
648 648 "libc",
649 649 ]
650 650
651 651 [[package]]
652 652 name = "js-sys"
653 653 version = "0.3.60"
654 654 source = "registry+https://github.com/rust-lang/crates.io-index"
655 655 checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
656 656 dependencies = [
657 657 "wasm-bindgen",
658 658 ]
659 659
660 660 [[package]]
661 661 name = "lazy_static"
662 662 version = "1.4.0"
663 663 source = "registry+https://github.com/rust-lang/crates.io-index"
664 664 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
665 665
666 666 [[package]]
667 667 name = "libc"
668 668 version = "0.2.137"
669 669 source = "registry+https://github.com/rust-lang/crates.io-index"
670 670 checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
671 671
672 672 [[package]]
673 673 name = "libm"
674 674 version = "0.2.6"
675 675 source = "registry+https://github.com/rust-lang/crates.io-index"
676 676 checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
677 677
678 678 [[package]]
679 679 name = "libz-sys"
680 680 version = "1.1.8"
681 681 source = "registry+https://github.com/rust-lang/crates.io-index"
682 682 checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
683 683 dependencies = [
684 684 "cc",
685 685 "pkg-config",
686 686 "vcpkg",
687 687 ]
688 688
689 689 [[package]]
690 690 name = "link-cplusplus"
691 691 version = "1.0.7"
692 692 source = "registry+https://github.com/rust-lang/crates.io-index"
693 693 checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
694 694 dependencies = [
695 695 "cc",
696 696 ]
697 697
698 698 [[package]]
699 699 name = "log"
700 700 version = "0.4.17"
701 701 source = "registry+https://github.com/rust-lang/crates.io-index"
702 702 checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
703 703 dependencies = [
704 704 "cfg-if",
705 705 ]
706 706
707 707 [[package]]
708 name = "logging_timer"
709 version = "1.1.0"
710 source = "registry+https://github.com/rust-lang/crates.io-index"
711 checksum = "64e96f261d684b7089aa576bb74e823241dccd994b27d30fabf1dcb3af284fe9"
712 dependencies = [
713 "log",
714 "logging_timer_proc_macros",
715 ]
716
717 [[package]]
718 name = "logging_timer_proc_macros"
719 version = "1.1.0"
720 source = "registry+https://github.com/rust-lang/crates.io-index"
721 checksum = "10a9062912d7952c5588cc474795e0b9ee008e7e6781127945b85413d4b99d81"
722 dependencies = [
723 "log",
724 "proc-macro2",
725 "quote",
726 "syn",
727 ]
728
729 [[package]]
708 730 name = "memchr"
709 731 version = "2.5.0"
710 732 source = "registry+https://github.com/rust-lang/crates.io-index"
711 733 checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
712 734
713 735 [[package]]
714 736 name = "memmap2"
715 737 version = "0.5.8"
716 738 source = "registry+https://github.com/rust-lang/crates.io-index"
717 739 checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc"
718 740 dependencies = [
719 741 "libc",
720 742 "stable_deref_trait",
721 743 ]
722 744
723 745 [[package]]
724 746 name = "memoffset"
725 747 version = "0.6.5"
726 748 source = "registry+https://github.com/rust-lang/crates.io-index"
727 749 checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
728 750 dependencies = [
729 751 "autocfg",
730 752 ]
731 753
732 754 [[package]]
733 name = "micro-timer"
734 version = "0.4.0"
735 source = "registry+https://github.com/rust-lang/crates.io-index"
736 checksum = "5de32cb59a062672560d6f0842c4aa7714727457b9fe2daf8987d995a176a405"
737 dependencies = [
738 "micro-timer-macros",
739 "scopeguard",
740 ]
741
742 [[package]]
743 name = "micro-timer-macros"
744 version = "0.4.0"
745 source = "registry+https://github.com/rust-lang/crates.io-index"
746 checksum = "cee948b94700125b52dfb68dd17c19f6326696c1df57f92c05ee857463c93ba1"
747 dependencies = [
748 "proc-macro2",
749 "quote",
750 "scopeguard",
751 "syn",
752 ]
753
754 [[package]]
755 755 name = "miniz_oxide"
756 756 version = "0.5.4"
757 757 source = "registry+https://github.com/rust-lang/crates.io-index"
758 758 checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
759 759 dependencies = [
760 760 "adler",
761 761 ]
762 762
763 763 [[package]]
764 764 name = "num-integer"
765 765 version = "0.1.45"
766 766 source = "registry+https://github.com/rust-lang/crates.io-index"
767 767 checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
768 768 dependencies = [
769 769 "autocfg",
770 770 "num-traits",
771 771 ]
772 772
773 773 [[package]]
774 774 name = "num-traits"
775 775 version = "0.2.15"
776 776 source = "registry+https://github.com/rust-lang/crates.io-index"
777 777 checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
778 778 dependencies = [
779 779 "autocfg",
780 780 "libm",
781 781 ]
782 782
783 783 [[package]]
784 784 name = "num_cpus"
785 785 version = "1.14.0"
786 786 source = "registry+https://github.com/rust-lang/crates.io-index"
787 787 checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
788 788 dependencies = [
789 789 "hermit-abi",
790 790 "libc",
791 791 ]
792 792
793 793 [[package]]
794 794 name = "once_cell"
795 795 version = "1.16.0"
796 796 source = "registry+https://github.com/rust-lang/crates.io-index"
797 797 checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
798 798
799 799 [[package]]
800 800 name = "opaque-debug"
801 801 version = "0.3.0"
802 802 source = "registry+https://github.com/rust-lang/crates.io-index"
803 803 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
804 804
805 805 [[package]]
806 806 name = "os_str_bytes"
807 807 version = "6.4.0"
808 808 source = "registry+https://github.com/rust-lang/crates.io-index"
809 809 checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e"
810 810
811 811 [[package]]
812 812 name = "ouroboros"
813 813 version = "0.15.5"
814 814 source = "registry+https://github.com/rust-lang/crates.io-index"
815 815 checksum = "dfbb50b356159620db6ac971c6d5c9ab788c9cc38a6f49619fca2a27acb062ca"
816 816 dependencies = [
817 817 "aliasable",
818 818 "ouroboros_macro",
819 819 ]
820 820
821 821 [[package]]
822 822 name = "ouroboros_macro"
823 823 version = "0.15.5"
824 824 source = "registry+https://github.com/rust-lang/crates.io-index"
825 825 checksum = "4a0d9d1a6191c4f391f87219d1ea42b23f09ee84d64763cd05ee6ea88d9f384d"
826 826 dependencies = [
827 827 "Inflector",
828 828 "proc-macro-error",
829 829 "proc-macro2",
830 830 "quote",
831 831 "syn",
832 832 ]
833 833
834 834 [[package]]
835 835 name = "output_vt100"
836 836 version = "0.1.3"
837 837 source = "registry+https://github.com/rust-lang/crates.io-index"
838 838 checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
839 839 dependencies = [
840 840 "winapi",
841 841 ]
842 842
843 843 [[package]]
844 844 name = "paste"
845 845 version = "1.0.9"
846 846 source = "registry+https://github.com/rust-lang/crates.io-index"
847 847 checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
848 848
849 849 [[package]]
850 850 name = "pkg-config"
851 851 version = "0.3.26"
852 852 source = "registry+https://github.com/rust-lang/crates.io-index"
853 853 checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
854 854
855 855 [[package]]
856 856 name = "ppv-lite86"
857 857 version = "0.2.17"
858 858 source = "registry+https://github.com/rust-lang/crates.io-index"
859 859 checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
860 860
861 861 [[package]]
862 862 name = "pretty_assertions"
863 863 version = "1.3.0"
864 864 source = "registry+https://github.com/rust-lang/crates.io-index"
865 865 checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
866 866 dependencies = [
867 867 "ctor",
868 868 "diff",
869 869 "output_vt100",
870 870 "yansi",
871 871 ]
872 872
873 873 [[package]]
874 874 name = "proc-macro-error"
875 875 version = "1.0.4"
876 876 source = "registry+https://github.com/rust-lang/crates.io-index"
877 877 checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
878 878 dependencies = [
879 879 "proc-macro-error-attr",
880 880 "proc-macro2",
881 881 "quote",
882 882 "syn",
883 883 "version_check",
884 884 ]
885 885
886 886 [[package]]
887 887 name = "proc-macro-error-attr"
888 888 version = "1.0.4"
889 889 source = "registry+https://github.com/rust-lang/crates.io-index"
890 890 checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
891 891 dependencies = [
892 892 "proc-macro2",
893 893 "quote",
894 894 "version_check",
895 895 ]
896 896
897 897 [[package]]
898 898 name = "proc-macro2"
899 899 version = "1.0.47"
900 900 source = "registry+https://github.com/rust-lang/crates.io-index"
901 901 checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
902 902 dependencies = [
903 903 "unicode-ident",
904 904 ]
905 905
906 906 [[package]]
907 907 name = "python3-sys"
908 908 version = "0.7.1"
909 909 source = "registry+https://github.com/rust-lang/crates.io-index"
910 910 checksum = "49f8b50d72fb3015735aa403eebf19bbd72c093bfeeae24ee798be5f2f1aab52"
911 911 dependencies = [
912 912 "libc",
913 913 "regex",
914 914 ]
915 915
916 916 [[package]]
917 917 name = "quote"
918 918 version = "1.0.21"
919 919 source = "registry+https://github.com/rust-lang/crates.io-index"
920 920 checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
921 921 dependencies = [
922 922 "proc-macro2",
923 923 ]
924 924
925 925 [[package]]
926 926 name = "rand"
927 927 version = "0.7.3"
928 928 source = "registry+https://github.com/rust-lang/crates.io-index"
929 929 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
930 930 dependencies = [
931 931 "getrandom 0.1.16",
932 932 "libc",
933 933 "rand_chacha 0.2.2",
934 934 "rand_core 0.5.1",
935 935 "rand_hc",
936 936 ]
937 937
938 938 [[package]]
939 939 name = "rand"
940 940 version = "0.8.5"
941 941 source = "registry+https://github.com/rust-lang/crates.io-index"
942 942 checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
943 943 dependencies = [
944 944 "libc",
945 945 "rand_chacha 0.3.1",
946 946 "rand_core 0.6.4",
947 947 ]
948 948
949 949 [[package]]
950 950 name = "rand_chacha"
951 951 version = "0.2.2"
952 952 source = "registry+https://github.com/rust-lang/crates.io-index"
953 953 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
954 954 dependencies = [
955 955 "ppv-lite86",
956 956 "rand_core 0.5.1",
957 957 ]
958 958
959 959 [[package]]
960 960 name = "rand_chacha"
961 961 version = "0.3.1"
962 962 source = "registry+https://github.com/rust-lang/crates.io-index"
963 963 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
964 964 dependencies = [
965 965 "ppv-lite86",
966 966 "rand_core 0.6.4",
967 967 ]
968 968
969 969 [[package]]
970 970 name = "rand_core"
971 971 version = "0.5.1"
972 972 source = "registry+https://github.com/rust-lang/crates.io-index"
973 973 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
974 974 dependencies = [
975 975 "getrandom 0.1.16",
976 976 ]
977 977
978 978 [[package]]
979 979 name = "rand_core"
980 980 version = "0.6.4"
981 981 source = "registry+https://github.com/rust-lang/crates.io-index"
982 982 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
983 983 dependencies = [
984 984 "getrandom 0.2.8",
985 985 ]
986 986
987 987 [[package]]
988 988 name = "rand_distr"
989 989 version = "0.4.3"
990 990 source = "registry+https://github.com/rust-lang/crates.io-index"
991 991 checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
992 992 dependencies = [
993 993 "num-traits",
994 994 "rand 0.8.5",
995 995 ]
996 996
997 997 [[package]]
998 998 name = "rand_hc"
999 999 version = "0.2.0"
1000 1000 source = "registry+https://github.com/rust-lang/crates.io-index"
1001 1001 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
1002 1002 dependencies = [
1003 1003 "rand_core 0.5.1",
1004 1004 ]
1005 1005
1006 1006 [[package]]
1007 1007 name = "rand_pcg"
1008 1008 version = "0.3.1"
1009 1009 source = "registry+https://github.com/rust-lang/crates.io-index"
1010 1010 checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
1011 1011 dependencies = [
1012 1012 "rand_core 0.6.4",
1013 1013 ]
1014 1014
1015 1015 [[package]]
1016 1016 name = "rand_xoshiro"
1017 1017 version = "0.6.0"
1018 1018 source = "registry+https://github.com/rust-lang/crates.io-index"
1019 1019 checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
1020 1020 dependencies = [
1021 1021 "rand_core 0.6.4",
1022 1022 ]
1023 1023
1024 1024 [[package]]
1025 1025 name = "rayon"
1026 1026 version = "1.5.3"
1027 1027 source = "registry+https://github.com/rust-lang/crates.io-index"
1028 1028 checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
1029 1029 dependencies = [
1030 1030 "autocfg",
1031 1031 "crossbeam-deque",
1032 1032 "either",
1033 1033 "rayon-core",
1034 1034 ]
1035 1035
1036 1036 [[package]]
1037 1037 name = "rayon-core"
1038 1038 version = "1.9.3"
1039 1039 source = "registry+https://github.com/rust-lang/crates.io-index"
1040 1040 checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
1041 1041 dependencies = [
1042 1042 "crossbeam-channel",
1043 1043 "crossbeam-deque",
1044 1044 "crossbeam-utils",
1045 1045 "num_cpus",
1046 1046 ]
1047 1047
1048 1048 [[package]]
1049 1049 name = "redox_syscall"
1050 1050 version = "0.2.16"
1051 1051 source = "registry+https://github.com/rust-lang/crates.io-index"
1052 1052 checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
1053 1053 dependencies = [
1054 1054 "bitflags",
1055 1055 ]
1056 1056
1057 1057 [[package]]
1058 1058 name = "regex"
1059 1059 version = "1.7.0"
1060 1060 source = "registry+https://github.com/rust-lang/crates.io-index"
1061 1061 checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
1062 1062 dependencies = [
1063 1063 "aho-corasick",
1064 1064 "memchr",
1065 1065 "regex-syntax",
1066 1066 ]
1067 1067
1068 1068 [[package]]
1069 1069 name = "regex-syntax"
1070 1070 version = "0.6.28"
1071 1071 source = "registry+https://github.com/rust-lang/crates.io-index"
1072 1072 checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
1073 1073
1074 1074 [[package]]
1075 1075 name = "remove_dir_all"
1076 1076 version = "0.5.3"
1077 1077 source = "registry+https://github.com/rust-lang/crates.io-index"
1078 1078 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
1079 1079 dependencies = [
1080 1080 "winapi",
1081 1081 ]
1082 1082
1083 1083 [[package]]
1084 1084 name = "rhg"
1085 1085 version = "0.1.0"
1086 1086 dependencies = [
1087 1087 "atty",
1088 1088 "chrono",
1089 1089 "clap",
1090 1090 "derive_more",
1091 1091 "env_logger",
1092 1092 "format-bytes",
1093 1093 "hg-core",
1094 1094 "home",
1095 1095 "lazy_static",
1096 1096 "log",
1097 "micro-timer",
1097 "logging_timer",
1098 1098 "rayon",
1099 1099 "regex",
1100 1100 "users",
1101 1101 "which",
1102 1102 ]
1103 1103
1104 1104 [[package]]
1105 1105 name = "rustc_version"
1106 1106 version = "0.4.0"
1107 1107 source = "registry+https://github.com/rust-lang/crates.io-index"
1108 1108 checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
1109 1109 dependencies = [
1110 1110 "semver",
1111 1111 ]
1112 1112
1113 1113 [[package]]
1114 1114 name = "same-file"
1115 1115 version = "1.0.6"
1116 1116 source = "registry+https://github.com/rust-lang/crates.io-index"
1117 1117 checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
1118 1118 dependencies = [
1119 1119 "winapi-util",
1120 1120 ]
1121 1121
1122 1122 [[package]]
1123 1123 name = "scopeguard"
1124 1124 version = "1.1.0"
1125 1125 source = "registry+https://github.com/rust-lang/crates.io-index"
1126 1126 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1127 1127
1128 1128 [[package]]
1129 1129 name = "scratch"
1130 1130 version = "1.0.2"
1131 1131 source = "registry+https://github.com/rust-lang/crates.io-index"
1132 1132 checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
1133 1133
1134 1134 [[package]]
1135 1135 name = "semver"
1136 1136 version = "1.0.14"
1137 1137 source = "registry+https://github.com/rust-lang/crates.io-index"
1138 1138 checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
1139 1139
1140 1140 [[package]]
1141 1141 name = "sha-1"
1142 1142 version = "0.9.8"
1143 1143 source = "registry+https://github.com/rust-lang/crates.io-index"
1144 1144 checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
1145 1145 dependencies = [
1146 1146 "block-buffer 0.9.0",
1147 1147 "cfg-if",
1148 1148 "cpufeatures",
1149 1149 "digest 0.9.0",
1150 1150 "opaque-debug",
1151 1151 ]
1152 1152
1153 1153 [[package]]
1154 1154 name = "sha-1"
1155 1155 version = "0.10.0"
1156 1156 source = "registry+https://github.com/rust-lang/crates.io-index"
1157 1157 checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
1158 1158 dependencies = [
1159 1159 "cfg-if",
1160 1160 "cpufeatures",
1161 1161 "digest 0.10.5",
1162 1162 ]
1163 1163
1164 1164 [[package]]
1165 1165 name = "sized-chunks"
1166 1166 version = "0.6.5"
1167 1167 source = "registry+https://github.com/rust-lang/crates.io-index"
1168 1168 checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
1169 1169 dependencies = [
1170 1170 "bitmaps",
1171 1171 "typenum",
1172 1172 ]
1173 1173
1174 1174 [[package]]
1175 1175 name = "stable_deref_trait"
1176 1176 version = "1.2.0"
1177 1177 source = "registry+https://github.com/rust-lang/crates.io-index"
1178 1178 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
1179 1179
1180 1180 [[package]]
1181 1181 name = "static_assertions"
1182 1182 version = "1.1.0"
1183 1183 source = "registry+https://github.com/rust-lang/crates.io-index"
1184 1184 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1185 1185
1186 1186 [[package]]
1187 1187 name = "strsim"
1188 1188 version = "0.10.0"
1189 1189 source = "registry+https://github.com/rust-lang/crates.io-index"
1190 1190 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
1191 1191
1192 1192 [[package]]
1193 1193 name = "syn"
1194 1194 version = "1.0.103"
1195 1195 source = "registry+https://github.com/rust-lang/crates.io-index"
1196 1196 checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
1197 1197 dependencies = [
1198 1198 "proc-macro2",
1199 1199 "quote",
1200 1200 "unicode-ident",
1201 1201 ]
1202 1202
1203 1203 [[package]]
1204 1204 name = "tempfile"
1205 1205 version = "3.3.0"
1206 1206 source = "registry+https://github.com/rust-lang/crates.io-index"
1207 1207 checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
1208 1208 dependencies = [
1209 1209 "cfg-if",
1210 1210 "fastrand",
1211 1211 "libc",
1212 1212 "redox_syscall",
1213 1213 "remove_dir_all",
1214 1214 "winapi",
1215 1215 ]
1216 1216
1217 1217 [[package]]
1218 1218 name = "termcolor"
1219 1219 version = "1.1.3"
1220 1220 source = "registry+https://github.com/rust-lang/crates.io-index"
1221 1221 checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
1222 1222 dependencies = [
1223 1223 "winapi-util",
1224 1224 ]
1225 1225
1226 1226 [[package]]
1227 1227 name = "thread_local"
1228 1228 version = "1.1.4"
1229 1229 source = "registry+https://github.com/rust-lang/crates.io-index"
1230 1230 checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
1231 1231 dependencies = [
1232 1232 "once_cell",
1233 1233 ]
1234 1234
1235 1235 [[package]]
1236 1236 name = "time"
1237 1237 version = "0.1.44"
1238 1238 source = "registry+https://github.com/rust-lang/crates.io-index"
1239 1239 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
1240 1240 dependencies = [
1241 1241 "libc",
1242 1242 "wasi 0.10.0+wasi-snapshot-preview1",
1243 1243 "winapi",
1244 1244 ]
1245 1245
1246 1246 [[package]]
1247 1247 name = "twox-hash"
1248 1248 version = "1.6.3"
1249 1249 source = "registry+https://github.com/rust-lang/crates.io-index"
1250 1250 checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
1251 1251 dependencies = [
1252 1252 "cfg-if",
1253 1253 "rand 0.8.5",
1254 1254 "static_assertions",
1255 1255 ]
1256 1256
1257 1257 [[package]]
1258 1258 name = "typenum"
1259 1259 version = "1.15.0"
1260 1260 source = "registry+https://github.com/rust-lang/crates.io-index"
1261 1261 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
1262 1262
1263 1263 [[package]]
1264 1264 name = "unicode-ident"
1265 1265 version = "1.0.5"
1266 1266 source = "registry+https://github.com/rust-lang/crates.io-index"
1267 1267 checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
1268 1268
1269 1269 [[package]]
1270 1270 name = "unicode-width"
1271 1271 version = "0.1.10"
1272 1272 source = "registry+https://github.com/rust-lang/crates.io-index"
1273 1273 checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
1274 1274
1275 1275 [[package]]
1276 1276 name = "users"
1277 1277 version = "0.11.0"
1278 1278 source = "registry+https://github.com/rust-lang/crates.io-index"
1279 1279 checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
1280 1280 dependencies = [
1281 1281 "libc",
1282 1282 "log",
1283 1283 ]
1284 1284
1285 1285 [[package]]
1286 1286 name = "vcpkg"
1287 1287 version = "0.2.15"
1288 1288 source = "registry+https://github.com/rust-lang/crates.io-index"
1289 1289 checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
1290 1290
1291 1291 [[package]]
1292 1292 name = "vcsgraph"
1293 1293 version = "0.2.0"
1294 1294 source = "registry+https://github.com/rust-lang/crates.io-index"
1295 1295 checksum = "4cb68c231e2575f7503a7c19213875f9d4ec2e84e963a56ce3de4b6bee351ef7"
1296 1296 dependencies = [
1297 1297 "hex",
1298 1298 "rand 0.7.3",
1299 1299 "sha-1 0.9.8",
1300 1300 ]
1301 1301
1302 1302 [[package]]
1303 1303 name = "version_check"
1304 1304 version = "0.9.4"
1305 1305 source = "registry+https://github.com/rust-lang/crates.io-index"
1306 1306 checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
1307 1307
1308 1308 [[package]]
1309 1309 name = "wasi"
1310 1310 version = "0.9.0+wasi-snapshot-preview1"
1311 1311 source = "registry+https://github.com/rust-lang/crates.io-index"
1312 1312 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1313 1313
1314 1314 [[package]]
1315 1315 name = "wasi"
1316 1316 version = "0.10.0+wasi-snapshot-preview1"
1317 1317 source = "registry+https://github.com/rust-lang/crates.io-index"
1318 1318 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
1319 1319
1320 1320 [[package]]
1321 1321 name = "wasi"
1322 1322 version = "0.11.0+wasi-snapshot-preview1"
1323 1323 source = "registry+https://github.com/rust-lang/crates.io-index"
1324 1324 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1325 1325
1326 1326 [[package]]
1327 1327 name = "wasm-bindgen"
1328 1328 version = "0.2.83"
1329 1329 source = "registry+https://github.com/rust-lang/crates.io-index"
1330 1330 checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
1331 1331 dependencies = [
1332 1332 "cfg-if",
1333 1333 "wasm-bindgen-macro",
1334 1334 ]
1335 1335
1336 1336 [[package]]
1337 1337 name = "wasm-bindgen-backend"
1338 1338 version = "0.2.83"
1339 1339 source = "registry+https://github.com/rust-lang/crates.io-index"
1340 1340 checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
1341 1341 dependencies = [
1342 1342 "bumpalo",
1343 1343 "log",
1344 1344 "once_cell",
1345 1345 "proc-macro2",
1346 1346 "quote",
1347 1347 "syn",
1348 1348 "wasm-bindgen-shared",
1349 1349 ]
1350 1350
1351 1351 [[package]]
1352 1352 name = "wasm-bindgen-macro"
1353 1353 version = "0.2.83"
1354 1354 source = "registry+https://github.com/rust-lang/crates.io-index"
1355 1355 checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
1356 1356 dependencies = [
1357 1357 "quote",
1358 1358 "wasm-bindgen-macro-support",
1359 1359 ]
1360 1360
1361 1361 [[package]]
1362 1362 name = "wasm-bindgen-macro-support"
1363 1363 version = "0.2.83"
1364 1364 source = "registry+https://github.com/rust-lang/crates.io-index"
1365 1365 checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
1366 1366 dependencies = [
1367 1367 "proc-macro2",
1368 1368 "quote",
1369 1369 "syn",
1370 1370 "wasm-bindgen-backend",
1371 1371 "wasm-bindgen-shared",
1372 1372 ]
1373 1373
1374 1374 [[package]]
1375 1375 name = "wasm-bindgen-shared"
1376 1376 version = "0.2.83"
1377 1377 source = "registry+https://github.com/rust-lang/crates.io-index"
1378 1378 checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
1379 1379
1380 1380 [[package]]
1381 1381 name = "which"
1382 1382 version = "4.3.0"
1383 1383 source = "registry+https://github.com/rust-lang/crates.io-index"
1384 1384 checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
1385 1385 dependencies = [
1386 1386 "either",
1387 1387 "libc",
1388 1388 "once_cell",
1389 1389 ]
1390 1390
1391 1391 [[package]]
1392 1392 name = "winapi"
1393 1393 version = "0.3.9"
1394 1394 source = "registry+https://github.com/rust-lang/crates.io-index"
1395 1395 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1396 1396 dependencies = [
1397 1397 "winapi-i686-pc-windows-gnu",
1398 1398 "winapi-x86_64-pc-windows-gnu",
1399 1399 ]
1400 1400
1401 1401 [[package]]
1402 1402 name = "winapi-i686-pc-windows-gnu"
1403 1403 version = "0.4.0"
1404 1404 source = "registry+https://github.com/rust-lang/crates.io-index"
1405 1405 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1406 1406
1407 1407 [[package]]
1408 1408 name = "winapi-util"
1409 1409 version = "0.1.5"
1410 1410 source = "registry+https://github.com/rust-lang/crates.io-index"
1411 1411 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1412 1412 dependencies = [
1413 1413 "winapi",
1414 1414 ]
1415 1415
1416 1416 [[package]]
1417 1417 name = "winapi-x86_64-pc-windows-gnu"
1418 1418 version = "0.4.0"
1419 1419 source = "registry+https://github.com/rust-lang/crates.io-index"
1420 1420 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1421 1421
1422 1422 [[package]]
1423 1423 name = "yansi"
1424 1424 version = "0.5.1"
1425 1425 source = "registry+https://github.com/rust-lang/crates.io-index"
1426 1426 checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
1427 1427
1428 1428 [[package]]
1429 1429 name = "zstd"
1430 1430 version = "0.11.2+zstd.1.5.2"
1431 1431 source = "registry+https://github.com/rust-lang/crates.io-index"
1432 1432 checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
1433 1433 dependencies = [
1434 1434 "zstd-safe",
1435 1435 ]
1436 1436
1437 1437 [[package]]
1438 1438 name = "zstd-safe"
1439 1439 version = "5.0.2+zstd.1.5.2"
1440 1440 source = "registry+https://github.com/rust-lang/crates.io-index"
1441 1441 checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
1442 1442 dependencies = [
1443 1443 "libc",
1444 1444 "zstd-sys",
1445 1445 ]
1446 1446
1447 1447 [[package]]
1448 1448 name = "zstd-sys"
1449 1449 version = "2.0.1+zstd.1.5.2"
1450 1450 source = "registry+https://github.com/rust-lang/crates.io-index"
1451 1451 checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b"
1452 1452 dependencies = [
1453 1453 "cc",
1454 1454 "libc",
1455 1455 ]
@@ -1,52 +1,52 b''
1 1 [package]
2 2 name = "hg-core"
3 3 version = "0.1.0"
4 4 authors = ["Georges Racinet <gracinet@anybox.fr>"]
5 5 description = "Mercurial pure Rust core library, with no assumption on Python bindings (FFI)"
6 6 edition = "2021"
7 7
8 8 [lib]
9 9 name = "hg"
10 10
11 11 [dependencies]
12 12 bitflags = "1.3.2"
13 13 bytes-cast = "0.2.0"
14 14 byteorder = "1.4.3"
15 15 derive_more = "0.99.17"
16 16 hashbrown = { version = "0.13.1", features = ["rayon"] }
17 17 home = "0.5.4"
18 18 im-rc = "15.1.0"
19 19 itertools = "0.10.5"
20 20 lazy_static = "1.4.0"
21 21 libc = "0.2.137"
22 logging_timer = "1.1.0"
22 23 ouroboros = "0.15.5"
23 24 rand = "0.8.5"
24 25 rand_pcg = "0.3.1"
25 26 rand_distr = "0.4.3"
26 27 rayon = "1.5.3"
27 28 regex = "1.7.0"
28 29 sha-1 = "0.10.0"
29 30 twox-hash = "1.6.3"
30 31 same-file = "1.0.6"
31 32 tempfile = "3.3.0"
32 33 thread_local = "1.1.4"
33 34 crossbeam-channel = "0.5.6"
34 micro-timer = "0.4.0"
35 35 log = "0.4.17"
36 36 memmap2 = { version = "0.5.8", features = ["stable_deref_trait"] }
37 37 zstd = "0.11.2"
38 38 format-bytes = "0.3.0"
39 39 # once_cell 1.15 uses edition 2021, while the heptapod CI
40 40 # uses an old version of Cargo that doesn't support it.
41 41 once_cell = "1.16.0"
42 42
43 43 # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until
44 44 # we have a clearer view of which backend is the fastest.
45 45 [dependencies.flate2]
46 46 version = "1.0.24"
47 47 features = ["zlib"]
48 48 default-features = false
49 49
50 50 [dev-dependencies]
51 51 clap = { version = "4.0.24", features = ["derive"] }
52 52 pretty_assertions = "1.1.0"
@@ -1,136 +1,135 b''
1 1 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
2 2 //
3 3 // This software may be used and distributed according to the terms of the
4 4 // GNU General Public License version 2 or any later version.
5 5
6 6 use crate::errors::HgError;
7 7 use crate::utils::hg_path::HgPath;
8 8 use crate::{dirstate::EntryState, DirstateEntry, DirstateParents};
9 9 use byteorder::{BigEndian, WriteBytesExt};
10 10 use bytes_cast::{unaligned, BytesCast};
11 use micro_timer::timed;
12 11
13 12 /// Parents are stored in the dirstate as byte hashes.
14 13 pub const PARENT_SIZE: usize = 20;
15 14 /// Dirstate entries have a static part of 8 + 32 + 32 + 32 + 32 bits.
16 15 const MIN_ENTRY_SIZE: usize = 17;
17 16
18 17 type ParseResult<'a> = (
19 18 &'a DirstateParents,
20 19 Vec<(&'a HgPath, DirstateEntry)>,
21 20 Vec<(&'a HgPath, &'a HgPath)>,
22 21 );
23 22
24 23 pub fn parse_dirstate_parents(
25 24 contents: &[u8],
26 25 ) -> Result<&DirstateParents, HgError> {
27 26 let (parents, _rest) = DirstateParents::from_bytes(contents)
28 27 .map_err(|_| HgError::corrupted("Too little data for dirstate."))?;
29 28 Ok(parents)
30 29 }
31 30
32 #[timed]
31 #[logging_timer::time("trace")]
33 32 pub fn parse_dirstate(contents: &[u8]) -> Result<ParseResult, HgError> {
34 33 let mut copies = Vec::new();
35 34 let mut entries = Vec::new();
36 35 let parents =
37 36 parse_dirstate_entries(contents, |path, entry, copy_source| {
38 37 if let Some(source) = copy_source {
39 38 copies.push((path, source));
40 39 }
41 40 entries.push((path, *entry));
42 41 Ok(())
43 42 })?;
44 43 Ok((parents, entries, copies))
45 44 }
46 45
47 46 #[derive(BytesCast)]
48 47 #[repr(C)]
49 48 struct RawEntry {
50 49 state: u8,
51 50 mode: unaligned::I32Be,
52 51 size: unaligned::I32Be,
53 52 mtime: unaligned::I32Be,
54 53 length: unaligned::I32Be,
55 54 }
56 55
57 56 pub fn parse_dirstate_entries<'a>(
58 57 mut contents: &'a [u8],
59 58 mut each_entry: impl FnMut(
60 59 &'a HgPath,
61 60 &DirstateEntry,
62 61 Option<&'a HgPath>,
63 62 ) -> Result<(), HgError>,
64 63 ) -> Result<&'a DirstateParents, HgError> {
65 64 let (parents, rest) = DirstateParents::from_bytes(contents)
66 65 .map_err(|_| HgError::corrupted("Too little data for dirstate."))?;
67 66 contents = rest;
68 67 while !contents.is_empty() {
69 68 let (raw_entry, rest) = RawEntry::from_bytes(contents)
70 69 .map_err(|_| HgError::corrupted("Overflow in dirstate."))?;
71 70
72 71 let entry = DirstateEntry::from_v1_data(
73 72 EntryState::try_from(raw_entry.state)?,
74 73 raw_entry.mode.get(),
75 74 raw_entry.size.get(),
76 75 raw_entry.mtime.get(),
77 76 );
78 77 let (paths, rest) =
79 78 u8::slice_from_bytes(rest, raw_entry.length.get() as usize)
80 79 .map_err(|_| HgError::corrupted("Overflow in dirstate."))?;
81 80
82 81 // `paths` is either a single path, or two paths separated by a NULL
83 82 // byte
84 83 let mut iter = paths.splitn(2, |&byte| byte == b'\0');
85 84 let path = HgPath::new(
86 85 iter.next().expect("splitn always yields at least one item"),
87 86 );
88 87 let copy_source = iter.next().map(HgPath::new);
89 88 each_entry(path, &entry, copy_source)?;
90 89
91 90 contents = rest;
92 91 }
93 92 Ok(parents)
94 93 }
95 94
96 95 fn packed_filename_and_copy_source_size(
97 96 filename: &HgPath,
98 97 copy_source: Option<&HgPath>,
99 98 ) -> usize {
100 99 filename.len()
101 100 + if let Some(source) = copy_source {
102 101 b"\0".len() + source.len()
103 102 } else {
104 103 0
105 104 }
106 105 }
107 106
108 107 pub fn packed_entry_size(
109 108 filename: &HgPath,
110 109 copy_source: Option<&HgPath>,
111 110 ) -> usize {
112 111 MIN_ENTRY_SIZE
113 112 + packed_filename_and_copy_source_size(filename, copy_source)
114 113 }
115 114
116 115 pub fn pack_entry(
117 116 filename: &HgPath,
118 117 entry: &DirstateEntry,
119 118 copy_source: Option<&HgPath>,
120 119 packed: &mut Vec<u8>,
121 120 ) {
122 121 let length = packed_filename_and_copy_source_size(filename, copy_source);
123 122 let (state, mode, size, mtime) = entry.v1_data();
124 123
125 124 // Unwrapping because `impl std::io::Write for Vec<u8>` never errors
126 125 packed.write_u8(state).unwrap();
127 126 packed.write_i32::<BigEndian>(mode).unwrap();
128 127 packed.write_i32::<BigEndian>(size).unwrap();
129 128 packed.write_i32::<BigEndian>(mtime).unwrap();
130 129 packed.write_i32::<BigEndian>(length as i32).unwrap();
131 130 packed.extend(filename.as_bytes());
132 131 if let Some(source) = copy_source {
133 132 packed.push(b'\0');
134 133 packed.extend(source.as_bytes());
135 134 }
136 135 }
@@ -1,1907 +1,1906 b''
1 1 use bytes_cast::BytesCast;
2 use micro_timer::timed;
3 2 use std::borrow::Cow;
4 3 use std::path::PathBuf;
5 4
6 5 use super::on_disk;
7 6 use super::on_disk::DirstateV2ParseError;
8 7 use super::owning::OwningDirstateMap;
9 8 use super::path_with_basename::WithBasename;
10 9 use crate::dirstate::parsers::pack_entry;
11 10 use crate::dirstate::parsers::packed_entry_size;
12 11 use crate::dirstate::parsers::parse_dirstate_entries;
13 12 use crate::dirstate::CopyMapIter;
14 13 use crate::dirstate::DirstateV2Data;
15 14 use crate::dirstate::ParentFileData;
16 15 use crate::dirstate::StateMapIter;
17 16 use crate::dirstate::TruncatedTimestamp;
18 17 use crate::matchers::Matcher;
19 18 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 19 use crate::DirstateEntry;
21 20 use crate::DirstateError;
22 21 use crate::DirstateMapError;
23 22 use crate::DirstateParents;
24 23 use crate::DirstateStatus;
25 24 use crate::FastHashbrownMap as FastHashMap;
26 25 use crate::PatternFileWarning;
27 26 use crate::StatusError;
28 27 use crate::StatusOptions;
29 28
30 29 /// Append to an existing data file if the amount of unreachable data (not used
31 30 /// anymore) is less than this fraction of the total amount of existing data.
32 31 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33 32
34 33 #[derive(Debug, PartialEq, Eq)]
35 34 /// Version of the on-disk format
36 35 pub enum DirstateVersion {
37 36 V1,
38 37 V2,
39 38 }
40 39
41 40 #[derive(Debug)]
42 41 pub struct DirstateMap<'on_disk> {
43 42 /// Contents of the `.hg/dirstate` file
44 43 pub(super) on_disk: &'on_disk [u8],
45 44
46 45 pub(super) root: ChildNodes<'on_disk>,
47 46
48 47 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
49 48 pub(super) nodes_with_entry_count: u32,
50 49
51 50 /// Number of nodes anywhere in the tree that have
52 51 /// `.copy_source.is_some()`.
53 52 pub(super) nodes_with_copy_source_count: u32,
54 53
55 54 /// See on_disk::Header
56 55 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
57 56
58 57 /// How many bytes of `on_disk` are not used anymore
59 58 pub(super) unreachable_bytes: u32,
60 59
61 60 /// Size of the data used to first load this `DirstateMap`. Used in case
62 61 /// we need to write some new metadata, but no new data on disk.
63 62 pub(super) old_data_size: usize,
64 63
65 64 pub(super) dirstate_version: DirstateVersion,
66 65 }
67 66
68 67 /// Using a plain `HgPathBuf` of the full path from the repository root as a
69 68 /// map key would also work: all paths in a given map have the same parent
70 69 /// path, so comparing full paths gives the same result as comparing base
71 70 /// names. However `HashMap` would waste time always re-hashing the same
72 71 /// string prefix.
73 72 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
74 73
75 74 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
76 75 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
77 76 #[derive(Debug)]
78 77 pub(super) enum BorrowedPath<'tree, 'on_disk> {
79 78 InMemory(&'tree HgPathBuf),
80 79 OnDisk(&'on_disk HgPath),
81 80 }
82 81
83 82 #[derive(Debug)]
84 83 pub(super) enum ChildNodes<'on_disk> {
85 84 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
86 85 OnDisk(&'on_disk [on_disk::Node]),
87 86 }
88 87
89 88 #[derive(Debug)]
90 89 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
91 90 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
92 91 OnDisk(&'on_disk [on_disk::Node]),
93 92 }
94 93
95 94 #[derive(Debug)]
96 95 pub(super) enum NodeRef<'tree, 'on_disk> {
97 96 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
98 97 OnDisk(&'on_disk on_disk::Node),
99 98 }
100 99
101 100 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
102 101 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
103 102 match *self {
104 103 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
105 104 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
106 105 }
107 106 }
108 107 }
109 108
110 109 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
111 110 type Target = HgPath;
112 111
113 112 fn deref(&self) -> &HgPath {
114 113 match *self {
115 114 BorrowedPath::InMemory(in_memory) => in_memory,
116 115 BorrowedPath::OnDisk(on_disk) => on_disk,
117 116 }
118 117 }
119 118 }
120 119
121 120 impl Default for ChildNodes<'_> {
122 121 fn default() -> Self {
123 122 ChildNodes::InMemory(Default::default())
124 123 }
125 124 }
126 125
127 126 impl<'on_disk> ChildNodes<'on_disk> {
128 127 pub(super) fn as_ref<'tree>(
129 128 &'tree self,
130 129 ) -> ChildNodesRef<'tree, 'on_disk> {
131 130 match self {
132 131 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
133 132 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
134 133 }
135 134 }
136 135
137 136 pub(super) fn is_empty(&self) -> bool {
138 137 match self {
139 138 ChildNodes::InMemory(nodes) => nodes.is_empty(),
140 139 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
141 140 }
142 141 }
143 142
144 143 fn make_mut(
145 144 &mut self,
146 145 on_disk: &'on_disk [u8],
147 146 unreachable_bytes: &mut u32,
148 147 ) -> Result<
149 148 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
150 149 DirstateV2ParseError,
151 150 > {
152 151 match self {
153 152 ChildNodes::InMemory(nodes) => Ok(nodes),
154 153 ChildNodes::OnDisk(nodes) => {
155 154 *unreachable_bytes +=
156 155 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
157 156 let nodes = nodes
158 157 .iter()
159 158 .map(|node| {
160 159 Ok((
161 160 node.path(on_disk)?,
162 161 node.to_in_memory_node(on_disk)?,
163 162 ))
164 163 })
165 164 .collect::<Result<_, _>>()?;
166 165 *self = ChildNodes::InMemory(nodes);
167 166 match self {
168 167 ChildNodes::InMemory(nodes) => Ok(nodes),
169 168 ChildNodes::OnDisk(_) => unreachable!(),
170 169 }
171 170 }
172 171 }
173 172 }
174 173 }
175 174
176 175 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
177 176 pub(super) fn get(
178 177 &self,
179 178 base_name: &HgPath,
180 179 on_disk: &'on_disk [u8],
181 180 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
182 181 match self {
183 182 ChildNodesRef::InMemory(nodes) => Ok(nodes
184 183 .get_key_value(base_name)
185 184 .map(|(k, v)| NodeRef::InMemory(k, v))),
186 185 ChildNodesRef::OnDisk(nodes) => {
187 186 let mut parse_result = Ok(());
188 187 let search_result = nodes.binary_search_by(|node| {
189 188 match node.base_name(on_disk) {
190 189 Ok(node_base_name) => node_base_name.cmp(base_name),
191 190 Err(e) => {
192 191 parse_result = Err(e);
193 192 // Dummy comparison result, `search_result` won’t
194 193 // be used since `parse_result` is an error
195 194 std::cmp::Ordering::Equal
196 195 }
197 196 }
198 197 });
199 198 parse_result.map(|()| {
200 199 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
201 200 })
202 201 }
203 202 }
204 203 }
205 204
206 205 /// Iterate in undefined order
207 206 pub(super) fn iter(
208 207 &self,
209 208 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
210 209 match self {
211 210 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
212 211 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
213 212 ),
214 213 ChildNodesRef::OnDisk(nodes) => {
215 214 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
216 215 }
217 216 }
218 217 }
219 218
220 219 /// Iterate in parallel in undefined order
221 220 pub(super) fn par_iter(
222 221 &self,
223 222 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
224 223 {
225 224 use rayon::prelude::*;
226 225 match self {
227 226 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
228 227 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
229 228 ),
230 229 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
231 230 nodes.par_iter().map(NodeRef::OnDisk),
232 231 ),
233 232 }
234 233 }
235 234
236 235 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
237 236 match self {
238 237 ChildNodesRef::InMemory(nodes) => {
239 238 let mut vec: Vec<_> = nodes
240 239 .iter()
241 240 .map(|(k, v)| NodeRef::InMemory(k, v))
242 241 .collect();
243 242 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
244 243 match node {
245 244 NodeRef::InMemory(path, _node) => path.base_name(),
246 245 NodeRef::OnDisk(_) => unreachable!(),
247 246 }
248 247 }
249 248 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
250 249 // value: https://github.com/rust-lang/rust/issues/34162
251 250 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
252 251 vec
253 252 }
254 253 ChildNodesRef::OnDisk(nodes) => {
255 254 // Nodes on disk are already sorted
256 255 nodes.iter().map(NodeRef::OnDisk).collect()
257 256 }
258 257 }
259 258 }
260 259 }
261 260
262 261 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
263 262 pub(super) fn full_path(
264 263 &self,
265 264 on_disk: &'on_disk [u8],
266 265 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
267 266 match self {
268 267 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
269 268 NodeRef::OnDisk(node) => node.full_path(on_disk),
270 269 }
271 270 }
272 271
273 272 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
274 273 /// HgPath>` detached from `'tree`
275 274 pub(super) fn full_path_borrowed(
276 275 &self,
277 276 on_disk: &'on_disk [u8],
278 277 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
279 278 match self {
280 279 NodeRef::InMemory(path, _node) => match path.full_path() {
281 280 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
282 281 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
283 282 },
284 283 NodeRef::OnDisk(node) => {
285 284 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
286 285 }
287 286 }
288 287 }
289 288
290 289 pub(super) fn base_name(
291 290 &self,
292 291 on_disk: &'on_disk [u8],
293 292 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
294 293 match self {
295 294 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
296 295 NodeRef::OnDisk(node) => node.base_name(on_disk),
297 296 }
298 297 }
299 298
300 299 pub(super) fn children(
301 300 &self,
302 301 on_disk: &'on_disk [u8],
303 302 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
304 303 match self {
305 304 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
306 305 NodeRef::OnDisk(node) => {
307 306 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
308 307 }
309 308 }
310 309 }
311 310
312 311 pub(super) fn has_copy_source(&self) -> bool {
313 312 match self {
314 313 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
315 314 NodeRef::OnDisk(node) => node.has_copy_source(),
316 315 }
317 316 }
318 317
319 318 pub(super) fn copy_source(
320 319 &self,
321 320 on_disk: &'on_disk [u8],
322 321 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
323 322 match self {
324 323 NodeRef::InMemory(_path, node) => {
325 324 Ok(node.copy_source.as_ref().map(|s| &**s))
326 325 }
327 326 NodeRef::OnDisk(node) => node.copy_source(on_disk),
328 327 }
329 328 }
330 329 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
331 330 /// HgPath>` detached from `'tree`
332 331 pub(super) fn copy_source_borrowed(
333 332 &self,
334 333 on_disk: &'on_disk [u8],
335 334 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
336 335 {
337 336 Ok(match self {
338 337 NodeRef::InMemory(_path, node) => {
339 338 node.copy_source.as_ref().map(|source| match source {
340 339 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
341 340 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
342 341 })
343 342 }
344 343 NodeRef::OnDisk(node) => node
345 344 .copy_source(on_disk)?
346 345 .map(|source| BorrowedPath::OnDisk(source)),
347 346 })
348 347 }
349 348
350 349 pub(super) fn entry(
351 350 &self,
352 351 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
353 352 match self {
354 353 NodeRef::InMemory(_path, node) => {
355 354 Ok(node.data.as_entry().copied())
356 355 }
357 356 NodeRef::OnDisk(node) => node.entry(),
358 357 }
359 358 }
360 359
361 360 pub(super) fn cached_directory_mtime(
362 361 &self,
363 362 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
364 363 match self {
365 364 NodeRef::InMemory(_path, node) => Ok(match node.data {
366 365 NodeData::CachedDirectory { mtime } => Some(mtime),
367 366 _ => None,
368 367 }),
369 368 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
370 369 }
371 370 }
372 371
373 372 pub(super) fn descendants_with_entry_count(&self) -> u32 {
374 373 match self {
375 374 NodeRef::InMemory(_path, node) => {
376 375 node.descendants_with_entry_count
377 376 }
378 377 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
379 378 }
380 379 }
381 380
382 381 pub(super) fn tracked_descendants_count(&self) -> u32 {
383 382 match self {
384 383 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
385 384 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
386 385 }
387 386 }
388 387 }
389 388
390 389 /// Represents a file or a directory
391 390 #[derive(Default, Debug)]
392 391 pub(super) struct Node<'on_disk> {
393 392 pub(super) data: NodeData,
394 393
395 394 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
396 395
397 396 pub(super) children: ChildNodes<'on_disk>,
398 397
399 398 /// How many (non-inclusive) descendants of this node have an entry.
400 399 pub(super) descendants_with_entry_count: u32,
401 400
402 401 /// How many (non-inclusive) descendants of this node have an entry whose
403 402 /// state is "tracked".
404 403 pub(super) tracked_descendants_count: u32,
405 404 }
406 405
407 406 #[derive(Debug)]
408 407 pub(super) enum NodeData {
409 408 Entry(DirstateEntry),
410 409 CachedDirectory { mtime: TruncatedTimestamp },
411 410 None,
412 411 }
413 412
414 413 impl Default for NodeData {
415 414 fn default() -> Self {
416 415 NodeData::None
417 416 }
418 417 }
419 418
420 419 impl NodeData {
421 420 fn has_entry(&self) -> bool {
422 421 match self {
423 422 NodeData::Entry(_) => true,
424 423 _ => false,
425 424 }
426 425 }
427 426
428 427 fn as_entry(&self) -> Option<&DirstateEntry> {
429 428 match self {
430 429 NodeData::Entry(entry) => Some(entry),
431 430 _ => None,
432 431 }
433 432 }
434 433
435 434 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
436 435 match self {
437 436 NodeData::Entry(entry) => Some(entry),
438 437 _ => None,
439 438 }
440 439 }
441 440 }
442 441
443 442 impl<'on_disk> DirstateMap<'on_disk> {
444 443 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
445 444 Self {
446 445 on_disk,
447 446 root: ChildNodes::default(),
448 447 nodes_with_entry_count: 0,
449 448 nodes_with_copy_source_count: 0,
450 449 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
451 450 unreachable_bytes: 0,
452 451 old_data_size: 0,
453 452 dirstate_version: DirstateVersion::V1,
454 453 }
455 454 }
456 455
457 #[timed]
456 #[logging_timer::time("trace")]
458 457 pub fn new_v2(
459 458 on_disk: &'on_disk [u8],
460 459 data_size: usize,
461 460 metadata: &[u8],
462 461 ) -> Result<Self, DirstateError> {
463 462 if let Some(data) = on_disk.get(..data_size) {
464 463 Ok(on_disk::read(data, metadata)?)
465 464 } else {
466 465 Err(DirstateV2ParseError::new("not enough bytes on disk").into())
467 466 }
468 467 }
469 468
470 #[timed]
469 #[logging_timer::time("trace")]
471 470 pub fn new_v1(
472 471 on_disk: &'on_disk [u8],
473 472 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
474 473 let mut map = Self::empty(on_disk);
475 474 if map.on_disk.is_empty() {
476 475 return Ok((map, None));
477 476 }
478 477
479 478 let parents = parse_dirstate_entries(
480 479 map.on_disk,
481 480 |path, entry, copy_source| {
482 481 let tracked = entry.tracked();
483 482 let node = Self::get_or_insert_node_inner(
484 483 map.on_disk,
485 484 &mut map.unreachable_bytes,
486 485 &mut map.root,
487 486 path,
488 487 WithBasename::to_cow_borrowed,
489 488 |ancestor| {
490 489 if tracked {
491 490 ancestor.tracked_descendants_count += 1
492 491 }
493 492 ancestor.descendants_with_entry_count += 1
494 493 },
495 494 )?;
496 495 assert!(
497 496 !node.data.has_entry(),
498 497 "duplicate dirstate entry in read"
499 498 );
500 499 assert!(
501 500 node.copy_source.is_none(),
502 501 "duplicate dirstate entry in read"
503 502 );
504 503 node.data = NodeData::Entry(*entry);
505 504 node.copy_source = copy_source.map(Cow::Borrowed);
506 505 map.nodes_with_entry_count += 1;
507 506 if copy_source.is_some() {
508 507 map.nodes_with_copy_source_count += 1
509 508 }
510 509 Ok(())
511 510 },
512 511 )?;
513 512 let parents = Some(parents.clone());
514 513
515 514 Ok((map, parents))
516 515 }
517 516
518 517 /// Assuming dirstate-v2 format, returns whether the next write should
519 518 /// append to the existing data file that contains `self.on_disk` (true),
520 519 /// or create a new data file from scratch (false).
521 520 pub(super) fn write_should_append(&self) -> bool {
522 521 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
523 522 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
524 523 }
525 524
526 525 fn get_node<'tree>(
527 526 &'tree self,
528 527 path: &HgPath,
529 528 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
530 529 let mut children = self.root.as_ref();
531 530 let mut components = path.components();
532 531 let mut component =
533 532 components.next().expect("expected at least one components");
534 533 loop {
535 534 if let Some(child) = children.get(component, self.on_disk)? {
536 535 if let Some(next_component) = components.next() {
537 536 component = next_component;
538 537 children = child.children(self.on_disk)?;
539 538 } else {
540 539 return Ok(Some(child));
541 540 }
542 541 } else {
543 542 return Ok(None);
544 543 }
545 544 }
546 545 }
547 546
548 547 /// Returns a mutable reference to the node at `path` if it exists
549 548 ///
550 549 /// `each_ancestor` is a callback that is called for each ancestor node
551 550 /// when descending the tree. It is used to keep the different counters
552 551 /// of the `DirstateMap` up-to-date.
553 552 fn get_node_mut<'tree>(
554 553 &'tree mut self,
555 554 path: &HgPath,
556 555 each_ancestor: impl FnMut(&mut Node),
557 556 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
558 557 Self::get_node_mut_inner(
559 558 self.on_disk,
560 559 &mut self.unreachable_bytes,
561 560 &mut self.root,
562 561 path,
563 562 each_ancestor,
564 563 )
565 564 }
566 565
567 566 /// Lower-level version of `get_node_mut`.
568 567 ///
569 568 /// This takes `root` instead of `&mut self` so that callers can mutate
570 569 /// other fields while the returned borrow is still valid.
571 570 ///
572 571 /// `each_ancestor` is a callback that is called for each ancestor node
573 572 /// when descending the tree. It is used to keep the different counters
574 573 /// of the `DirstateMap` up-to-date.
575 574 fn get_node_mut_inner<'tree>(
576 575 on_disk: &'on_disk [u8],
577 576 unreachable_bytes: &mut u32,
578 577 root: &'tree mut ChildNodes<'on_disk>,
579 578 path: &HgPath,
580 579 mut each_ancestor: impl FnMut(&mut Node),
581 580 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
582 581 let mut children = root;
583 582 let mut components = path.components();
584 583 let mut component =
585 584 components.next().expect("expected at least one components");
586 585 loop {
587 586 if let Some(child) = children
588 587 .make_mut(on_disk, unreachable_bytes)?
589 588 .get_mut(component)
590 589 {
591 590 if let Some(next_component) = components.next() {
592 591 each_ancestor(child);
593 592 component = next_component;
594 593 children = &mut child.children;
595 594 } else {
596 595 return Ok(Some(child));
597 596 }
598 597 } else {
599 598 return Ok(None);
600 599 }
601 600 }
602 601 }
603 602
604 603 /// Get a mutable reference to the node at `path`, creating it if it does
605 604 /// not exist.
606 605 ///
607 606 /// `each_ancestor` is a callback that is called for each ancestor node
608 607 /// when descending the tree. It is used to keep the different counters
609 608 /// of the `DirstateMap` up-to-date.
610 609 fn get_or_insert_node<'tree, 'path>(
611 610 &'tree mut self,
612 611 path: &'path HgPath,
613 612 each_ancestor: impl FnMut(&mut Node),
614 613 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
615 614 Self::get_or_insert_node_inner(
616 615 self.on_disk,
617 616 &mut self.unreachable_bytes,
618 617 &mut self.root,
619 618 path,
620 619 WithBasename::to_cow_owned,
621 620 each_ancestor,
622 621 )
623 622 }
624 623
625 624 /// Lower-level version of `get_or_insert_node_inner`, which is used when
626 625 /// parsing disk data to remove allocations for new nodes.
627 626 fn get_or_insert_node_inner<'tree, 'path>(
628 627 on_disk: &'on_disk [u8],
629 628 unreachable_bytes: &mut u32,
630 629 root: &'tree mut ChildNodes<'on_disk>,
631 630 path: &'path HgPath,
632 631 to_cow: impl Fn(
633 632 WithBasename<&'path HgPath>,
634 633 ) -> WithBasename<Cow<'on_disk, HgPath>>,
635 634 mut each_ancestor: impl FnMut(&mut Node),
636 635 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
637 636 let mut child_nodes = root;
638 637 let mut inclusive_ancestor_paths =
639 638 WithBasename::inclusive_ancestors_of(path);
640 639 let mut ancestor_path = inclusive_ancestor_paths
641 640 .next()
642 641 .expect("expected at least one inclusive ancestor");
643 642 loop {
644 643 let (_, child_node) = child_nodes
645 644 .make_mut(on_disk, unreachable_bytes)?
646 645 .raw_entry_mut()
647 646 .from_key(ancestor_path.base_name())
648 647 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
649 648 if let Some(next) = inclusive_ancestor_paths.next() {
650 649 each_ancestor(child_node);
651 650 ancestor_path = next;
652 651 child_nodes = &mut child_node.children;
653 652 } else {
654 653 return Ok(child_node);
655 654 }
656 655 }
657 656 }
658 657
659 658 fn reset_state(
660 659 &mut self,
661 660 filename: &HgPath,
662 661 old_entry_opt: Option<DirstateEntry>,
663 662 wc_tracked: bool,
664 663 p1_tracked: bool,
665 664 p2_info: bool,
666 665 has_meaningful_mtime: bool,
667 666 parent_file_data_opt: Option<ParentFileData>,
668 667 ) -> Result<(), DirstateError> {
669 668 let (had_entry, was_tracked) = match old_entry_opt {
670 669 Some(old_entry) => (true, old_entry.tracked()),
671 670 None => (false, false),
672 671 };
673 672 let node = self.get_or_insert_node(filename, |ancestor| {
674 673 if !had_entry {
675 674 ancestor.descendants_with_entry_count += 1;
676 675 }
677 676 if was_tracked {
678 677 if !wc_tracked {
679 678 ancestor.tracked_descendants_count = ancestor
680 679 .tracked_descendants_count
681 680 .checked_sub(1)
682 681 .expect("tracked count to be >= 0");
683 682 }
684 683 } else {
685 684 if wc_tracked {
686 685 ancestor.tracked_descendants_count += 1;
687 686 }
688 687 }
689 688 })?;
690 689
691 690 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
692 691 DirstateV2Data {
693 692 wc_tracked,
694 693 p1_tracked,
695 694 p2_info,
696 695 mode_size: parent_file_data.mode_size,
697 696 mtime: if has_meaningful_mtime {
698 697 parent_file_data.mtime
699 698 } else {
700 699 None
701 700 },
702 701 ..Default::default()
703 702 }
704 703 } else {
705 704 DirstateV2Data {
706 705 wc_tracked,
707 706 p1_tracked,
708 707 p2_info,
709 708 ..Default::default()
710 709 }
711 710 };
712 711 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
713 712 if !had_entry {
714 713 self.nodes_with_entry_count += 1;
715 714 }
716 715 Ok(())
717 716 }
718 717
719 718 fn set_tracked(
720 719 &mut self,
721 720 filename: &HgPath,
722 721 old_entry_opt: Option<DirstateEntry>,
723 722 ) -> Result<bool, DirstateV2ParseError> {
724 723 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
725 724 let had_entry = old_entry_opt.is_some();
726 725 let tracked_count_increment = if was_tracked { 0 } else { 1 };
727 726 let mut new = false;
728 727
729 728 let node = self.get_or_insert_node(filename, |ancestor| {
730 729 if !had_entry {
731 730 ancestor.descendants_with_entry_count += 1;
732 731 }
733 732
734 733 ancestor.tracked_descendants_count += tracked_count_increment;
735 734 })?;
736 735 if let Some(old_entry) = old_entry_opt {
737 736 let mut e = old_entry.clone();
738 737 if e.tracked() {
739 738 // XXX
740 739 // This is probably overkill for more case, but we need this to
741 740 // fully replace the `normallookup` call with `set_tracked`
742 741 // one. Consider smoothing this in the future.
743 742 e.set_possibly_dirty();
744 743 } else {
745 744 new = true;
746 745 e.set_tracked();
747 746 }
748 747 node.data = NodeData::Entry(e)
749 748 } else {
750 749 node.data = NodeData::Entry(DirstateEntry::new_tracked());
751 750 self.nodes_with_entry_count += 1;
752 751 new = true;
753 752 };
754 753 Ok(new)
755 754 }
756 755
757 756 /// Set a node as untracked in the dirstate.
758 757 ///
759 758 /// It is the responsibility of the caller to remove the copy source and/or
760 759 /// the entry itself if appropriate.
761 760 ///
762 761 /// # Panics
763 762 ///
764 763 /// Panics if the node does not exist.
765 764 fn set_untracked(
766 765 &mut self,
767 766 filename: &HgPath,
768 767 old_entry: DirstateEntry,
769 768 ) -> Result<(), DirstateV2ParseError> {
770 769 let node = self
771 770 .get_node_mut(filename, |ancestor| {
772 771 ancestor.tracked_descendants_count = ancestor
773 772 .tracked_descendants_count
774 773 .checked_sub(1)
775 774 .expect("tracked_descendants_count should be >= 0");
776 775 })?
777 776 .expect("node should exist");
778 777 let mut new_entry = old_entry.clone();
779 778 new_entry.set_untracked();
780 779 node.data = NodeData::Entry(new_entry);
781 780 Ok(())
782 781 }
783 782
784 783 /// Set a node as clean in the dirstate.
785 784 ///
786 785 /// It is the responsibility of the caller to remove the copy source.
787 786 ///
788 787 /// # Panics
789 788 ///
790 789 /// Panics if the node does not exist.
791 790 fn set_clean(
792 791 &mut self,
793 792 filename: &HgPath,
794 793 old_entry: DirstateEntry,
795 794 mode: u32,
796 795 size: u32,
797 796 mtime: TruncatedTimestamp,
798 797 ) -> Result<(), DirstateError> {
799 798 let node = self
800 799 .get_node_mut(filename, |ancestor| {
801 800 if !old_entry.tracked() {
802 801 ancestor.tracked_descendants_count += 1;
803 802 }
804 803 })?
805 804 .expect("node should exist");
806 805 let mut new_entry = old_entry.clone();
807 806 new_entry.set_clean(mode, size, mtime);
808 807 node.data = NodeData::Entry(new_entry);
809 808 Ok(())
810 809 }
811 810
812 811 /// Set a node as possibly dirty in the dirstate.
813 812 ///
814 813 /// # Panics
815 814 ///
816 815 /// Panics if the node does not exist.
817 816 fn set_possibly_dirty(
818 817 &mut self,
819 818 filename: &HgPath,
820 819 ) -> Result<(), DirstateError> {
821 820 let node = self
822 821 .get_node_mut(filename, |_ancestor| {})?
823 822 .expect("node should exist");
824 823 let entry = node.data.as_entry_mut().expect("entry should exist");
825 824 entry.set_possibly_dirty();
826 825 node.data = NodeData::Entry(*entry);
827 826 Ok(())
828 827 }
829 828
830 829 /// Clears the cached mtime for the (potential) folder at `path`.
831 830 pub(super) fn clear_cached_mtime(
832 831 &mut self,
833 832 path: &HgPath,
834 833 ) -> Result<(), DirstateV2ParseError> {
835 834 let node = match self.get_node_mut(path, |_ancestor| {})? {
836 835 Some(node) => node,
837 836 None => return Ok(()),
838 837 };
839 838 if let NodeData::CachedDirectory { .. } = &node.data {
840 839 node.data = NodeData::None
841 840 }
842 841 Ok(())
843 842 }
844 843
845 844 /// Sets the cached mtime for the (potential) folder at `path`.
846 845 pub(super) fn set_cached_mtime(
847 846 &mut self,
848 847 path: &HgPath,
849 848 mtime: TruncatedTimestamp,
850 849 ) -> Result<(), DirstateV2ParseError> {
851 850 let node = match self.get_node_mut(path, |_ancestor| {})? {
852 851 Some(node) => node,
853 852 None => return Ok(()),
854 853 };
855 854 match &node.data {
856 855 NodeData::Entry(_) => {} // Don’t overwrite an entry
857 856 NodeData::CachedDirectory { .. } | NodeData::None => {
858 857 node.data = NodeData::CachedDirectory { mtime }
859 858 }
860 859 }
861 860 Ok(())
862 861 }
863 862
864 863 fn iter_nodes<'tree>(
865 864 &'tree self,
866 865 ) -> impl Iterator<
867 866 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
868 867 > + 'tree {
869 868 // Depth first tree traversal.
870 869 //
871 870 // If we could afford internal iteration and recursion,
872 871 // this would look like:
873 872 //
874 873 // ```
875 874 // fn traverse_children(
876 875 // children: &ChildNodes,
877 876 // each: &mut impl FnMut(&Node),
878 877 // ) {
879 878 // for child in children.values() {
880 879 // traverse_children(&child.children, each);
881 880 // each(child);
882 881 // }
883 882 // }
884 883 // ```
885 884 //
886 885 // However we want an external iterator and therefore can’t use the
887 886 // call stack. Use an explicit stack instead:
888 887 let mut stack = Vec::new();
889 888 let mut iter = self.root.as_ref().iter();
890 889 std::iter::from_fn(move || {
891 890 while let Some(child_node) = iter.next() {
892 891 let children = match child_node.children(self.on_disk) {
893 892 Ok(children) => children,
894 893 Err(error) => return Some(Err(error)),
895 894 };
896 895 // Pseudo-recursion
897 896 let new_iter = children.iter();
898 897 let old_iter = std::mem::replace(&mut iter, new_iter);
899 898 stack.push((child_node, old_iter));
900 899 }
901 900 // Found the end of a `children.iter()` iterator.
902 901 if let Some((child_node, next_iter)) = stack.pop() {
903 902 // "Return" from pseudo-recursion by restoring state from the
904 903 // explicit stack
905 904 iter = next_iter;
906 905
907 906 Some(Ok(child_node))
908 907 } else {
909 908 // Reached the bottom of the stack, we’re done
910 909 None
911 910 }
912 911 })
913 912 }
914 913
915 914 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
916 915 if let Cow::Borrowed(path) = path {
917 916 *unreachable_bytes += path.len() as u32
918 917 }
919 918 }
920 919 }
921 920
922 921 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
923 922 ///
924 923 /// The callback is only called for incoming `Ok` values. Errors are passed
925 924 /// through as-is. In order to let it use the `?` operator the callback is
926 925 /// expected to return a `Result` of `Option`, instead of an `Option` of
927 926 /// `Result`.
928 927 fn filter_map_results<'a, I, F, A, B, E>(
929 928 iter: I,
930 929 f: F,
931 930 ) -> impl Iterator<Item = Result<B, E>> + 'a
932 931 where
933 932 I: Iterator<Item = Result<A, E>> + 'a,
934 933 F: Fn(A) -> Result<Option<B>, E> + 'a,
935 934 {
936 935 iter.filter_map(move |result| match result {
937 936 Ok(node) => f(node).transpose(),
938 937 Err(e) => Some(Err(e)),
939 938 })
940 939 }
941 940
942 941 impl OwningDirstateMap {
943 942 pub fn clear(&mut self) {
944 943 self.with_dmap_mut(|map| {
945 944 map.root = Default::default();
946 945 map.nodes_with_entry_count = 0;
947 946 map.nodes_with_copy_source_count = 0;
948 947 });
949 948 }
950 949
951 950 pub fn set_tracked(
952 951 &mut self,
953 952 filename: &HgPath,
954 953 ) -> Result<bool, DirstateV2ParseError> {
955 954 let old_entry_opt = self.get(filename)?;
956 955 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
957 956 }
958 957
959 958 pub fn set_untracked(
960 959 &mut self,
961 960 filename: &HgPath,
962 961 ) -> Result<bool, DirstateError> {
963 962 let old_entry_opt = self.get(filename)?;
964 963 match old_entry_opt {
965 964 None => Ok(false),
966 965 Some(old_entry) => {
967 966 if !old_entry.tracked() {
968 967 // `DirstateMap::set_untracked` is not a noop if
969 968 // already not tracked as it will decrement the
970 969 // tracked counters while going down.
971 970 return Ok(true);
972 971 }
973 972 if old_entry.added() {
974 973 // Untracking an "added" entry will just result in a
975 974 // worthless entry (and other parts of the code will
976 975 // complain about it), just drop it entirely.
977 976 self.drop_entry_and_copy_source(filename)?;
978 977 return Ok(true);
979 978 }
980 979 if !old_entry.p2_info() {
981 980 self.copy_map_remove(filename)?;
982 981 }
983 982
984 983 self.with_dmap_mut(|map| {
985 984 map.set_untracked(filename, old_entry)?;
986 985 Ok(true)
987 986 })
988 987 }
989 988 }
990 989 }
991 990
992 991 pub fn set_clean(
993 992 &mut self,
994 993 filename: &HgPath,
995 994 mode: u32,
996 995 size: u32,
997 996 mtime: TruncatedTimestamp,
998 997 ) -> Result<(), DirstateError> {
999 998 let old_entry = match self.get(filename)? {
1000 999 None => {
1001 1000 return Err(
1002 1001 DirstateMapError::PathNotFound(filename.into()).into()
1003 1002 )
1004 1003 }
1005 1004 Some(e) => e,
1006 1005 };
1007 1006 self.copy_map_remove(filename)?;
1008 1007 self.with_dmap_mut(|map| {
1009 1008 map.set_clean(filename, old_entry, mode, size, mtime)
1010 1009 })
1011 1010 }
1012 1011
1013 1012 pub fn set_possibly_dirty(
1014 1013 &mut self,
1015 1014 filename: &HgPath,
1016 1015 ) -> Result<(), DirstateError> {
1017 1016 if self.get(filename)?.is_none() {
1018 1017 return Err(DirstateMapError::PathNotFound(filename.into()).into());
1019 1018 }
1020 1019 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
1021 1020 }
1022 1021
1023 1022 pub fn reset_state(
1024 1023 &mut self,
1025 1024 filename: &HgPath,
1026 1025 wc_tracked: bool,
1027 1026 p1_tracked: bool,
1028 1027 p2_info: bool,
1029 1028 has_meaningful_mtime: bool,
1030 1029 parent_file_data_opt: Option<ParentFileData>,
1031 1030 ) -> Result<(), DirstateError> {
1032 1031 if !(p1_tracked || p2_info || wc_tracked) {
1033 1032 self.drop_entry_and_copy_source(filename)?;
1034 1033 return Ok(());
1035 1034 }
1036 1035 self.copy_map_remove(filename)?;
1037 1036 let old_entry_opt = self.get(filename)?;
1038 1037 self.with_dmap_mut(|map| {
1039 1038 map.reset_state(
1040 1039 filename,
1041 1040 old_entry_opt,
1042 1041 wc_tracked,
1043 1042 p1_tracked,
1044 1043 p2_info,
1045 1044 has_meaningful_mtime,
1046 1045 parent_file_data_opt,
1047 1046 )
1048 1047 })
1049 1048 }
1050 1049
1051 1050 pub fn drop_entry_and_copy_source(
1052 1051 &mut self,
1053 1052 filename: &HgPath,
1054 1053 ) -> Result<(), DirstateError> {
1055 1054 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
1056 1055 struct Dropped {
1057 1056 was_tracked: bool,
1058 1057 had_entry: bool,
1059 1058 had_copy_source: bool,
1060 1059 }
1061 1060
1062 1061 /// If this returns `Ok(Some((dropped, removed)))`, then
1063 1062 ///
1064 1063 /// * `dropped` is about the leaf node that was at `filename`
1065 1064 /// * `removed` is whether this particular level of recursion just
1066 1065 /// removed a node in `nodes`.
1067 1066 fn recur<'on_disk>(
1068 1067 on_disk: &'on_disk [u8],
1069 1068 unreachable_bytes: &mut u32,
1070 1069 nodes: &mut ChildNodes<'on_disk>,
1071 1070 path: &HgPath,
1072 1071 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1073 1072 let (first_path_component, rest_of_path) =
1074 1073 path.split_first_component();
1075 1074 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1076 1075 let node = if let Some(node) = nodes.get_mut(first_path_component)
1077 1076 {
1078 1077 node
1079 1078 } else {
1080 1079 return Ok(None);
1081 1080 };
1082 1081 let dropped;
1083 1082 if let Some(rest) = rest_of_path {
1084 1083 if let Some((d, removed)) = recur(
1085 1084 on_disk,
1086 1085 unreachable_bytes,
1087 1086 &mut node.children,
1088 1087 rest,
1089 1088 )? {
1090 1089 dropped = d;
1091 1090 if dropped.had_entry {
1092 1091 node.descendants_with_entry_count = node
1093 1092 .descendants_with_entry_count
1094 1093 .checked_sub(1)
1095 1094 .expect(
1096 1095 "descendants_with_entry_count should be >= 0",
1097 1096 );
1098 1097 }
1099 1098 if dropped.was_tracked {
1100 1099 node.tracked_descendants_count = node
1101 1100 .tracked_descendants_count
1102 1101 .checked_sub(1)
1103 1102 .expect(
1104 1103 "tracked_descendants_count should be >= 0",
1105 1104 );
1106 1105 }
1107 1106
1108 1107 // Directory caches must be invalidated when removing a
1109 1108 // child node
1110 1109 if removed {
1111 1110 if let NodeData::CachedDirectory { .. } = &node.data {
1112 1111 node.data = NodeData::None
1113 1112 }
1114 1113 }
1115 1114 } else {
1116 1115 return Ok(None);
1117 1116 }
1118 1117 } else {
1119 1118 let entry = node.data.as_entry();
1120 1119 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1121 1120 let had_entry = entry.is_some();
1122 1121 if had_entry {
1123 1122 node.data = NodeData::None
1124 1123 }
1125 1124 let mut had_copy_source = false;
1126 1125 if let Some(source) = &node.copy_source {
1127 1126 DirstateMap::count_dropped_path(unreachable_bytes, source);
1128 1127 had_copy_source = true;
1129 1128 node.copy_source = None
1130 1129 }
1131 1130 dropped = Dropped {
1132 1131 was_tracked,
1133 1132 had_entry,
1134 1133 had_copy_source,
1135 1134 };
1136 1135 }
1137 1136 // After recursion, for both leaf (rest_of_path is None) nodes and
1138 1137 // parent nodes, remove a node if it just became empty.
1139 1138 let remove = !node.data.has_entry()
1140 1139 && node.copy_source.is_none()
1141 1140 && node.children.is_empty();
1142 1141 if remove {
1143 1142 let (key, _) =
1144 1143 nodes.remove_entry(first_path_component).unwrap();
1145 1144 DirstateMap::count_dropped_path(
1146 1145 unreachable_bytes,
1147 1146 key.full_path(),
1148 1147 )
1149 1148 }
1150 1149 Ok(Some((dropped, remove)))
1151 1150 }
1152 1151
1153 1152 self.with_dmap_mut(|map| {
1154 1153 if let Some((dropped, _removed)) = recur(
1155 1154 map.on_disk,
1156 1155 &mut map.unreachable_bytes,
1157 1156 &mut map.root,
1158 1157 filename,
1159 1158 )? {
1160 1159 if dropped.had_entry {
1161 1160 map.nodes_with_entry_count = map
1162 1161 .nodes_with_entry_count
1163 1162 .checked_sub(1)
1164 1163 .expect("nodes_with_entry_count should be >= 0");
1165 1164 }
1166 1165 if dropped.had_copy_source {
1167 1166 map.nodes_with_copy_source_count = map
1168 1167 .nodes_with_copy_source_count
1169 1168 .checked_sub(1)
1170 1169 .expect("nodes_with_copy_source_count should be >= 0");
1171 1170 }
1172 1171 } else {
1173 1172 debug_assert!(!was_tracked);
1174 1173 }
1175 1174 Ok(())
1176 1175 })
1177 1176 }
1178 1177
1179 1178 pub fn has_tracked_dir(
1180 1179 &mut self,
1181 1180 directory: &HgPath,
1182 1181 ) -> Result<bool, DirstateError> {
1183 1182 self.with_dmap_mut(|map| {
1184 1183 if let Some(node) = map.get_node(directory)? {
1185 1184 // A node without a `DirstateEntry` was created to hold child
1186 1185 // nodes, and is therefore a directory.
1187 1186 let is_dir = node.entry()?.is_none();
1188 1187 Ok(is_dir && node.tracked_descendants_count() > 0)
1189 1188 } else {
1190 1189 Ok(false)
1191 1190 }
1192 1191 })
1193 1192 }
1194 1193
1195 1194 pub fn has_dir(
1196 1195 &mut self,
1197 1196 directory: &HgPath,
1198 1197 ) -> Result<bool, DirstateError> {
1199 1198 self.with_dmap_mut(|map| {
1200 1199 if let Some(node) = map.get_node(directory)? {
1201 1200 // A node without a `DirstateEntry` was created to hold child
1202 1201 // nodes, and is therefore a directory.
1203 1202 let is_dir = node.entry()?.is_none();
1204 1203 Ok(is_dir && node.descendants_with_entry_count() > 0)
1205 1204 } else {
1206 1205 Ok(false)
1207 1206 }
1208 1207 })
1209 1208 }
1210 1209
1211 #[timed]
1210 #[logging_timer::time("trace")]
1212 1211 pub fn pack_v1(
1213 1212 &self,
1214 1213 parents: DirstateParents,
1215 1214 ) -> Result<Vec<u8>, DirstateError> {
1216 1215 let map = self.get_map();
1217 1216 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1218 1217 // reallocations
1219 1218 let mut size = parents.as_bytes().len();
1220 1219 for node in map.iter_nodes() {
1221 1220 let node = node?;
1222 1221 if node.entry()?.is_some() {
1223 1222 size += packed_entry_size(
1224 1223 node.full_path(map.on_disk)?,
1225 1224 node.copy_source(map.on_disk)?,
1226 1225 );
1227 1226 }
1228 1227 }
1229 1228
1230 1229 let mut packed = Vec::with_capacity(size);
1231 1230 packed.extend(parents.as_bytes());
1232 1231
1233 1232 for node in map.iter_nodes() {
1234 1233 let node = node?;
1235 1234 if let Some(entry) = node.entry()? {
1236 1235 pack_entry(
1237 1236 node.full_path(map.on_disk)?,
1238 1237 &entry,
1239 1238 node.copy_source(map.on_disk)?,
1240 1239 &mut packed,
1241 1240 );
1242 1241 }
1243 1242 }
1244 1243 Ok(packed)
1245 1244 }
1246 1245
1247 1246 /// Returns new data and metadata together with whether that data should be
1248 1247 /// appended to the existing data file whose content is at
1249 1248 /// `map.on_disk` (true), instead of written to a new data file
1250 1249 /// (false), and the previous size of data on disk.
1251 #[timed]
1250 #[logging_timer::time("trace")]
1252 1251 pub fn pack_v2(
1253 1252 &self,
1254 1253 can_append: bool,
1255 1254 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool, usize), DirstateError>
1256 1255 {
1257 1256 let map = self.get_map();
1258 1257 on_disk::write(map, can_append)
1259 1258 }
1260 1259
1261 1260 /// `callback` allows the caller to process and do something with the
1262 1261 /// results of the status. This is needed to do so efficiently (i.e.
1263 1262 /// without cloning the `DirstateStatus` object with its paths) because
1264 1263 /// we need to borrow from `Self`.
1265 1264 pub fn with_status<R>(
1266 1265 &mut self,
1267 1266 matcher: &(dyn Matcher + Sync),
1268 1267 root_dir: PathBuf,
1269 1268 ignore_files: Vec<PathBuf>,
1270 1269 options: StatusOptions,
1271 1270 callback: impl for<'r> FnOnce(
1272 1271 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1273 1272 ) -> R,
1274 1273 ) -> R {
1275 1274 self.with_dmap_mut(|map| {
1276 1275 callback(super::status::status(
1277 1276 map,
1278 1277 matcher,
1279 1278 root_dir,
1280 1279 ignore_files,
1281 1280 options,
1282 1281 ))
1283 1282 })
1284 1283 }
1285 1284
1286 1285 pub fn copy_map_len(&self) -> usize {
1287 1286 let map = self.get_map();
1288 1287 map.nodes_with_copy_source_count as usize
1289 1288 }
1290 1289
1291 1290 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1292 1291 let map = self.get_map();
1293 1292 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1294 1293 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1295 1294 Some((node.full_path(map.on_disk)?, source))
1296 1295 } else {
1297 1296 None
1298 1297 })
1299 1298 }))
1300 1299 }
1301 1300
1302 1301 pub fn copy_map_contains_key(
1303 1302 &self,
1304 1303 key: &HgPath,
1305 1304 ) -> Result<bool, DirstateV2ParseError> {
1306 1305 let map = self.get_map();
1307 1306 Ok(if let Some(node) = map.get_node(key)? {
1308 1307 node.has_copy_source()
1309 1308 } else {
1310 1309 false
1311 1310 })
1312 1311 }
1313 1312
1314 1313 pub fn copy_map_get(
1315 1314 &self,
1316 1315 key: &HgPath,
1317 1316 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1318 1317 let map = self.get_map();
1319 1318 if let Some(node) = map.get_node(key)? {
1320 1319 if let Some(source) = node.copy_source(map.on_disk)? {
1321 1320 return Ok(Some(source));
1322 1321 }
1323 1322 }
1324 1323 Ok(None)
1325 1324 }
1326 1325
1327 1326 pub fn copy_map_remove(
1328 1327 &mut self,
1329 1328 key: &HgPath,
1330 1329 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1331 1330 self.with_dmap_mut(|map| {
1332 1331 let count = &mut map.nodes_with_copy_source_count;
1333 1332 let unreachable_bytes = &mut map.unreachable_bytes;
1334 1333 Ok(DirstateMap::get_node_mut_inner(
1335 1334 map.on_disk,
1336 1335 unreachable_bytes,
1337 1336 &mut map.root,
1338 1337 key,
1339 1338 |_ancestor| {},
1340 1339 )?
1341 1340 .and_then(|node| {
1342 1341 if let Some(source) = &node.copy_source {
1343 1342 *count = count
1344 1343 .checked_sub(1)
1345 1344 .expect("nodes_with_copy_source_count should be >= 0");
1346 1345 DirstateMap::count_dropped_path(unreachable_bytes, source);
1347 1346 }
1348 1347 node.copy_source.take().map(Cow::into_owned)
1349 1348 }))
1350 1349 })
1351 1350 }
1352 1351
1353 1352 pub fn copy_map_insert(
1354 1353 &mut self,
1355 1354 key: &HgPath,
1356 1355 value: &HgPath,
1357 1356 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1358 1357 self.with_dmap_mut(|map| {
1359 1358 let node = map.get_or_insert_node(&key, |_ancestor| {})?;
1360 1359 let had_copy_source = node.copy_source.is_none();
1361 1360 let old = node
1362 1361 .copy_source
1363 1362 .replace(value.to_owned().into())
1364 1363 .map(Cow::into_owned);
1365 1364 if had_copy_source {
1366 1365 map.nodes_with_copy_source_count += 1
1367 1366 }
1368 1367 Ok(old)
1369 1368 })
1370 1369 }
1371 1370
1372 1371 pub fn len(&self) -> usize {
1373 1372 let map = self.get_map();
1374 1373 map.nodes_with_entry_count as usize
1375 1374 }
1376 1375
1377 1376 pub fn contains_key(
1378 1377 &self,
1379 1378 key: &HgPath,
1380 1379 ) -> Result<bool, DirstateV2ParseError> {
1381 1380 Ok(self.get(key)?.is_some())
1382 1381 }
1383 1382
1384 1383 pub fn get(
1385 1384 &self,
1386 1385 key: &HgPath,
1387 1386 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1388 1387 let map = self.get_map();
1389 1388 Ok(if let Some(node) = map.get_node(key)? {
1390 1389 node.entry()?
1391 1390 } else {
1392 1391 None
1393 1392 })
1394 1393 }
1395 1394
1396 1395 pub fn iter(&self) -> StateMapIter<'_> {
1397 1396 let map = self.get_map();
1398 1397 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1399 1398 Ok(if let Some(entry) = node.entry()? {
1400 1399 Some((node.full_path(map.on_disk)?, entry))
1401 1400 } else {
1402 1401 None
1403 1402 })
1404 1403 }))
1405 1404 }
1406 1405
1407 1406 pub fn iter_tracked_dirs(
1408 1407 &mut self,
1409 1408 ) -> Result<
1410 1409 Box<
1411 1410 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1412 1411 + Send
1413 1412 + '_,
1414 1413 >,
1415 1414 DirstateError,
1416 1415 > {
1417 1416 let map = self.get_map();
1418 1417 let on_disk = map.on_disk;
1419 1418 Ok(Box::new(filter_map_results(
1420 1419 map.iter_nodes(),
1421 1420 move |node| {
1422 1421 Ok(if node.tracked_descendants_count() > 0 {
1423 1422 Some(node.full_path(on_disk)?)
1424 1423 } else {
1425 1424 None
1426 1425 })
1427 1426 },
1428 1427 )))
1429 1428 }
1430 1429
1431 1430 /// Only public because it needs to be exposed to the Python layer.
1432 1431 /// It is not the full `setparents` logic, only the parts that mutate the
1433 1432 /// entries.
1434 1433 pub fn setparents_fixup(
1435 1434 &mut self,
1436 1435 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1437 1436 // XXX
1438 1437 // All the copying and re-querying is quite inefficient, but this is
1439 1438 // still a lot better than doing it from Python.
1440 1439 //
1441 1440 // The better solution is to develop a mechanism for `iter_mut`,
1442 1441 // which will be a lot more involved: we're dealing with a lazy,
1443 1442 // append-mostly, tree-like data structure. This will do for now.
1444 1443 let mut copies = vec![];
1445 1444 let mut files_with_p2_info = vec![];
1446 1445 for res in self.iter() {
1447 1446 let (path, entry) = res?;
1448 1447 if entry.p2_info() {
1449 1448 files_with_p2_info.push(path.to_owned())
1450 1449 }
1451 1450 }
1452 1451 self.with_dmap_mut(|map| {
1453 1452 for path in files_with_p2_info.iter() {
1454 1453 let node = map.get_or_insert_node(path, |_| {})?;
1455 1454 let entry =
1456 1455 node.data.as_entry_mut().expect("entry should exist");
1457 1456 entry.drop_merge_data();
1458 1457 if let Some(source) = node.copy_source.take().as_deref() {
1459 1458 copies.push((path.to_owned(), source.to_owned()));
1460 1459 }
1461 1460 }
1462 1461 Ok(copies)
1463 1462 })
1464 1463 }
1465 1464
1466 1465 pub fn debug_iter(
1467 1466 &self,
1468 1467 all: bool,
1469 1468 ) -> Box<
1470 1469 dyn Iterator<
1471 1470 Item = Result<
1472 1471 (&HgPath, (u8, i32, i32, i32)),
1473 1472 DirstateV2ParseError,
1474 1473 >,
1475 1474 > + Send
1476 1475 + '_,
1477 1476 > {
1478 1477 let map = self.get_map();
1479 1478 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1480 1479 let debug_tuple = if let Some(entry) = node.entry()? {
1481 1480 entry.debug_tuple()
1482 1481 } else if !all {
1483 1482 return Ok(None);
1484 1483 } else if let Some(mtime) = node.cached_directory_mtime()? {
1485 1484 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1486 1485 } else {
1487 1486 (b' ', 0, -1, -1)
1488 1487 };
1489 1488 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1490 1489 }))
1491 1490 }
1492 1491 }
1493 1492 #[cfg(test)]
1494 1493 mod tests {
1495 1494 use super::*;
1496 1495
1497 1496 /// Shortcut to return tracked descendants of a path.
1498 1497 /// Panics if the path does not exist.
1499 1498 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1500 1499 let path = dbg!(HgPath::new(path));
1501 1500 let node = map.get_map().get_node(path);
1502 1501 node.unwrap().unwrap().tracked_descendants_count()
1503 1502 }
1504 1503
1505 1504 /// Shortcut to return descendants with an entry.
1506 1505 /// Panics if the path does not exist.
1507 1506 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1508 1507 let path = dbg!(HgPath::new(path));
1509 1508 let node = map.get_map().get_node(path);
1510 1509 node.unwrap().unwrap().descendants_with_entry_count()
1511 1510 }
1512 1511
1513 1512 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1514 1513 let path = dbg!(HgPath::new(path));
1515 1514 let node = map.get_map().get_node(path);
1516 1515 assert!(node.unwrap().is_none());
1517 1516 }
1518 1517
1519 1518 /// Shortcut for path creation in tests
1520 1519 fn p(b: &[u8]) -> &HgPath {
1521 1520 HgPath::new(b)
1522 1521 }
1523 1522
1524 1523 /// Test the very simple case a single tracked file
1525 1524 #[test]
1526 1525 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1527 1526 let mut map = OwningDirstateMap::new_empty(vec![]);
1528 1527 assert_eq!(map.len(), 0);
1529 1528
1530 1529 map.set_tracked(p(b"some/nested/path"))?;
1531 1530
1532 1531 assert_eq!(map.len(), 1);
1533 1532 assert_eq!(tracked_descendants(&map, b"some"), 1);
1534 1533 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1535 1534 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1536 1535
1537 1536 map.set_untracked(p(b"some/nested/path"))?;
1538 1537 assert_eq!(map.len(), 0);
1539 1538 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1540 1539
1541 1540 Ok(())
1542 1541 }
1543 1542
1544 1543 /// Test the simple case of all tracked, but multiple files
1545 1544 #[test]
1546 1545 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1547 1546 let mut map = OwningDirstateMap::new_empty(vec![]);
1548 1547
1549 1548 map.set_tracked(p(b"some/nested/path"))?;
1550 1549 map.set_tracked(p(b"some/nested/file"))?;
1551 1550 // one layer without any files to test deletion cascade
1552 1551 map.set_tracked(p(b"some/other/nested/path"))?;
1553 1552 map.set_tracked(p(b"root_file"))?;
1554 1553 map.set_tracked(p(b"some/file"))?;
1555 1554 map.set_tracked(p(b"some/file2"))?;
1556 1555 map.set_tracked(p(b"some/file3"))?;
1557 1556
1558 1557 assert_eq!(map.len(), 7);
1559 1558 assert_eq!(tracked_descendants(&map, b"some"), 6);
1560 1559 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1561 1560 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1562 1561 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1563 1562 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1564 1563
1565 1564 map.set_untracked(p(b"some/nested/path"))?;
1566 1565 assert_eq!(map.len(), 6);
1567 1566 assert_eq!(tracked_descendants(&map, b"some"), 5);
1568 1567 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1569 1568 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1570 1569 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1571 1570
1572 1571 map.set_untracked(p(b"some/nested/file"))?;
1573 1572 assert_eq!(map.len(), 5);
1574 1573 assert_eq!(tracked_descendants(&map, b"some"), 4);
1575 1574 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1576 1575 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1577 1576 assert_does_not_exist(&map, b"some_nested");
1578 1577
1579 1578 map.set_untracked(p(b"some/other/nested/path"))?;
1580 1579 assert_eq!(map.len(), 4);
1581 1580 assert_eq!(tracked_descendants(&map, b"some"), 3);
1582 1581 assert_does_not_exist(&map, b"some/other");
1583 1582
1584 1583 map.set_untracked(p(b"root_file"))?;
1585 1584 assert_eq!(map.len(), 3);
1586 1585 assert_eq!(tracked_descendants(&map, b"some"), 3);
1587 1586 assert_does_not_exist(&map, b"root_file");
1588 1587
1589 1588 map.set_untracked(p(b"some/file"))?;
1590 1589 assert_eq!(map.len(), 2);
1591 1590 assert_eq!(tracked_descendants(&map, b"some"), 2);
1592 1591 assert_does_not_exist(&map, b"some/file");
1593 1592
1594 1593 map.set_untracked(p(b"some/file2"))?;
1595 1594 assert_eq!(map.len(), 1);
1596 1595 assert_eq!(tracked_descendants(&map, b"some"), 1);
1597 1596 assert_does_not_exist(&map, b"some/file2");
1598 1597
1599 1598 map.set_untracked(p(b"some/file3"))?;
1600 1599 assert_eq!(map.len(), 0);
1601 1600 assert_does_not_exist(&map, b"some/file3");
1602 1601
1603 1602 Ok(())
1604 1603 }
1605 1604
1606 1605 /// Check with a mix of tracked and non-tracked items
1607 1606 #[test]
1608 1607 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1609 1608 let mut map = OwningDirstateMap::new_empty(vec![]);
1610 1609
1611 1610 // A file that was just added
1612 1611 map.set_tracked(p(b"some/nested/path"))?;
1613 1612 // This has no information, the dirstate should ignore it
1614 1613 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1615 1614 assert_does_not_exist(&map, b"some/file");
1616 1615
1617 1616 // A file that was removed
1618 1617 map.reset_state(
1619 1618 p(b"some/nested/file"),
1620 1619 false,
1621 1620 true,
1622 1621 false,
1623 1622 false,
1624 1623 None,
1625 1624 )?;
1626 1625 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1627 1626 // Only present in p2
1628 1627 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1629 1628 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1630 1629 // A file that was merged
1631 1630 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1632 1631 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1633 1632 // A file that is added, with info from p2
1634 1633 // XXX is that actually possible?
1635 1634 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1636 1635 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1637 1636 // A clean file
1638 1637 // One layer without any files to test deletion cascade
1639 1638 map.reset_state(
1640 1639 p(b"some/other/nested/path"),
1641 1640 true,
1642 1641 true,
1643 1642 false,
1644 1643 false,
1645 1644 None,
1646 1645 )?;
1647 1646 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1648 1647
1649 1648 assert_eq!(map.len(), 6);
1650 1649 assert_eq!(tracked_descendants(&map, b"some"), 3);
1651 1650 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1652 1651 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1653 1652 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1654 1653 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1655 1654 assert_eq!(
1656 1655 descendants_with_an_entry(&map, b"some/other/nested/path"),
1657 1656 0
1658 1657 );
1659 1658 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1660 1659 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1661 1660
1662 1661 // might as well check this
1663 1662 map.set_untracked(p(b"path/does/not/exist"))?;
1664 1663 assert_eq!(map.len(), 6);
1665 1664
1666 1665 map.set_untracked(p(b"some/other/nested/path"))?;
1667 1666 // It is set untracked but not deleted since it held other information
1668 1667 assert_eq!(map.len(), 6);
1669 1668 assert_eq!(tracked_descendants(&map, b"some"), 2);
1670 1669 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1671 1670 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1672 1671 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1673 1672 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1674 1673 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1675 1674
1676 1675 map.set_untracked(p(b"some/nested/path"))?;
1677 1676 // It is set untracked *and* deleted since it was only added
1678 1677 assert_eq!(map.len(), 5);
1679 1678 assert_eq!(tracked_descendants(&map, b"some"), 1);
1680 1679 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1681 1680 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1682 1681 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1683 1682 assert_does_not_exist(&map, b"some/nested/path");
1684 1683
1685 1684 map.set_untracked(p(b"root_file"))?;
1686 1685 // Untracked but not deleted
1687 1686 assert_eq!(map.len(), 5);
1688 1687 assert!(map.get(p(b"root_file"))?.is_some());
1689 1688
1690 1689 map.set_untracked(p(b"some/file2"))?;
1691 1690 assert_eq!(map.len(), 5);
1692 1691 assert_eq!(tracked_descendants(&map, b"some"), 0);
1693 1692 assert!(map.get(p(b"some/file2"))?.is_some());
1694 1693
1695 1694 map.set_untracked(p(b"some/file3"))?;
1696 1695 assert_eq!(map.len(), 5);
1697 1696 assert_eq!(tracked_descendants(&map, b"some"), 0);
1698 1697 assert!(map.get(p(b"some/file3"))?.is_some());
1699 1698
1700 1699 Ok(())
1701 1700 }
1702 1701
1703 1702 /// Check that copies counter is correctly updated
1704 1703 #[test]
1705 1704 fn test_copy_source() -> Result<(), DirstateError> {
1706 1705 let mut map = OwningDirstateMap::new_empty(vec![]);
1707 1706
1708 1707 // Clean file
1709 1708 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1710 1709 // Merged file
1711 1710 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1712 1711 // Removed file
1713 1712 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1714 1713 // Added file
1715 1714 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1716 1715 // Add copy
1717 1716 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1718 1717 assert_eq!(map.copy_map_len(), 1);
1719 1718
1720 1719 // Copy override
1721 1720 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1722 1721 assert_eq!(map.copy_map_len(), 1);
1723 1722
1724 1723 // Multiple copies
1725 1724 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1726 1725 assert_eq!(map.copy_map_len(), 2);
1727 1726
1728 1727 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1729 1728 assert_eq!(map.copy_map_len(), 3);
1730 1729
1731 1730 // Added, so the entry is completely removed
1732 1731 map.set_untracked(p(b"files/added"))?;
1733 1732 assert_does_not_exist(&map, b"files/added");
1734 1733 assert_eq!(map.copy_map_len(), 2);
1735 1734
1736 1735 // Removed, so the entry is kept around, so is its copy
1737 1736 map.set_untracked(p(b"removed"))?;
1738 1737 assert!(map.get(p(b"removed"))?.is_some());
1739 1738 assert_eq!(map.copy_map_len(), 2);
1740 1739
1741 1740 // Clean, so the entry is kept around, but not its copy
1742 1741 map.set_untracked(p(b"files/clean"))?;
1743 1742 assert!(map.get(p(b"files/clean"))?.is_some());
1744 1743 assert_eq!(map.copy_map_len(), 1);
1745 1744
1746 1745 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1747 1746 assert_eq!(map.copy_map_len(), 2);
1748 1747
1749 1748 // Info from p2, so its copy source info is kept around
1750 1749 map.set_untracked(p(b"files/from_p2"))?;
1751 1750 assert!(map.get(p(b"files/from_p2"))?.is_some());
1752 1751 assert_eq!(map.copy_map_len(), 2);
1753 1752
1754 1753 Ok(())
1755 1754 }
1756 1755
1757 1756 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1758 1757 /// does not actually come from the disk, but it's opaque to the code being
1759 1758 /// tested.
1760 1759 #[test]
1761 1760 fn test_on_disk() -> Result<(), DirstateError> {
1762 1761 // First let's create some data to put "on disk"
1763 1762 let mut map = OwningDirstateMap::new_empty(vec![]);
1764 1763
1765 1764 // A file that was just added
1766 1765 map.set_tracked(p(b"some/nested/added"))?;
1767 1766 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1768 1767
1769 1768 // A file that was removed
1770 1769 map.reset_state(
1771 1770 p(b"some/nested/removed"),
1772 1771 false,
1773 1772 true,
1774 1773 false,
1775 1774 false,
1776 1775 None,
1777 1776 )?;
1778 1777 // Only present in p2
1779 1778 map.reset_state(
1780 1779 p(b"other/p2_info_only"),
1781 1780 false,
1782 1781 false,
1783 1782 true,
1784 1783 false,
1785 1784 None,
1786 1785 )?;
1787 1786 map.copy_map_insert(
1788 1787 p(b"other/p2_info_only"),
1789 1788 p(b"other/p2_info_copy_source"),
1790 1789 )?;
1791 1790 // A file that was merged
1792 1791 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1793 1792 // A file that is added, with info from p2
1794 1793 // XXX is that actually possible?
1795 1794 map.reset_state(
1796 1795 p(b"other/added_with_p2"),
1797 1796 true,
1798 1797 false,
1799 1798 true,
1800 1799 false,
1801 1800 None,
1802 1801 )?;
1803 1802 // One layer without any files to test deletion cascade
1804 1803 // A clean file
1805 1804 map.reset_state(
1806 1805 p(b"some/other/nested/clean"),
1807 1806 true,
1808 1807 true,
1809 1808 false,
1810 1809 false,
1811 1810 None,
1812 1811 )?;
1813 1812
1814 1813 let (packed, metadata, _should_append, _old_data_size) =
1815 1814 map.pack_v2(false)?;
1816 1815 let packed_len = packed.len();
1817 1816 assert!(packed_len > 0);
1818 1817
1819 1818 // Recreate "from disk"
1820 1819 let mut map = OwningDirstateMap::new_v2(
1821 1820 packed,
1822 1821 packed_len,
1823 1822 metadata.as_bytes(),
1824 1823 )?;
1825 1824
1826 1825 // Check that everything is accounted for
1827 1826 assert!(map.contains_key(p(b"some/nested/added"))?);
1828 1827 assert!(map.contains_key(p(b"some/nested/removed"))?);
1829 1828 assert!(map.contains_key(p(b"merged"))?);
1830 1829 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1831 1830 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1832 1831 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1833 1832 assert_eq!(
1834 1833 map.copy_map_get(p(b"some/nested/added"))?,
1835 1834 Some(p(b"added_copy_source"))
1836 1835 );
1837 1836 assert_eq!(
1838 1837 map.copy_map_get(p(b"other/p2_info_only"))?,
1839 1838 Some(p(b"other/p2_info_copy_source"))
1840 1839 );
1841 1840 assert_eq!(tracked_descendants(&map, b"some"), 2);
1842 1841 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1843 1842 assert_eq!(tracked_descendants(&map, b"other"), 1);
1844 1843 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1845 1844 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1846 1845 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1847 1846 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1848 1847 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1849 1848 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1850 1849 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1851 1850 assert_eq!(map.len(), 6);
1852 1851 assert_eq!(map.get_map().unreachable_bytes, 0);
1853 1852 assert_eq!(map.copy_map_len(), 2);
1854 1853
1855 1854 // Shouldn't change anything since it's already not tracked
1856 1855 map.set_untracked(p(b"some/nested/removed"))?;
1857 1856 assert_eq!(map.get_map().unreachable_bytes, 0);
1858 1857
1859 1858 match map.get_map().root {
1860 1859 ChildNodes::InMemory(_) => {
1861 1860 panic!("root should not have been mutated")
1862 1861 }
1863 1862 _ => (),
1864 1863 }
1865 1864 // We haven't mutated enough (nothing, actually), we should still be in
1866 1865 // the append strategy
1867 1866 assert!(map.get_map().write_should_append());
1868 1867
1869 1868 // But this mutates the structure, so there should be unreachable_bytes
1870 1869 assert!(map.set_untracked(p(b"some/nested/added"))?);
1871 1870 let unreachable_bytes = map.get_map().unreachable_bytes;
1872 1871 assert!(unreachable_bytes > 0);
1873 1872
1874 1873 match map.get_map().root {
1875 1874 ChildNodes::OnDisk(_) => panic!("root should have been mutated"),
1876 1875 _ => (),
1877 1876 }
1878 1877
1879 1878 // This should not mutate the structure either, since `root` has
1880 1879 // already been mutated along with its direct children.
1881 1880 map.set_untracked(p(b"merged"))?;
1882 1881 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1883 1882
1884 1883 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1885 1884 NodeRef::InMemory(_, _) => {
1886 1885 panic!("'other/added_with_p2' should not have been mutated")
1887 1886 }
1888 1887 _ => (),
1889 1888 }
1890 1889 // But this should, since it's in a different path
1891 1890 // than `<root>some/nested/add`
1892 1891 map.set_untracked(p(b"other/added_with_p2"))?;
1893 1892 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1894 1893
1895 1894 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1896 1895 NodeRef::OnDisk(_) => {
1897 1896 panic!("'other/added_with_p2' should have been mutated")
1898 1897 }
1899 1898 _ => (),
1900 1899 }
1901 1900
1902 1901 // We have rewritten most of the tree, we should create a new file
1903 1902 assert!(!map.get_map().write_should_append());
1904 1903
1905 1904 Ok(())
1906 1905 }
1907 1906 }
@@ -1,1003 +1,1002 b''
1 1 use crate::dirstate::entry::TruncatedTimestamp;
2 2 use crate::dirstate::status::IgnoreFnType;
3 3 use crate::dirstate::status::StatusPath;
4 4 use crate::dirstate_tree::dirstate_map::BorrowedPath;
5 5 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
6 6 use crate::dirstate_tree::dirstate_map::DirstateMap;
7 7 use crate::dirstate_tree::dirstate_map::DirstateVersion;
8 8 use crate::dirstate_tree::dirstate_map::NodeRef;
9 9 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
10 10 use crate::matchers::get_ignore_function;
11 11 use crate::matchers::Matcher;
12 12 use crate::utils::files::get_bytes_from_os_string;
13 13 use crate::utils::files::get_bytes_from_path;
14 14 use crate::utils::files::get_path_from_bytes;
15 15 use crate::utils::hg_path::HgPath;
16 16 use crate::BadMatch;
17 17 use crate::DirstateStatus;
18 18 use crate::HgPathCow;
19 19 use crate::PatternFileWarning;
20 20 use crate::StatusError;
21 21 use crate::StatusOptions;
22 use micro_timer::timed;
23 22 use once_cell::sync::OnceCell;
24 23 use rayon::prelude::*;
25 24 use sha1::{Digest, Sha1};
26 25 use std::borrow::Cow;
27 26 use std::io;
28 27 use std::path::Path;
29 28 use std::path::PathBuf;
30 29 use std::sync::Mutex;
31 30 use std::time::SystemTime;
32 31
33 32 /// Returns the status of the working directory compared to its parent
34 33 /// changeset.
35 34 ///
36 35 /// This algorithm is based on traversing the filesystem tree (`fs` in function
37 36 /// and variable names) and dirstate tree at the same time. The core of this
38 37 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
39 38 /// and its use of `itertools::merge_join_by`. When reaching a path that only
40 39 /// exists in one of the two trees, depending on information requested by
41 40 /// `options` we may need to traverse the remaining subtree.
42 #[timed]
41 #[logging_timer::time("trace")]
43 42 pub fn status<'dirstate>(
44 43 dmap: &'dirstate mut DirstateMap,
45 44 matcher: &(dyn Matcher + Sync),
46 45 root_dir: PathBuf,
47 46 ignore_files: Vec<PathBuf>,
48 47 options: StatusOptions,
49 48 ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError>
50 49 {
51 50 // Force the global rayon threadpool to not exceed 16 concurrent threads.
52 51 // This is a stop-gap measure until we figure out why using more than 16
53 52 // threads makes `status` slower for each additional thread.
54 53 // We use `ok()` in case the global threadpool has already been
55 54 // instantiated in `rhg` or some other caller.
56 55 // TODO find the underlying cause and fix it, then remove this.
57 56 rayon::ThreadPoolBuilder::new()
58 57 .num_threads(16.min(rayon::current_num_threads()))
59 58 .build_global()
60 59 .ok();
61 60
62 61 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
63 62 if options.list_ignored || options.list_unknown {
64 63 let (ignore_fn, warnings, changed) = match dmap.dirstate_version {
65 64 DirstateVersion::V1 => {
66 65 let (ignore_fn, warnings) = get_ignore_function(
67 66 ignore_files,
68 67 &root_dir,
69 68 &mut |_source, _pattern_bytes| {},
70 69 )?;
71 70 (ignore_fn, warnings, None)
72 71 }
73 72 DirstateVersion::V2 => {
74 73 let mut hasher = Sha1::new();
75 74 let (ignore_fn, warnings) = get_ignore_function(
76 75 ignore_files,
77 76 &root_dir,
78 77 &mut |source, pattern_bytes| {
79 78 // If inside the repo, use the relative version to
80 79 // make it deterministic inside tests.
81 80 // The performance hit should be negligible.
82 81 let source = source
83 82 .strip_prefix(&root_dir)
84 83 .unwrap_or(source);
85 84 let source = get_bytes_from_path(source);
86 85
87 86 let mut subhasher = Sha1::new();
88 87 subhasher.update(pattern_bytes);
89 88 let patterns_hash = subhasher.finalize();
90 89
91 90 hasher.update(source);
92 91 hasher.update(b" ");
93 92 hasher.update(patterns_hash);
94 93 hasher.update(b"\n");
95 94 },
96 95 )?;
97 96 let new_hash = *hasher.finalize().as_ref();
98 97 let changed = new_hash != dmap.ignore_patterns_hash;
99 98 dmap.ignore_patterns_hash = new_hash;
100 99 (ignore_fn, warnings, Some(changed))
101 100 }
102 101 };
103 102 (ignore_fn, warnings, changed)
104 103 } else {
105 104 (Box::new(|&_| true), vec![], None)
106 105 };
107 106
108 107 let filesystem_time_at_status_start =
109 108 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
110 109
111 110 // If the repository is under the current directory, prefer using a
112 111 // relative path, so the kernel needs to traverse fewer directory in every
113 112 // call to `read_dir` or `symlink_metadata`.
114 113 // This is effective in the common case where the current directory is the
115 114 // repository root.
116 115
117 116 // TODO: Better yet would be to use libc functions like `openat` and
118 117 // `fstatat` to remove such repeated traversals entirely, but the standard
119 118 // library does not provide APIs based on those.
120 119 // Maybe with a crate like https://crates.io/crates/openat instead?
121 120 let root_dir = if let Some(relative) = std::env::current_dir()
122 121 .ok()
123 122 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
124 123 {
125 124 relative
126 125 } else {
127 126 &root_dir
128 127 };
129 128
130 129 let outcome = DirstateStatus {
131 130 filesystem_time_at_status_start,
132 131 ..Default::default()
133 132 };
134 133 let common = StatusCommon {
135 134 dmap,
136 135 options,
137 136 matcher,
138 137 ignore_fn,
139 138 outcome: Mutex::new(outcome),
140 139 ignore_patterns_have_changed: patterns_changed,
141 140 new_cacheable_directories: Default::default(),
142 141 outdated_cached_directories: Default::default(),
143 142 filesystem_time_at_status_start,
144 143 };
145 144 let is_at_repo_root = true;
146 145 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
147 146 let has_ignored_ancestor = HasIgnoredAncestor::create(None, hg_path);
148 147 let root_cached_mtime = None;
149 148 // If the path we have for the repository root is a symlink, do follow it.
150 149 // (As opposed to symlinks within the working directory which are not
151 150 // followed, using `std::fs::symlink_metadata`.)
152 151 common.traverse_fs_directory_and_dirstate(
153 152 &has_ignored_ancestor,
154 153 dmap.root.as_ref(),
155 154 hg_path,
156 155 &DirEntry {
157 156 hg_path: Cow::Borrowed(HgPath::new(b"")),
158 157 fs_path: Cow::Borrowed(&root_dir),
159 158 symlink_metadata: None,
160 159 file_type: FakeFileType::Directory,
161 160 },
162 161 root_cached_mtime,
163 162 is_at_repo_root,
164 163 )?;
165 164 let mut outcome = common.outcome.into_inner().unwrap();
166 165 let new_cacheable = common.new_cacheable_directories.into_inner().unwrap();
167 166 let outdated = common.outdated_cached_directories.into_inner().unwrap();
168 167
169 168 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
170 169 || !outdated.is_empty()
171 170 || (!new_cacheable.is_empty()
172 171 && dmap.dirstate_version == DirstateVersion::V2);
173 172
174 173 // Remove outdated mtimes before adding new mtimes, in case a given
175 174 // directory is both
176 175 for path in &outdated {
177 176 dmap.clear_cached_mtime(path)?;
178 177 }
179 178 for (path, mtime) in &new_cacheable {
180 179 dmap.set_cached_mtime(path, *mtime)?;
181 180 }
182 181
183 182 Ok((outcome, warnings))
184 183 }
185 184
186 185 /// Bag of random things needed by various parts of the algorithm. Reduces the
187 186 /// number of parameters passed to functions.
188 187 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
189 188 dmap: &'tree DirstateMap<'on_disk>,
190 189 options: StatusOptions,
191 190 matcher: &'a (dyn Matcher + Sync),
192 191 ignore_fn: IgnoreFnType<'a>,
193 192 outcome: Mutex<DirstateStatus<'on_disk>>,
194 193 /// New timestamps of directories to be used for caching their readdirs
195 194 new_cacheable_directories:
196 195 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
197 196 /// Used to invalidate the readdir cache of directories
198 197 outdated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
199 198
200 199 /// Whether ignore files like `.hgignore` have changed since the previous
201 200 /// time a `status()` call wrote their hash to the dirstate. `None` means
202 201 /// we don’t know as this run doesn’t list either ignored or uknown files
203 202 /// and therefore isn’t reading `.hgignore`.
204 203 ignore_patterns_have_changed: Option<bool>,
205 204
206 205 /// The current time at the start of the `status()` algorithm, as measured
207 206 /// and possibly truncated by the filesystem.
208 207 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
209 208 }
210 209
211 210 enum Outcome {
212 211 Modified,
213 212 Added,
214 213 Removed,
215 214 Deleted,
216 215 Clean,
217 216 Ignored,
218 217 Unknown,
219 218 Unsure,
220 219 }
221 220
222 221 /// Lazy computation of whether a given path has a hgignored
223 222 /// ancestor.
224 223 struct HasIgnoredAncestor<'a> {
225 224 /// `path` and `parent` constitute the inputs to the computation,
226 225 /// `cache` stores the outcome.
227 226 path: &'a HgPath,
228 227 parent: Option<&'a HasIgnoredAncestor<'a>>,
229 228 cache: OnceCell<bool>,
230 229 }
231 230
232 231 impl<'a> HasIgnoredAncestor<'a> {
233 232 fn create(
234 233 parent: Option<&'a HasIgnoredAncestor<'a>>,
235 234 path: &'a HgPath,
236 235 ) -> HasIgnoredAncestor<'a> {
237 236 Self {
238 237 path,
239 238 parent,
240 239 cache: OnceCell::new(),
241 240 }
242 241 }
243 242
244 243 fn force<'b>(&self, ignore_fn: &IgnoreFnType<'b>) -> bool {
245 244 match self.parent {
246 245 None => false,
247 246 Some(parent) => {
248 247 *(parent.cache.get_or_init(|| {
249 248 parent.force(ignore_fn) || ignore_fn(&self.path)
250 249 }))
251 250 }
252 251 }
253 252 }
254 253 }
255 254
256 255 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
257 256 fn push_outcome(
258 257 &self,
259 258 which: Outcome,
260 259 dirstate_node: &NodeRef<'tree, 'on_disk>,
261 260 ) -> Result<(), DirstateV2ParseError> {
262 261 let path = dirstate_node
263 262 .full_path_borrowed(self.dmap.on_disk)?
264 263 .detach_from_tree();
265 264 let copy_source = if self.options.list_copies {
266 265 dirstate_node
267 266 .copy_source_borrowed(self.dmap.on_disk)?
268 267 .map(|source| source.detach_from_tree())
269 268 } else {
270 269 None
271 270 };
272 271 self.push_outcome_common(which, path, copy_source);
273 272 Ok(())
274 273 }
275 274
276 275 fn push_outcome_without_copy_source(
277 276 &self,
278 277 which: Outcome,
279 278 path: &BorrowedPath<'_, 'on_disk>,
280 279 ) {
281 280 self.push_outcome_common(which, path.detach_from_tree(), None)
282 281 }
283 282
284 283 fn push_outcome_common(
285 284 &self,
286 285 which: Outcome,
287 286 path: HgPathCow<'on_disk>,
288 287 copy_source: Option<HgPathCow<'on_disk>>,
289 288 ) {
290 289 let mut outcome = self.outcome.lock().unwrap();
291 290 let vec = match which {
292 291 Outcome::Modified => &mut outcome.modified,
293 292 Outcome::Added => &mut outcome.added,
294 293 Outcome::Removed => &mut outcome.removed,
295 294 Outcome::Deleted => &mut outcome.deleted,
296 295 Outcome::Clean => &mut outcome.clean,
297 296 Outcome::Ignored => &mut outcome.ignored,
298 297 Outcome::Unknown => &mut outcome.unknown,
299 298 Outcome::Unsure => &mut outcome.unsure,
300 299 };
301 300 vec.push(StatusPath { path, copy_source });
302 301 }
303 302
304 303 fn read_dir(
305 304 &self,
306 305 hg_path: &HgPath,
307 306 fs_path: &Path,
308 307 is_at_repo_root: bool,
309 308 ) -> Result<Vec<DirEntry>, ()> {
310 309 DirEntry::read_dir(fs_path, is_at_repo_root)
311 310 .map_err(|error| self.io_error(error, hg_path))
312 311 }
313 312
314 313 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
315 314 let errno = error.raw_os_error().expect("expected real OS error");
316 315 self.outcome
317 316 .lock()
318 317 .unwrap()
319 318 .bad
320 319 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
321 320 }
322 321
323 322 fn check_for_outdated_directory_cache(
324 323 &self,
325 324 dirstate_node: &NodeRef<'tree, 'on_disk>,
326 325 ) -> Result<bool, DirstateV2ParseError> {
327 326 if self.ignore_patterns_have_changed == Some(true)
328 327 && dirstate_node.cached_directory_mtime()?.is_some()
329 328 {
330 329 self.outdated_cached_directories.lock().unwrap().push(
331 330 dirstate_node
332 331 .full_path_borrowed(self.dmap.on_disk)?
333 332 .detach_from_tree(),
334 333 );
335 334 return Ok(true);
336 335 }
337 336 Ok(false)
338 337 }
339 338
340 339 /// If this returns true, we can get accurate results by only using
341 340 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
342 341 /// need to call `read_dir`.
343 342 fn can_skip_fs_readdir(
344 343 &self,
345 344 directory_entry: &DirEntry,
346 345 cached_directory_mtime: Option<TruncatedTimestamp>,
347 346 ) -> bool {
348 347 if !self.options.list_unknown && !self.options.list_ignored {
349 348 // All states that we care about listing have corresponding
350 349 // dirstate entries.
351 350 // This happens for example with `hg status -mard`.
352 351 return true;
353 352 }
354 353 if !self.options.list_ignored
355 354 && self.ignore_patterns_have_changed == Some(false)
356 355 {
357 356 if let Some(cached_mtime) = cached_directory_mtime {
358 357 // The dirstate contains a cached mtime for this directory, set
359 358 // by a previous run of the `status` algorithm which found this
360 359 // directory eligible for `read_dir` caching.
361 360 if let Ok(meta) = directory_entry.symlink_metadata() {
362 361 if cached_mtime
363 362 .likely_equal_to_mtime_of(&meta)
364 363 .unwrap_or(false)
365 364 {
366 365 // The mtime of that directory has not changed
367 366 // since then, which means that the results of
368 367 // `read_dir` should also be unchanged.
369 368 return true;
370 369 }
371 370 }
372 371 }
373 372 }
374 373 false
375 374 }
376 375
377 376 /// Returns whether all child entries of the filesystem directory have a
378 377 /// corresponding dirstate node or are ignored.
379 378 fn traverse_fs_directory_and_dirstate<'ancestor>(
380 379 &self,
381 380 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
382 381 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
383 382 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
384 383 directory_entry: &DirEntry,
385 384 cached_directory_mtime: Option<TruncatedTimestamp>,
386 385 is_at_repo_root: bool,
387 386 ) -> Result<bool, DirstateV2ParseError> {
388 387 if self.can_skip_fs_readdir(directory_entry, cached_directory_mtime) {
389 388 dirstate_nodes
390 389 .par_iter()
391 390 .map(|dirstate_node| {
392 391 let fs_path = &directory_entry.fs_path;
393 392 let fs_path = fs_path.join(get_path_from_bytes(
394 393 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
395 394 ));
396 395 match std::fs::symlink_metadata(&fs_path) {
397 396 Ok(fs_metadata) => {
398 397 let file_type =
399 398 match fs_metadata.file_type().try_into() {
400 399 Ok(file_type) => file_type,
401 400 Err(_) => return Ok(()),
402 401 };
403 402 let entry = DirEntry {
404 403 hg_path: Cow::Borrowed(
405 404 dirstate_node
406 405 .full_path(&self.dmap.on_disk)?,
407 406 ),
408 407 fs_path: Cow::Borrowed(&fs_path),
409 408 symlink_metadata: Some(fs_metadata),
410 409 file_type,
411 410 };
412 411 self.traverse_fs_and_dirstate(
413 412 &entry,
414 413 dirstate_node,
415 414 has_ignored_ancestor,
416 415 )
417 416 }
418 417 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
419 418 self.traverse_dirstate_only(dirstate_node)
420 419 }
421 420 Err(error) => {
422 421 let hg_path =
423 422 dirstate_node.full_path(self.dmap.on_disk)?;
424 423 Ok(self.io_error(error, hg_path))
425 424 }
426 425 }
427 426 })
428 427 .collect::<Result<_, _>>()?;
429 428
430 429 // We don’t know, so conservatively say this isn’t the case
431 430 let children_all_have_dirstate_node_or_are_ignored = false;
432 431
433 432 return Ok(children_all_have_dirstate_node_or_are_ignored);
434 433 }
435 434
436 435 let mut fs_entries = if let Ok(entries) = self.read_dir(
437 436 directory_hg_path,
438 437 &directory_entry.fs_path,
439 438 is_at_repo_root,
440 439 ) {
441 440 entries
442 441 } else {
443 442 // Treat an unreadable directory (typically because of insufficient
444 443 // permissions) like an empty directory. `self.read_dir` has
445 444 // already called `self.io_error` so a warning will be emitted.
446 445 Vec::new()
447 446 };
448 447
449 448 // `merge_join_by` requires both its input iterators to be sorted:
450 449
451 450 let dirstate_nodes = dirstate_nodes.sorted();
452 451 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
453 452 // https://github.com/rust-lang/rust/issues/34162
454 453 fs_entries.sort_unstable_by(|e1, e2| e1.hg_path.cmp(&e2.hg_path));
455 454
456 455 // Propagate here any error that would happen inside the comparison
457 456 // callback below
458 457 for dirstate_node in &dirstate_nodes {
459 458 dirstate_node.base_name(self.dmap.on_disk)?;
460 459 }
461 460 itertools::merge_join_by(
462 461 dirstate_nodes,
463 462 &fs_entries,
464 463 |dirstate_node, fs_entry| {
465 464 // This `unwrap` never panics because we already propagated
466 465 // those errors above
467 466 dirstate_node
468 467 .base_name(self.dmap.on_disk)
469 468 .unwrap()
470 469 .cmp(&fs_entry.hg_path)
471 470 },
472 471 )
473 472 .par_bridge()
474 473 .map(|pair| {
475 474 use itertools::EitherOrBoth::*;
476 475 let has_dirstate_node_or_is_ignored;
477 476 match pair {
478 477 Both(dirstate_node, fs_entry) => {
479 478 self.traverse_fs_and_dirstate(
480 479 &fs_entry,
481 480 dirstate_node,
482 481 has_ignored_ancestor,
483 482 )?;
484 483 has_dirstate_node_or_is_ignored = true
485 484 }
486 485 Left(dirstate_node) => {
487 486 self.traverse_dirstate_only(dirstate_node)?;
488 487 has_dirstate_node_or_is_ignored = true;
489 488 }
490 489 Right(fs_entry) => {
491 490 has_dirstate_node_or_is_ignored = self.traverse_fs_only(
492 491 has_ignored_ancestor.force(&self.ignore_fn),
493 492 directory_hg_path,
494 493 fs_entry,
495 494 )
496 495 }
497 496 }
498 497 Ok(has_dirstate_node_or_is_ignored)
499 498 })
500 499 .try_reduce(|| true, |a, b| Ok(a && b))
501 500 }
502 501
503 502 fn traverse_fs_and_dirstate<'ancestor>(
504 503 &self,
505 504 fs_entry: &DirEntry,
506 505 dirstate_node: NodeRef<'tree, 'on_disk>,
507 506 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
508 507 ) -> Result<(), DirstateV2ParseError> {
509 508 let outdated_dircache =
510 509 self.check_for_outdated_directory_cache(&dirstate_node)?;
511 510 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
512 511 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
513 512 if !file_or_symlink {
514 513 // If we previously had a file here, it was removed (with
515 514 // `hg rm` or similar) or deleted before it could be
516 515 // replaced by a directory or something else.
517 516 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
518 517 }
519 518 if fs_entry.is_dir() {
520 519 if self.options.collect_traversed_dirs {
521 520 self.outcome
522 521 .lock()
523 522 .unwrap()
524 523 .traversed
525 524 .push(hg_path.detach_from_tree())
526 525 }
527 526 let is_ignored = HasIgnoredAncestor::create(
528 527 Some(&has_ignored_ancestor),
529 528 hg_path,
530 529 );
531 530 let is_at_repo_root = false;
532 531 let children_all_have_dirstate_node_or_are_ignored = self
533 532 .traverse_fs_directory_and_dirstate(
534 533 &is_ignored,
535 534 dirstate_node.children(self.dmap.on_disk)?,
536 535 hg_path,
537 536 fs_entry,
538 537 dirstate_node.cached_directory_mtime()?,
539 538 is_at_repo_root,
540 539 )?;
541 540 self.maybe_save_directory_mtime(
542 541 children_all_have_dirstate_node_or_are_ignored,
543 542 fs_entry,
544 543 dirstate_node,
545 544 outdated_dircache,
546 545 )?
547 546 } else {
548 547 if file_or_symlink && self.matcher.matches(&hg_path) {
549 548 if let Some(entry) = dirstate_node.entry()? {
550 549 if !entry.any_tracked() {
551 550 // Forward-compat if we start tracking unknown/ignored
552 551 // files for caching reasons
553 552 self.mark_unknown_or_ignored(
554 553 has_ignored_ancestor.force(&self.ignore_fn),
555 554 &hg_path,
556 555 );
557 556 }
558 557 if entry.added() {
559 558 self.push_outcome(Outcome::Added, &dirstate_node)?;
560 559 } else if entry.removed() {
561 560 self.push_outcome(Outcome::Removed, &dirstate_node)?;
562 561 } else if entry.modified() {
563 562 self.push_outcome(Outcome::Modified, &dirstate_node)?;
564 563 } else {
565 564 self.handle_normal_file(&dirstate_node, fs_entry)?;
566 565 }
567 566 } else {
568 567 // `node.entry.is_none()` indicates a "directory"
569 568 // node, but the filesystem has a file
570 569 self.mark_unknown_or_ignored(
571 570 has_ignored_ancestor.force(&self.ignore_fn),
572 571 hg_path,
573 572 );
574 573 }
575 574 }
576 575
577 576 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
578 577 {
579 578 self.traverse_dirstate_only(child_node)?
580 579 }
581 580 }
582 581 Ok(())
583 582 }
584 583
585 584 /// Save directory mtime if applicable.
586 585 ///
587 586 /// `outdated_directory_cache` is `true` if we've just invalidated the
588 587 /// cache for this directory in `check_for_outdated_directory_cache`,
589 588 /// which forces the update.
590 589 fn maybe_save_directory_mtime(
591 590 &self,
592 591 children_all_have_dirstate_node_or_are_ignored: bool,
593 592 directory_entry: &DirEntry,
594 593 dirstate_node: NodeRef<'tree, 'on_disk>,
595 594 outdated_directory_cache: bool,
596 595 ) -> Result<(), DirstateV2ParseError> {
597 596 if !children_all_have_dirstate_node_or_are_ignored {
598 597 return Ok(());
599 598 }
600 599 // All filesystem directory entries from `read_dir` have a
601 600 // corresponding node in the dirstate, so we can reconstitute the
602 601 // names of those entries without calling `read_dir` again.
603 602
604 603 // TODO: use let-else here and below when available:
605 604 // https://github.com/rust-lang/rust/issues/87335
606 605 let status_start = if let Some(status_start) =
607 606 &self.filesystem_time_at_status_start
608 607 {
609 608 status_start
610 609 } else {
611 610 return Ok(());
612 611 };
613 612
614 613 // Although the Rust standard library’s `SystemTime` type
615 614 // has nanosecond precision, the times reported for a
616 615 // directory’s (or file’s) modified time may have lower
617 616 // resolution based on the filesystem (for example ext3
618 617 // only stores integer seconds), kernel (see
619 618 // https://stackoverflow.com/a/14393315/1162888), etc.
620 619 let metadata = match directory_entry.symlink_metadata() {
621 620 Ok(meta) => meta,
622 621 Err(_) => return Ok(()),
623 622 };
624 623 let directory_mtime = if let Ok(option) =
625 624 TruncatedTimestamp::for_reliable_mtime_of(&metadata, status_start)
626 625 {
627 626 if let Some(directory_mtime) = option {
628 627 directory_mtime
629 628 } else {
630 629 // The directory was modified too recently,
631 630 // don’t cache its `read_dir` results.
632 631 //
633 632 // 1. A change to this directory (direct child was
634 633 // added or removed) cause its mtime to be set
635 634 // (possibly truncated) to `directory_mtime`
636 635 // 2. This `status` algorithm calls `read_dir`
637 636 // 3. An other change is made to the same directory is
638 637 // made so that calling `read_dir` agin would give
639 638 // different results, but soon enough after 1. that
640 639 // the mtime stays the same
641 640 //
642 641 // On a system where the time resolution poor, this
643 642 // scenario is not unlikely if all three steps are caused
644 643 // by the same script.
645 644 return Ok(());
646 645 }
647 646 } else {
648 647 // OS/libc does not support mtime?
649 648 return Ok(());
650 649 };
651 650 // We’ve observed (through `status_start`) that time has
652 651 // “progressed” since `directory_mtime`, so any further
653 652 // change to this directory is extremely likely to cause a
654 653 // different mtime.
655 654 //
656 655 // Having the same mtime again is not entirely impossible
657 656 // since the system clock is not monotonous. It could jump
658 657 // backward to some point before `directory_mtime`, then a
659 658 // directory change could potentially happen during exactly
660 659 // the wrong tick.
661 660 //
662 661 // We deem this scenario (unlike the previous one) to be
663 662 // unlikely enough in practice.
664 663
665 664 let is_up_to_date = if let Some(cached) =
666 665 dirstate_node.cached_directory_mtime()?
667 666 {
668 667 !outdated_directory_cache && cached.likely_equal(directory_mtime)
669 668 } else {
670 669 false
671 670 };
672 671 if !is_up_to_date {
673 672 let hg_path = dirstate_node
674 673 .full_path_borrowed(self.dmap.on_disk)?
675 674 .detach_from_tree();
676 675 self.new_cacheable_directories
677 676 .lock()
678 677 .unwrap()
679 678 .push((hg_path, directory_mtime))
680 679 }
681 680 Ok(())
682 681 }
683 682
684 683 /// A file that is clean in the dirstate was found in the filesystem
685 684 fn handle_normal_file(
686 685 &self,
687 686 dirstate_node: &NodeRef<'tree, 'on_disk>,
688 687 fs_entry: &DirEntry,
689 688 ) -> Result<(), DirstateV2ParseError> {
690 689 // Keep the low 31 bits
691 690 fn truncate_u64(value: u64) -> i32 {
692 691 (value & 0x7FFF_FFFF) as i32
693 692 }
694 693
695 694 let fs_metadata = match fs_entry.symlink_metadata() {
696 695 Ok(meta) => meta,
697 696 Err(_) => return Ok(()),
698 697 };
699 698
700 699 let entry = dirstate_node
701 700 .entry()?
702 701 .expect("handle_normal_file called with entry-less node");
703 702 let mode_changed =
704 703 || self.options.check_exec && entry.mode_changed(&fs_metadata);
705 704 let size = entry.size();
706 705 let size_changed = size != truncate_u64(fs_metadata.len());
707 706 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
708 707 // issue6456: Size returned may be longer due to encryption
709 708 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
710 709 self.push_outcome(Outcome::Unsure, dirstate_node)?
711 710 } else if dirstate_node.has_copy_source()
712 711 || entry.is_from_other_parent()
713 712 || (size >= 0 && (size_changed || mode_changed()))
714 713 {
715 714 self.push_outcome(Outcome::Modified, dirstate_node)?
716 715 } else {
717 716 let mtime_looks_clean;
718 717 if let Some(dirstate_mtime) = entry.truncated_mtime() {
719 718 let fs_mtime = TruncatedTimestamp::for_mtime_of(&fs_metadata)
720 719 .expect("OS/libc does not support mtime?");
721 720 // There might be a change in the future if for example the
722 721 // internal clock become off while process run, but this is a
723 722 // case where the issues the user would face
724 723 // would be a lot worse and there is nothing we
725 724 // can really do.
726 725 mtime_looks_clean = fs_mtime.likely_equal(dirstate_mtime)
727 726 } else {
728 727 // No mtime in the dirstate entry
729 728 mtime_looks_clean = false
730 729 };
731 730 if !mtime_looks_clean {
732 731 self.push_outcome(Outcome::Unsure, dirstate_node)?
733 732 } else if self.options.list_clean {
734 733 self.push_outcome(Outcome::Clean, dirstate_node)?
735 734 }
736 735 }
737 736 Ok(())
738 737 }
739 738
740 739 /// A node in the dirstate tree has no corresponding filesystem entry
741 740 fn traverse_dirstate_only(
742 741 &self,
743 742 dirstate_node: NodeRef<'tree, 'on_disk>,
744 743 ) -> Result<(), DirstateV2ParseError> {
745 744 self.check_for_outdated_directory_cache(&dirstate_node)?;
746 745 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
747 746 dirstate_node
748 747 .children(self.dmap.on_disk)?
749 748 .par_iter()
750 749 .map(|child_node| self.traverse_dirstate_only(child_node))
751 750 .collect()
752 751 }
753 752
754 753 /// A node in the dirstate tree has no corresponding *file* on the
755 754 /// filesystem
756 755 ///
757 756 /// Does nothing on a "directory" node
758 757 fn mark_removed_or_deleted_if_file(
759 758 &self,
760 759 dirstate_node: &NodeRef<'tree, 'on_disk>,
761 760 ) -> Result<(), DirstateV2ParseError> {
762 761 if let Some(entry) = dirstate_node.entry()? {
763 762 if !entry.any_tracked() {
764 763 // Future-compat for when we start storing ignored and unknown
765 764 // files for caching reasons
766 765 return Ok(());
767 766 }
768 767 let path = dirstate_node.full_path(self.dmap.on_disk)?;
769 768 if self.matcher.matches(path) {
770 769 if entry.removed() {
771 770 self.push_outcome(Outcome::Removed, dirstate_node)?
772 771 } else {
773 772 self.push_outcome(Outcome::Deleted, &dirstate_node)?
774 773 }
775 774 }
776 775 }
777 776 Ok(())
778 777 }
779 778
780 779 /// Something in the filesystem has no corresponding dirstate node
781 780 ///
782 781 /// Returns whether that path is ignored
783 782 fn traverse_fs_only(
784 783 &self,
785 784 has_ignored_ancestor: bool,
786 785 directory_hg_path: &HgPath,
787 786 fs_entry: &DirEntry,
788 787 ) -> bool {
789 788 let hg_path = directory_hg_path.join(&fs_entry.hg_path);
790 789 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
791 790 if fs_entry.is_dir() {
792 791 let is_ignored =
793 792 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
794 793 let traverse_children = if is_ignored {
795 794 // Descendants of an ignored directory are all ignored
796 795 self.options.list_ignored
797 796 } else {
798 797 // Descendants of an unknown directory may be either unknown or
799 798 // ignored
800 799 self.options.list_unknown || self.options.list_ignored
801 800 };
802 801 if traverse_children {
803 802 let is_at_repo_root = false;
804 803 if let Ok(children_fs_entries) =
805 804 self.read_dir(&hg_path, &fs_entry.fs_path, is_at_repo_root)
806 805 {
807 806 children_fs_entries.par_iter().for_each(|child_fs_entry| {
808 807 self.traverse_fs_only(
809 808 is_ignored,
810 809 &hg_path,
811 810 child_fs_entry,
812 811 );
813 812 })
814 813 }
815 814 if self.options.collect_traversed_dirs {
816 815 self.outcome.lock().unwrap().traversed.push(hg_path.into())
817 816 }
818 817 }
819 818 is_ignored
820 819 } else {
821 820 if file_or_symlink {
822 821 if self.matcher.matches(&hg_path) {
823 822 self.mark_unknown_or_ignored(
824 823 has_ignored_ancestor,
825 824 &BorrowedPath::InMemory(&hg_path),
826 825 )
827 826 } else {
828 827 // We haven’t computed whether this path is ignored. It
829 828 // might not be, and a future run of status might have a
830 829 // different matcher that matches it. So treat it as not
831 830 // ignored. That is, inhibit readdir caching of the parent
832 831 // directory.
833 832 false
834 833 }
835 834 } else {
836 835 // This is neither a directory, a plain file, or a symlink.
837 836 // Treat it like an ignored file.
838 837 true
839 838 }
840 839 }
841 840 }
842 841
843 842 /// Returns whether that path is ignored
844 843 fn mark_unknown_or_ignored(
845 844 &self,
846 845 has_ignored_ancestor: bool,
847 846 hg_path: &BorrowedPath<'_, 'on_disk>,
848 847 ) -> bool {
849 848 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
850 849 if is_ignored {
851 850 if self.options.list_ignored {
852 851 self.push_outcome_without_copy_source(
853 852 Outcome::Ignored,
854 853 hg_path,
855 854 )
856 855 }
857 856 } else {
858 857 if self.options.list_unknown {
859 858 self.push_outcome_without_copy_source(
860 859 Outcome::Unknown,
861 860 hg_path,
862 861 )
863 862 }
864 863 }
865 864 is_ignored
866 865 }
867 866 }
868 867
869 868 /// Since [`std::fs::FileType`] cannot be built directly, we emulate what we
870 869 /// care about.
871 870 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
872 871 enum FakeFileType {
873 872 File,
874 873 Directory,
875 874 Symlink,
876 875 }
877 876
878 877 impl TryFrom<std::fs::FileType> for FakeFileType {
879 878 type Error = ();
880 879
881 880 fn try_from(f: std::fs::FileType) -> Result<Self, Self::Error> {
882 881 if f.is_dir() {
883 882 Ok(Self::Directory)
884 883 } else if f.is_file() {
885 884 Ok(Self::File)
886 885 } else if f.is_symlink() {
887 886 Ok(Self::Symlink)
888 887 } else {
889 888 // Things like FIFO etc.
890 889 Err(())
891 890 }
892 891 }
893 892 }
894 893
895 894 struct DirEntry<'a> {
896 895 /// Path as stored in the dirstate, or just the filename for optimization.
897 896 hg_path: HgPathCow<'a>,
898 897 /// Filesystem path
899 898 fs_path: Cow<'a, Path>,
900 899 /// Lazily computed
901 900 symlink_metadata: Option<std::fs::Metadata>,
902 901 /// Already computed for ergonomics.
903 902 file_type: FakeFileType,
904 903 }
905 904
906 905 impl<'a> DirEntry<'a> {
907 906 /// Returns **unsorted** entries in the given directory, with name,
908 907 /// metadata and file type.
909 908 ///
910 909 /// If a `.hg` sub-directory is encountered:
911 910 ///
912 911 /// * At the repository root, ignore that sub-directory
913 912 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
914 913 /// list instead.
915 914 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
916 915 // `read_dir` returns a "not found" error for the empty path
917 916 let at_cwd = path == Path::new("");
918 917 let read_dir_path = if at_cwd { Path::new(".") } else { path };
919 918 let mut results = Vec::new();
920 919 for entry in read_dir_path.read_dir()? {
921 920 let entry = entry?;
922 921 let file_type = match entry.file_type() {
923 922 Ok(v) => v,
924 923 Err(e) => {
925 924 // race with file deletion?
926 925 if e.kind() == std::io::ErrorKind::NotFound {
927 926 continue;
928 927 } else {
929 928 return Err(e);
930 929 }
931 930 }
932 931 };
933 932 let file_name = entry.file_name();
934 933 // FIXME don't do this when cached
935 934 if file_name == ".hg" {
936 935 if is_at_repo_root {
937 936 // Skip the repo’s own .hg (might be a symlink)
938 937 continue;
939 938 } else if file_type.is_dir() {
940 939 // A .hg sub-directory at another location means a subrepo,
941 940 // skip it entirely.
942 941 return Ok(Vec::new());
943 942 }
944 943 }
945 944 let full_path = if at_cwd {
946 945 file_name.clone().into()
947 946 } else {
948 947 entry.path()
949 948 };
950 949 let filename =
951 950 Cow::Owned(get_bytes_from_os_string(file_name).into());
952 951 let file_type = match FakeFileType::try_from(file_type) {
953 952 Ok(file_type) => file_type,
954 953 Err(_) => continue,
955 954 };
956 955 results.push(DirEntry {
957 956 hg_path: filename,
958 957 fs_path: Cow::Owned(full_path.to_path_buf()),
959 958 symlink_metadata: None,
960 959 file_type,
961 960 })
962 961 }
963 962 Ok(results)
964 963 }
965 964
966 965 fn symlink_metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
967 966 match &self.symlink_metadata {
968 967 Some(meta) => Ok(meta.clone()),
969 968 None => std::fs::symlink_metadata(&self.fs_path),
970 969 }
971 970 }
972 971
973 972 fn is_dir(&self) -> bool {
974 973 self.file_type == FakeFileType::Directory
975 974 }
976 975
977 976 fn is_file(&self) -> bool {
978 977 self.file_type == FakeFileType::File
979 978 }
980 979
981 980 fn is_symlink(&self) -> bool {
982 981 self.file_type == FakeFileType::Symlink
983 982 }
984 983 }
985 984
986 985 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
987 986 /// of the give repository.
988 987 ///
989 988 /// This is similar to `SystemTime::now()`, with the result truncated to the
990 989 /// same time resolution as other files’ modification times. Using `.hg`
991 990 /// instead of the system’s default temporary directory (such as `/tmp`) makes
992 991 /// it more likely the temporary file is in the same disk partition as contents
993 992 /// of the working directory, which can matter since different filesystems may
994 993 /// store timestamps with different resolutions.
995 994 ///
996 995 /// This may fail, typically if we lack write permissions. In that case we
997 996 /// should continue the `status()` algoritm anyway and consider the current
998 997 /// date/time to be unknown.
999 998 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
1000 999 tempfile::tempfile_in(repo_root.join(".hg"))?
1001 1000 .metadata()?
1002 1001 .modified()
1003 1002 }
@@ -1,140 +1,136 b''
1 1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
2 2 // and Mercurial contributors
3 3 //
4 4 // This software may be used and distributed according to the terms of the
5 5 // GNU General Public License version 2 or any later version.
6 6
7 7 mod ancestors;
8 8 pub mod dagops;
9 9 pub mod errors;
10 10 pub mod narrow;
11 11 pub mod sparse;
12 12 pub use ancestors::{AncestorsIterator, MissingAncestors};
13 13 pub mod dirstate;
14 14 pub mod dirstate_tree;
15 15 pub mod discovery;
16 16 pub mod exit_codes;
17 17 pub mod requirements;
18 18 pub mod testing; // unconditionally built, for use from integration tests
19 19 pub use dirstate::{
20 20 dirs_multiset::{DirsMultiset, DirsMultisetIter},
21 21 status::{
22 22 BadMatch, BadType, DirstateStatus, HgPathCow, StatusError,
23 23 StatusOptions,
24 24 },
25 25 DirstateEntry, DirstateParents, EntryState,
26 26 };
27 27 pub mod copy_tracing;
28 28 mod filepatterns;
29 29 pub mod matchers;
30 30 pub mod repo;
31 31 pub mod revlog;
32 32 pub use revlog::*;
33 33 pub mod checkexec;
34 34 pub mod config;
35 35 pub mod lock;
36 36 pub mod logging;
37 37 pub mod operations;
38 38 pub mod revset;
39 39 pub mod utils;
40 40 pub mod vfs;
41 41
42 42 use crate::utils::hg_path::{HgPathBuf, HgPathError};
43 43 pub use filepatterns::{
44 44 parse_pattern_syntax, read_pattern_file, IgnorePattern,
45 45 PatternFileWarning, PatternSyntax,
46 46 };
47 47 use std::collections::HashMap;
48 48 use std::fmt;
49 49 use twox_hash::RandomXxHashBuilder64;
50 50
51 /// This is a contract between the `micro-timer` crate and us, to expose
52 /// the `log` crate as `crate::log`.
53 use log;
54
55 51 pub type LineNumber = usize;
56 52
57 53 /// Rust's default hasher is too slow because it tries to prevent collision
58 54 /// attacks. We are not concerned about those: if an ill-minded person has
59 55 /// write access to your repository, you have other issues.
60 56 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
61 57
62 58 // TODO: should this be the default `FastHashMap` for all of hg-core, not just
63 59 // dirstate_tree? How does XxHash compare with AHash, hashbrown’s default?
64 60 pub type FastHashbrownMap<K, V> =
65 61 hashbrown::HashMap<K, V, RandomXxHashBuilder64>;
66 62
67 63 #[derive(Debug, PartialEq)]
68 64 pub enum DirstateMapError {
69 65 PathNotFound(HgPathBuf),
70 66 EmptyPath,
71 67 InvalidPath(HgPathError),
72 68 }
73 69
74 70 impl fmt::Display for DirstateMapError {
75 71 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76 72 match self {
77 73 DirstateMapError::PathNotFound(_) => {
78 74 f.write_str("expected a value, found none")
79 75 }
80 76 DirstateMapError::EmptyPath => {
81 77 f.write_str("Overflow in dirstate.")
82 78 }
83 79 DirstateMapError::InvalidPath(path_error) => path_error.fmt(f),
84 80 }
85 81 }
86 82 }
87 83
88 84 #[derive(Debug, derive_more::From)]
89 85 pub enum DirstateError {
90 86 Map(DirstateMapError),
91 87 Common(errors::HgError),
92 88 }
93 89
94 90 impl fmt::Display for DirstateError {
95 91 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
96 92 match self {
97 93 DirstateError::Map(error) => error.fmt(f),
98 94 DirstateError::Common(error) => error.fmt(f),
99 95 }
100 96 }
101 97 }
102 98
103 99 #[derive(Debug, derive_more::From)]
104 100 pub enum PatternError {
105 101 #[from]
106 102 Path(HgPathError),
107 103 UnsupportedSyntax(String),
108 104 UnsupportedSyntaxInFile(String, String, usize),
109 105 TooLong(usize),
110 106 #[from]
111 107 IO(std::io::Error),
112 108 /// Needed a pattern that can be turned into a regex but got one that
113 109 /// can't. This should only happen through programmer error.
114 110 NonRegexPattern(IgnorePattern),
115 111 }
116 112
117 113 impl fmt::Display for PatternError {
118 114 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119 115 match self {
120 116 PatternError::UnsupportedSyntax(syntax) => {
121 117 write!(f, "Unsupported syntax {}", syntax)
122 118 }
123 119 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
124 120 write!(
125 121 f,
126 122 "{}:{}: unsupported syntax {}",
127 123 file_path, line, syntax
128 124 )
129 125 }
130 126 PatternError::TooLong(size) => {
131 127 write!(f, "matcher pattern is too long ({} bytes)", size)
132 128 }
133 129 PatternError::IO(error) => error.fmt(f),
134 130 PatternError::Path(error) => error.fmt(f),
135 131 PatternError::NonRegexPattern(pattern) => {
136 132 write!(f, "'{:?}' cannot be turned into a regex", pattern)
137 133 }
138 134 }
139 135 }
140 136 }
@@ -1,1721 +1,1719 b''
1 1 // matchers.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Structs and types for matching files and directories.
9 9
10 10 use crate::{
11 11 dirstate::dirs_multiset::DirsChildrenMultiset,
12 12 filepatterns::{
13 13 build_single_regex, filter_subincludes, get_patterns_from_file,
14 14 PatternFileWarning, PatternResult,
15 15 },
16 16 utils::{
17 17 files::find_dirs,
18 18 hg_path::{HgPath, HgPathBuf},
19 19 Escaped,
20 20 },
21 21 DirsMultiset, DirstateMapError, FastHashMap, IgnorePattern, PatternError,
22 22 PatternSyntax,
23 23 };
24 24
25 25 use crate::dirstate::status::IgnoreFnType;
26 26 use crate::filepatterns::normalize_path_bytes;
27 27 use std::borrow::ToOwned;
28 28 use std::collections::HashSet;
29 29 use std::fmt::{Display, Error, Formatter};
30 30 use std::ops::Deref;
31 31 use std::path::{Path, PathBuf};
32 32
33 use micro_timer::timed;
34
35 33 #[derive(Debug, PartialEq)]
36 34 pub enum VisitChildrenSet {
37 35 /// Don't visit anything
38 36 Empty,
39 37 /// Only visit this directory
40 38 This,
41 39 /// Visit this directory and these subdirectories
42 40 /// TODO Should we implement a `NonEmptyHashSet`?
43 41 Set(HashSet<HgPathBuf>),
44 42 /// Visit this directory and all subdirectories
45 43 Recursive,
46 44 }
47 45
48 46 pub trait Matcher: core::fmt::Debug {
49 47 /// Explicitly listed files
50 48 fn file_set(&self) -> Option<&HashSet<HgPathBuf>>;
51 49 /// Returns whether `filename` is in `file_set`
52 50 fn exact_match(&self, filename: &HgPath) -> bool;
53 51 /// Returns whether `filename` is matched by this matcher
54 52 fn matches(&self, filename: &HgPath) -> bool;
55 53 /// Decides whether a directory should be visited based on whether it
56 54 /// has potential matches in it or one of its subdirectories, and
57 55 /// potentially lists which subdirectories of that directory should be
58 56 /// visited. This is based on the match's primary, included, and excluded
59 57 /// patterns.
60 58 ///
61 59 /// # Example
62 60 ///
63 61 /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would
64 62 /// return the following values (assuming the implementation of
65 63 /// visit_children_set is capable of recognizing this; some implementations
66 64 /// are not).
67 65 ///
68 66 /// ```text
69 67 /// ```ignore
70 68 /// '' -> {'foo', 'qux'}
71 69 /// 'baz' -> set()
72 70 /// 'foo' -> {'bar'}
73 71 /// // Ideally this would be `Recursive`, but since the prefix nature of
74 72 /// // matchers is applied to the entire matcher, we have to downgrade this
75 73 /// // to `This` due to the (yet to be implemented in Rust) non-prefix
76 74 /// // `RootFilesIn'-kind matcher being mixed in.
77 75 /// 'foo/bar' -> 'this'
78 76 /// 'qux' -> 'this'
79 77 /// ```
80 78 /// # Important
81 79 ///
82 80 /// Most matchers do not know if they're representing files or
83 81 /// directories. They see `['path:dir/f']` and don't know whether `f` is a
84 82 /// file or a directory, so `visit_children_set('dir')` for most matchers
85 83 /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's
86 84 /// a file (like the yet to be implemented in Rust `ExactMatcher` does),
87 85 /// it may return `VisitChildrenSet::This`.
88 86 /// Do not rely on the return being a `HashSet` indicating that there are
89 87 /// no files in this dir to investigate (or equivalently that if there are
90 88 /// files to investigate in 'dir' that it will always return
91 89 /// `VisitChildrenSet::This`).
92 90 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet;
93 91 /// Matcher will match everything and `files_set()` will be empty:
94 92 /// optimization might be possible.
95 93 fn matches_everything(&self) -> bool;
96 94 /// Matcher will match exactly the files in `files_set()`: optimization
97 95 /// might be possible.
98 96 fn is_exact(&self) -> bool;
99 97 }
100 98
101 99 /// Matches everything.
102 100 ///```
103 101 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
104 102 ///
105 103 /// let matcher = AlwaysMatcher;
106 104 ///
107 105 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
108 106 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
109 107 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
110 108 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
111 109 /// ```
112 110 #[derive(Debug)]
113 111 pub struct AlwaysMatcher;
114 112
115 113 impl Matcher for AlwaysMatcher {
116 114 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
117 115 None
118 116 }
119 117 fn exact_match(&self, _filename: &HgPath) -> bool {
120 118 false
121 119 }
122 120 fn matches(&self, _filename: &HgPath) -> bool {
123 121 true
124 122 }
125 123 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
126 124 VisitChildrenSet::Recursive
127 125 }
128 126 fn matches_everything(&self) -> bool {
129 127 true
130 128 }
131 129 fn is_exact(&self) -> bool {
132 130 false
133 131 }
134 132 }
135 133
136 134 /// Matches nothing.
137 135 #[derive(Debug)]
138 136 pub struct NeverMatcher;
139 137
140 138 impl Matcher for NeverMatcher {
141 139 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
142 140 None
143 141 }
144 142 fn exact_match(&self, _filename: &HgPath) -> bool {
145 143 false
146 144 }
147 145 fn matches(&self, _filename: &HgPath) -> bool {
148 146 false
149 147 }
150 148 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
151 149 VisitChildrenSet::Empty
152 150 }
153 151 fn matches_everything(&self) -> bool {
154 152 false
155 153 }
156 154 fn is_exact(&self) -> bool {
157 155 true
158 156 }
159 157 }
160 158
161 159 /// Matches the input files exactly. They are interpreted as paths, not
162 160 /// patterns.
163 161 ///
164 162 ///```
165 163 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::{HgPath, HgPathBuf} };
166 164 ///
167 165 /// let files = vec![HgPathBuf::from_bytes(b"a.txt"), HgPathBuf::from_bytes(br"re:.*\.c$")];
168 166 /// let matcher = FileMatcher::new(files).unwrap();
169 167 ///
170 168 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
171 169 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
172 170 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
173 171 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
174 172 /// ```
175 173 #[derive(Debug)]
176 174 pub struct FileMatcher {
177 175 files: HashSet<HgPathBuf>,
178 176 dirs: DirsMultiset,
179 177 }
180 178
181 179 impl FileMatcher {
182 180 pub fn new(files: Vec<HgPathBuf>) -> Result<Self, DirstateMapError> {
183 181 let dirs = DirsMultiset::from_manifest(&files)?;
184 182 Ok(Self {
185 183 files: HashSet::from_iter(files.into_iter()),
186 184 dirs,
187 185 })
188 186 }
189 187 fn inner_matches(&self, filename: &HgPath) -> bool {
190 188 self.files.contains(filename.as_ref())
191 189 }
192 190 }
193 191
194 192 impl Matcher for FileMatcher {
195 193 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
196 194 Some(&self.files)
197 195 }
198 196 fn exact_match(&self, filename: &HgPath) -> bool {
199 197 self.inner_matches(filename)
200 198 }
201 199 fn matches(&self, filename: &HgPath) -> bool {
202 200 self.inner_matches(filename)
203 201 }
204 202 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
205 203 if self.files.is_empty() || !self.dirs.contains(&directory) {
206 204 return VisitChildrenSet::Empty;
207 205 }
208 206 let mut candidates: HashSet<HgPathBuf> =
209 207 self.dirs.iter().cloned().collect();
210 208
211 209 candidates.extend(self.files.iter().cloned());
212 210 candidates.remove(HgPath::new(b""));
213 211
214 212 if !directory.as_ref().is_empty() {
215 213 let directory = [directory.as_ref().as_bytes(), b"/"].concat();
216 214 candidates = candidates
217 215 .iter()
218 216 .filter_map(|c| {
219 217 if c.as_bytes().starts_with(&directory) {
220 218 Some(HgPathBuf::from_bytes(
221 219 &c.as_bytes()[directory.len()..],
222 220 ))
223 221 } else {
224 222 None
225 223 }
226 224 })
227 225 .collect();
228 226 }
229 227
230 228 // `self.dirs` includes all of the directories, recursively, so if
231 229 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
232 230 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
233 231 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
234 232 // subdir will be in there without a slash.
235 233 VisitChildrenSet::Set(
236 234 candidates
237 235 .into_iter()
238 236 .filter_map(|c| {
239 237 if c.bytes().all(|b| *b != b'/') {
240 238 Some(c)
241 239 } else {
242 240 None
243 241 }
244 242 })
245 243 .collect(),
246 244 )
247 245 }
248 246 fn matches_everything(&self) -> bool {
249 247 false
250 248 }
251 249 fn is_exact(&self) -> bool {
252 250 true
253 251 }
254 252 }
255 253
256 254 /// Matches files that are included in the ignore rules.
257 255 /// ```
258 256 /// use hg::{
259 257 /// matchers::{IncludeMatcher, Matcher},
260 258 /// IgnorePattern,
261 259 /// PatternSyntax,
262 260 /// utils::hg_path::HgPath
263 261 /// };
264 262 /// use std::path::Path;
265 263 /// ///
266 264 /// let ignore_patterns =
267 265 /// vec![IgnorePattern::new(PatternSyntax::RootGlob, b"this*", Path::new(""))];
268 266 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
269 267 /// ///
270 268 /// assert_eq!(matcher.matches(HgPath::new(b"testing")), false);
271 269 /// assert_eq!(matcher.matches(HgPath::new(b"this should work")), true);
272 270 /// assert_eq!(matcher.matches(HgPath::new(b"this also")), true);
273 271 /// assert_eq!(matcher.matches(HgPath::new(b"but not this")), false);
274 272 /// ```
275 273 pub struct IncludeMatcher<'a> {
276 274 patterns: Vec<u8>,
277 275 match_fn: IgnoreFnType<'a>,
278 276 /// Whether all the patterns match a prefix (i.e. recursively)
279 277 prefix: bool,
280 278 roots: HashSet<HgPathBuf>,
281 279 dirs: HashSet<HgPathBuf>,
282 280 parents: HashSet<HgPathBuf>,
283 281 }
284 282
285 283 impl core::fmt::Debug for IncludeMatcher<'_> {
286 284 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
287 285 f.debug_struct("IncludeMatcher")
288 286 .field("patterns", &String::from_utf8_lossy(&self.patterns))
289 287 .field("prefix", &self.prefix)
290 288 .field("roots", &self.roots)
291 289 .field("dirs", &self.dirs)
292 290 .field("parents", &self.parents)
293 291 .finish()
294 292 }
295 293 }
296 294
297 295 impl<'a> Matcher for IncludeMatcher<'a> {
298 296 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
299 297 None
300 298 }
301 299
302 300 fn exact_match(&self, _filename: &HgPath) -> bool {
303 301 false
304 302 }
305 303
306 304 fn matches(&self, filename: &HgPath) -> bool {
307 305 (self.match_fn)(filename.as_ref())
308 306 }
309 307
310 308 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
311 309 let dir = directory.as_ref();
312 310 if self.prefix && self.roots.contains(dir) {
313 311 return VisitChildrenSet::Recursive;
314 312 }
315 313 if self.roots.contains(HgPath::new(b""))
316 314 || self.roots.contains(dir)
317 315 || self.dirs.contains(dir)
318 316 || find_dirs(dir).any(|parent_dir| self.roots.contains(parent_dir))
319 317 {
320 318 return VisitChildrenSet::This;
321 319 }
322 320
323 321 if self.parents.contains(directory.as_ref()) {
324 322 let multiset = self.get_all_parents_children();
325 323 if let Some(children) = multiset.get(dir) {
326 324 return VisitChildrenSet::Set(
327 325 children.into_iter().map(HgPathBuf::from).collect(),
328 326 );
329 327 }
330 328 }
331 329 VisitChildrenSet::Empty
332 330 }
333 331
334 332 fn matches_everything(&self) -> bool {
335 333 false
336 334 }
337 335
338 336 fn is_exact(&self) -> bool {
339 337 false
340 338 }
341 339 }
342 340
343 341 /// The union of multiple matchers. Will match if any of the matchers match.
344 342 #[derive(Debug)]
345 343 pub struct UnionMatcher {
346 344 matchers: Vec<Box<dyn Matcher + Sync>>,
347 345 }
348 346
349 347 impl Matcher for UnionMatcher {
350 348 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
351 349 None
352 350 }
353 351
354 352 fn exact_match(&self, _filename: &HgPath) -> bool {
355 353 false
356 354 }
357 355
358 356 fn matches(&self, filename: &HgPath) -> bool {
359 357 self.matchers.iter().any(|m| m.matches(filename))
360 358 }
361 359
362 360 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
363 361 let mut result = HashSet::new();
364 362 let mut this = false;
365 363 for matcher in self.matchers.iter() {
366 364 let visit = matcher.visit_children_set(directory);
367 365 match visit {
368 366 VisitChildrenSet::Empty => continue,
369 367 VisitChildrenSet::This => {
370 368 this = true;
371 369 // Don't break, we might have an 'all' in here.
372 370 continue;
373 371 }
374 372 VisitChildrenSet::Set(set) => {
375 373 result.extend(set);
376 374 }
377 375 VisitChildrenSet::Recursive => {
378 376 return visit;
379 377 }
380 378 }
381 379 }
382 380 if this {
383 381 return VisitChildrenSet::This;
384 382 }
385 383 if result.is_empty() {
386 384 VisitChildrenSet::Empty
387 385 } else {
388 386 VisitChildrenSet::Set(result)
389 387 }
390 388 }
391 389
392 390 fn matches_everything(&self) -> bool {
393 391 // TODO Maybe if all are AlwaysMatcher?
394 392 false
395 393 }
396 394
397 395 fn is_exact(&self) -> bool {
398 396 false
399 397 }
400 398 }
401 399
402 400 impl UnionMatcher {
403 401 pub fn new(matchers: Vec<Box<dyn Matcher + Sync>>) -> Self {
404 402 Self { matchers }
405 403 }
406 404 }
407 405
408 406 #[derive(Debug)]
409 407 pub struct IntersectionMatcher {
410 408 m1: Box<dyn Matcher + Sync>,
411 409 m2: Box<dyn Matcher + Sync>,
412 410 files: Option<HashSet<HgPathBuf>>,
413 411 }
414 412
415 413 impl Matcher for IntersectionMatcher {
416 414 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
417 415 self.files.as_ref()
418 416 }
419 417
420 418 fn exact_match(&self, filename: &HgPath) -> bool {
421 419 self.files.as_ref().map_or(false, |f| f.contains(filename))
422 420 }
423 421
424 422 fn matches(&self, filename: &HgPath) -> bool {
425 423 self.m1.matches(filename) && self.m2.matches(filename)
426 424 }
427 425
428 426 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
429 427 let m1_set = self.m1.visit_children_set(directory);
430 428 if m1_set == VisitChildrenSet::Empty {
431 429 return VisitChildrenSet::Empty;
432 430 }
433 431 let m2_set = self.m2.visit_children_set(directory);
434 432 if m2_set == VisitChildrenSet::Empty {
435 433 return VisitChildrenSet::Empty;
436 434 }
437 435
438 436 if m1_set == VisitChildrenSet::Recursive {
439 437 return m2_set;
440 438 } else if m2_set == VisitChildrenSet::Recursive {
441 439 return m1_set;
442 440 }
443 441
444 442 match (&m1_set, &m2_set) {
445 443 (VisitChildrenSet::Recursive, _) => m2_set,
446 444 (_, VisitChildrenSet::Recursive) => m1_set,
447 445 (VisitChildrenSet::This, _) | (_, VisitChildrenSet::This) => {
448 446 VisitChildrenSet::This
449 447 }
450 448 (VisitChildrenSet::Set(m1), VisitChildrenSet::Set(m2)) => {
451 449 let set: HashSet<_> = m1.intersection(&m2).cloned().collect();
452 450 if set.is_empty() {
453 451 VisitChildrenSet::Empty
454 452 } else {
455 453 VisitChildrenSet::Set(set)
456 454 }
457 455 }
458 456 _ => unreachable!(),
459 457 }
460 458 }
461 459
462 460 fn matches_everything(&self) -> bool {
463 461 self.m1.matches_everything() && self.m2.matches_everything()
464 462 }
465 463
466 464 fn is_exact(&self) -> bool {
467 465 self.m1.is_exact() || self.m2.is_exact()
468 466 }
469 467 }
470 468
471 469 impl IntersectionMatcher {
472 470 pub fn new(
473 471 mut m1: Box<dyn Matcher + Sync>,
474 472 mut m2: Box<dyn Matcher + Sync>,
475 473 ) -> Self {
476 474 let files = if m1.is_exact() || m2.is_exact() {
477 475 if !m1.is_exact() {
478 476 std::mem::swap(&mut m1, &mut m2);
479 477 }
480 478 m1.file_set().map(|m1_files| {
481 479 m1_files.iter().cloned().filter(|f| m2.matches(f)).collect()
482 480 })
483 481 } else {
484 482 None
485 483 };
486 484 Self { m1, m2, files }
487 485 }
488 486 }
489 487
490 488 #[derive(Debug)]
491 489 pub struct DifferenceMatcher {
492 490 base: Box<dyn Matcher + Sync>,
493 491 excluded: Box<dyn Matcher + Sync>,
494 492 files: Option<HashSet<HgPathBuf>>,
495 493 }
496 494
497 495 impl Matcher for DifferenceMatcher {
498 496 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
499 497 self.files.as_ref()
500 498 }
501 499
502 500 fn exact_match(&self, filename: &HgPath) -> bool {
503 501 self.files.as_ref().map_or(false, |f| f.contains(filename))
504 502 }
505 503
506 504 fn matches(&self, filename: &HgPath) -> bool {
507 505 self.base.matches(filename) && !self.excluded.matches(filename)
508 506 }
509 507
510 508 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
511 509 let excluded_set = self.excluded.visit_children_set(directory);
512 510 if excluded_set == VisitChildrenSet::Recursive {
513 511 return VisitChildrenSet::Empty;
514 512 }
515 513 let base_set = self.base.visit_children_set(directory);
516 514 // Possible values for base: 'recursive', 'this', set(...), set()
517 515 // Possible values for excluded: 'this', set(...), set()
518 516 // If excluded has nothing under here that we care about, return base,
519 517 // even if it's 'recursive'.
520 518 if excluded_set == VisitChildrenSet::Empty {
521 519 return base_set;
522 520 }
523 521 match base_set {
524 522 VisitChildrenSet::This | VisitChildrenSet::Recursive => {
525 523 // Never return 'recursive' here if excluded_set is any kind of
526 524 // non-empty (either 'this' or set(foo)), since excluded might
527 525 // return set() for a subdirectory.
528 526 VisitChildrenSet::This
529 527 }
530 528 set => {
531 529 // Possible values for base: set(...), set()
532 530 // Possible values for excluded: 'this', set(...)
533 531 // We ignore excluded set results. They're possibly incorrect:
534 532 // base = path:dir/subdir
535 533 // excluded=rootfilesin:dir,
536 534 // visit_children_set(''):
537 535 // base returns {'dir'}, excluded returns {'dir'}, if we
538 536 // subtracted we'd return set(), which is *not* correct, we
539 537 // still need to visit 'dir'!
540 538 set
541 539 }
542 540 }
543 541 }
544 542
545 543 fn matches_everything(&self) -> bool {
546 544 false
547 545 }
548 546
549 547 fn is_exact(&self) -> bool {
550 548 self.base.is_exact()
551 549 }
552 550 }
553 551
554 552 impl DifferenceMatcher {
555 553 pub fn new(
556 554 base: Box<dyn Matcher + Sync>,
557 555 excluded: Box<dyn Matcher + Sync>,
558 556 ) -> Self {
559 557 let base_is_exact = base.is_exact();
560 558 let base_files = base.file_set().map(ToOwned::to_owned);
561 559 let mut new = Self {
562 560 base,
563 561 excluded,
564 562 files: None,
565 563 };
566 564 if base_is_exact {
567 565 new.files = base_files.map(|files| {
568 566 files.iter().cloned().filter(|f| new.matches(f)).collect()
569 567 });
570 568 }
571 569 new
572 570 }
573 571 }
574 572
575 573 /// Wraps [`regex::bytes::Regex`] to improve performance in multithreaded
576 574 /// contexts.
577 575 ///
578 576 /// The `status` algorithm makes heavy use of threads, and calling `is_match`
579 577 /// from many threads at once is prone to contention, probably within the
580 578 /// scratch space needed as the regex DFA is built lazily.
581 579 ///
582 580 /// We are in the process of raising the issue upstream, but for now
583 581 /// the workaround used here is to store the `Regex` in a lazily populated
584 582 /// thread-local variable, sharing the initial read-only compilation, but
585 583 /// not the lazy dfa scratch space mentioned above.
586 584 ///
587 585 /// This reduces the contention observed with 16+ threads, but does not
588 586 /// completely remove it. Hopefully this can be addressed upstream.
589 587 struct RegexMatcher {
590 588 /// Compiled at the start of the status algorithm, used as a base for
591 589 /// cloning in each thread-local `self.local`, thus sharing the expensive
592 590 /// first compilation.
593 591 base: regex::bytes::Regex,
594 592 /// Thread-local variable that holds the `Regex` that is actually queried
595 593 /// from each thread.
596 594 local: thread_local::ThreadLocal<regex::bytes::Regex>,
597 595 }
598 596
599 597 impl RegexMatcher {
600 598 /// Returns whether the path matches the stored `Regex`.
601 599 pub fn is_match(&self, path: &HgPath) -> bool {
602 600 self.local
603 601 .get_or(|| self.base.clone())
604 602 .is_match(path.as_bytes())
605 603 }
606 604 }
607 605
608 606 /// Returns a function that matches an `HgPath` against the given regex
609 607 /// pattern.
610 608 ///
611 609 /// This can fail when the pattern is invalid or not supported by the
612 610 /// underlying engine (the `regex` crate), for instance anything with
613 611 /// back-references.
614 #[timed]
612 #[logging_timer::time("trace")]
615 613 fn re_matcher(pattern: &[u8]) -> PatternResult<RegexMatcher> {
616 614 use std::io::Write;
617 615
618 616 // The `regex` crate adds `.*` to the start and end of expressions if there
619 617 // are no anchors, so add the start anchor.
620 618 let mut escaped_bytes = vec![b'^', b'(', b'?', b':'];
621 619 for byte in pattern {
622 620 if *byte > 127 {
623 621 write!(escaped_bytes, "\\x{:x}", *byte).unwrap();
624 622 } else {
625 623 escaped_bytes.push(*byte);
626 624 }
627 625 }
628 626 escaped_bytes.push(b')');
629 627
630 628 // Avoid the cost of UTF8 checking
631 629 //
632 630 // # Safety
633 631 // This is safe because we escaped all non-ASCII bytes.
634 632 let pattern_string = unsafe { String::from_utf8_unchecked(escaped_bytes) };
635 633 let re = regex::bytes::RegexBuilder::new(&pattern_string)
636 634 .unicode(false)
637 635 // Big repos with big `.hgignore` will hit the default limit and
638 636 // incur a significant performance hit. One repo's `hg status` hit
639 637 // multiple *minutes*.
640 638 .dfa_size_limit(50 * (1 << 20))
641 639 .build()
642 640 .map_err(|e| PatternError::UnsupportedSyntax(e.to_string()))?;
643 641
644 642 Ok(RegexMatcher {
645 643 base: re,
646 644 local: Default::default(),
647 645 })
648 646 }
649 647
650 648 /// Returns the regex pattern and a function that matches an `HgPath` against
651 649 /// said regex formed by the given ignore patterns.
652 650 fn build_regex_match<'a, 'b>(
653 651 ignore_patterns: &'a [IgnorePattern],
654 652 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'b>)> {
655 653 let mut regexps = vec![];
656 654 let mut exact_set = HashSet::new();
657 655
658 656 for pattern in ignore_patterns {
659 657 if let Some(re) = build_single_regex(pattern)? {
660 658 regexps.push(re);
661 659 } else {
662 660 let exact = normalize_path_bytes(&pattern.pattern);
663 661 exact_set.insert(HgPathBuf::from_bytes(&exact));
664 662 }
665 663 }
666 664
667 665 let full_regex = regexps.join(&b'|');
668 666
669 667 // An empty pattern would cause the regex engine to incorrectly match the
670 668 // (empty) root directory
671 669 let func = if !(regexps.is_empty()) {
672 670 let matcher = re_matcher(&full_regex)?;
673 671 let func = move |filename: &HgPath| {
674 672 exact_set.contains(filename) || matcher.is_match(filename)
675 673 };
676 674 Box::new(func) as IgnoreFnType
677 675 } else {
678 676 let func = move |filename: &HgPath| exact_set.contains(filename);
679 677 Box::new(func) as IgnoreFnType
680 678 };
681 679
682 680 Ok((full_regex, func))
683 681 }
684 682
685 683 /// Returns roots and directories corresponding to each pattern.
686 684 ///
687 685 /// This calculates the roots and directories exactly matching the patterns and
688 686 /// returns a tuple of (roots, dirs). It does not return other directories
689 687 /// which may also need to be considered, like the parent directories.
690 688 fn roots_and_dirs(
691 689 ignore_patterns: &[IgnorePattern],
692 690 ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) {
693 691 let mut roots = Vec::new();
694 692 let mut dirs = Vec::new();
695 693
696 694 for ignore_pattern in ignore_patterns {
697 695 let IgnorePattern {
698 696 syntax, pattern, ..
699 697 } = ignore_pattern;
700 698 match syntax {
701 699 PatternSyntax::RootGlob | PatternSyntax::Glob => {
702 700 let mut root = HgPathBuf::new();
703 701 for p in pattern.split(|c| *c == b'/') {
704 702 if p.iter().any(|c| match *c {
705 703 b'[' | b'{' | b'*' | b'?' => true,
706 704 _ => false,
707 705 }) {
708 706 break;
709 707 }
710 708 root.push(HgPathBuf::from_bytes(p).as_ref());
711 709 }
712 710 roots.push(root);
713 711 }
714 712 PatternSyntax::Path | PatternSyntax::RelPath => {
715 713 let pat = HgPath::new(if pattern == b"." {
716 714 &[] as &[u8]
717 715 } else {
718 716 pattern
719 717 });
720 718 roots.push(pat.to_owned());
721 719 }
722 720 PatternSyntax::RootFiles => {
723 721 let pat = if pattern == b"." {
724 722 &[] as &[u8]
725 723 } else {
726 724 pattern
727 725 };
728 726 dirs.push(HgPathBuf::from_bytes(pat));
729 727 }
730 728 _ => {
731 729 roots.push(HgPathBuf::new());
732 730 }
733 731 }
734 732 }
735 733 (roots, dirs)
736 734 }
737 735
738 736 /// Paths extracted from patterns
739 737 #[derive(Debug, PartialEq)]
740 738 struct RootsDirsAndParents {
741 739 /// Directories to match recursively
742 740 pub roots: HashSet<HgPathBuf>,
743 741 /// Directories to match non-recursively
744 742 pub dirs: HashSet<HgPathBuf>,
745 743 /// Implicitly required directories to go to items in either roots or dirs
746 744 pub parents: HashSet<HgPathBuf>,
747 745 }
748 746
749 747 /// Extract roots, dirs and parents from patterns.
750 748 fn roots_dirs_and_parents(
751 749 ignore_patterns: &[IgnorePattern],
752 750 ) -> PatternResult<RootsDirsAndParents> {
753 751 let (roots, dirs) = roots_and_dirs(ignore_patterns);
754 752
755 753 let mut parents = HashSet::new();
756 754
757 755 parents.extend(
758 756 DirsMultiset::from_manifest(&dirs)
759 757 .map_err(|e| match e {
760 758 DirstateMapError::InvalidPath(e) => e,
761 759 _ => unreachable!(),
762 760 })?
763 761 .iter()
764 762 .map(ToOwned::to_owned),
765 763 );
766 764 parents.extend(
767 765 DirsMultiset::from_manifest(&roots)
768 766 .map_err(|e| match e {
769 767 DirstateMapError::InvalidPath(e) => e,
770 768 _ => unreachable!(),
771 769 })?
772 770 .iter()
773 771 .map(ToOwned::to_owned),
774 772 );
775 773
776 774 Ok(RootsDirsAndParents {
777 775 roots: HashSet::from_iter(roots),
778 776 dirs: HashSet::from_iter(dirs),
779 777 parents,
780 778 })
781 779 }
782 780
783 781 /// Returns a function that checks whether a given file (in the general sense)
784 782 /// should be matched.
785 783 fn build_match<'a, 'b>(
786 784 ignore_patterns: Vec<IgnorePattern>,
787 785 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'b>)> {
788 786 let mut match_funcs: Vec<IgnoreFnType<'b>> = vec![];
789 787 // For debugging and printing
790 788 let mut patterns = vec![];
791 789
792 790 let (subincludes, ignore_patterns) = filter_subincludes(ignore_patterns)?;
793 791
794 792 if !subincludes.is_empty() {
795 793 // Build prefix-based matcher functions for subincludes
796 794 let mut submatchers = FastHashMap::default();
797 795 let mut prefixes = vec![];
798 796
799 797 for sub_include in subincludes {
800 798 let matcher = IncludeMatcher::new(sub_include.included_patterns)?;
801 799 let match_fn =
802 800 Box::new(move |path: &HgPath| matcher.matches(path));
803 801 prefixes.push(sub_include.prefix.clone());
804 802 submatchers.insert(sub_include.prefix.clone(), match_fn);
805 803 }
806 804
807 805 let match_subinclude = move |filename: &HgPath| {
808 806 for prefix in prefixes.iter() {
809 807 if let Some(rel) = filename.relative_to(prefix) {
810 808 if (submatchers[prefix])(rel) {
811 809 return true;
812 810 }
813 811 }
814 812 }
815 813 false
816 814 };
817 815
818 816 match_funcs.push(Box::new(match_subinclude));
819 817 }
820 818
821 819 if !ignore_patterns.is_empty() {
822 820 // Either do dumb matching if all patterns are rootfiles, or match
823 821 // with a regex.
824 822 if ignore_patterns
825 823 .iter()
826 824 .all(|k| k.syntax == PatternSyntax::RootFiles)
827 825 {
828 826 let dirs: HashSet<_> = ignore_patterns
829 827 .iter()
830 828 .map(|k| k.pattern.to_owned())
831 829 .collect();
832 830 let mut dirs_vec: Vec<_> = dirs.iter().cloned().collect();
833 831
834 832 let match_func = move |path: &HgPath| -> bool {
835 833 let path = path.as_bytes();
836 834 let i = path.iter().rfind(|a| **a == b'/');
837 835 let dir = if let Some(i) = i {
838 836 &path[..*i as usize]
839 837 } else {
840 838 b"."
841 839 };
842 840 dirs.contains(dir.deref())
843 841 };
844 842 match_funcs.push(Box::new(match_func));
845 843
846 844 patterns.extend(b"rootfilesin: ");
847 845 dirs_vec.sort();
848 846 patterns.extend(dirs_vec.escaped_bytes());
849 847 } else {
850 848 let (new_re, match_func) = build_regex_match(&ignore_patterns)?;
851 849 patterns = new_re;
852 850 match_funcs.push(match_func)
853 851 }
854 852 }
855 853
856 854 Ok(if match_funcs.len() == 1 {
857 855 (patterns, match_funcs.remove(0))
858 856 } else {
859 857 (
860 858 patterns,
861 859 Box::new(move |f: &HgPath| -> bool {
862 860 match_funcs.iter().any(|match_func| match_func(f))
863 861 }),
864 862 )
865 863 })
866 864 }
867 865
868 866 /// Parses all "ignore" files with their recursive includes and returns a
869 867 /// function that checks whether a given file (in the general sense) should be
870 868 /// ignored.
871 869 pub fn get_ignore_matcher<'a>(
872 870 mut all_pattern_files: Vec<PathBuf>,
873 871 root_dir: &Path,
874 872 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
875 873 ) -> PatternResult<(IncludeMatcher<'a>, Vec<PatternFileWarning>)> {
876 874 let mut all_patterns = vec![];
877 875 let mut all_warnings = vec![];
878 876
879 877 // Sort to make the ordering of calls to `inspect_pattern_bytes`
880 878 // deterministic even if the ordering of `all_pattern_files` is not (such
881 879 // as when a iteration order of a Python dict or Rust HashMap is involved).
882 880 // Sort by "string" representation instead of the default by component
883 881 // (with a Rust-specific definition of a component)
884 882 all_pattern_files
885 883 .sort_unstable_by(|a, b| a.as_os_str().cmp(b.as_os_str()));
886 884
887 885 for pattern_file in &all_pattern_files {
888 886 let (patterns, warnings) = get_patterns_from_file(
889 887 pattern_file,
890 888 root_dir,
891 889 inspect_pattern_bytes,
892 890 )?;
893 891
894 892 all_patterns.extend(patterns.to_owned());
895 893 all_warnings.extend(warnings);
896 894 }
897 895 let matcher = IncludeMatcher::new(all_patterns)?;
898 896 Ok((matcher, all_warnings))
899 897 }
900 898
901 899 /// Parses all "ignore" files with their recursive includes and returns a
902 900 /// function that checks whether a given file (in the general sense) should be
903 901 /// ignored.
904 902 pub fn get_ignore_function<'a>(
905 903 all_pattern_files: Vec<PathBuf>,
906 904 root_dir: &Path,
907 905 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
908 906 ) -> PatternResult<(IgnoreFnType<'a>, Vec<PatternFileWarning>)> {
909 907 let res =
910 908 get_ignore_matcher(all_pattern_files, root_dir, inspect_pattern_bytes);
911 909 res.map(|(matcher, all_warnings)| {
912 910 let res: IgnoreFnType<'a> =
913 911 Box::new(move |path: &HgPath| matcher.matches(path));
914 912
915 913 (res, all_warnings)
916 914 })
917 915 }
918 916
919 917 impl<'a> IncludeMatcher<'a> {
920 918 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
921 919 let RootsDirsAndParents {
922 920 roots,
923 921 dirs,
924 922 parents,
925 923 } = roots_dirs_and_parents(&ignore_patterns)?;
926 924 let prefix = ignore_patterns.iter().all(|k| match k.syntax {
927 925 PatternSyntax::Path | PatternSyntax::RelPath => true,
928 926 _ => false,
929 927 });
930 928 let (patterns, match_fn) = build_match(ignore_patterns)?;
931 929
932 930 Ok(Self {
933 931 patterns,
934 932 match_fn,
935 933 prefix,
936 934 roots,
937 935 dirs,
938 936 parents,
939 937 })
940 938 }
941 939
942 940 fn get_all_parents_children(&self) -> DirsChildrenMultiset {
943 941 // TODO cache
944 942 let thing = self
945 943 .dirs
946 944 .iter()
947 945 .chain(self.roots.iter())
948 946 .chain(self.parents.iter());
949 947 DirsChildrenMultiset::new(thing, Some(&self.parents))
950 948 }
951 949
952 950 pub fn debug_get_patterns(&self) -> &[u8] {
953 951 self.patterns.as_ref()
954 952 }
955 953 }
956 954
957 955 impl<'a> Display for IncludeMatcher<'a> {
958 956 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
959 957 // XXX What about exact matches?
960 958 // I'm not sure it's worth it to clone the HashSet and keep it
961 959 // around just in case someone wants to display the matcher, plus
962 960 // it's going to be unreadable after a few entries, but we need to
963 961 // inform in this display that exact matches are being used and are
964 962 // (on purpose) missing from the `includes`.
965 963 write!(
966 964 f,
967 965 "IncludeMatcher(includes='{}')",
968 966 String::from_utf8_lossy(&self.patterns.escaped_bytes())
969 967 )
970 968 }
971 969 }
972 970
973 971 #[cfg(test)]
974 972 mod tests {
975 973 use super::*;
976 974 use pretty_assertions::assert_eq;
977 975 use std::path::Path;
978 976
979 977 #[test]
980 978 fn test_roots_and_dirs() {
981 979 let pats = vec![
982 980 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
983 981 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
984 982 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
985 983 ];
986 984 let (roots, dirs) = roots_and_dirs(&pats);
987 985
988 986 assert_eq!(
989 987 roots,
990 988 vec!(
991 989 HgPathBuf::from_bytes(b"g/h"),
992 990 HgPathBuf::from_bytes(b"g/h"),
993 991 HgPathBuf::new()
994 992 ),
995 993 );
996 994 assert_eq!(dirs, vec!());
997 995 }
998 996
999 997 #[test]
1000 998 fn test_roots_dirs_and_parents() {
1001 999 let pats = vec![
1002 1000 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1003 1001 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1004 1002 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1005 1003 ];
1006 1004
1007 1005 let mut roots = HashSet::new();
1008 1006 roots.insert(HgPathBuf::from_bytes(b"g/h"));
1009 1007 roots.insert(HgPathBuf::new());
1010 1008
1011 1009 let dirs = HashSet::new();
1012 1010
1013 1011 let mut parents = HashSet::new();
1014 1012 parents.insert(HgPathBuf::new());
1015 1013 parents.insert(HgPathBuf::from_bytes(b"g"));
1016 1014
1017 1015 assert_eq!(
1018 1016 roots_dirs_and_parents(&pats).unwrap(),
1019 1017 RootsDirsAndParents {
1020 1018 roots,
1021 1019 dirs,
1022 1020 parents
1023 1021 }
1024 1022 );
1025 1023 }
1026 1024
1027 1025 #[test]
1028 1026 fn test_filematcher_visit_children_set() {
1029 1027 // Visitchildrenset
1030 1028 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/foo.txt")];
1031 1029 let matcher = FileMatcher::new(files).unwrap();
1032 1030
1033 1031 let mut set = HashSet::new();
1034 1032 set.insert(HgPathBuf::from_bytes(b"dir"));
1035 1033 assert_eq!(
1036 1034 matcher.visit_children_set(HgPath::new(b"")),
1037 1035 VisitChildrenSet::Set(set)
1038 1036 );
1039 1037
1040 1038 let mut set = HashSet::new();
1041 1039 set.insert(HgPathBuf::from_bytes(b"subdir"));
1042 1040 assert_eq!(
1043 1041 matcher.visit_children_set(HgPath::new(b"dir")),
1044 1042 VisitChildrenSet::Set(set)
1045 1043 );
1046 1044
1047 1045 let mut set = HashSet::new();
1048 1046 set.insert(HgPathBuf::from_bytes(b"foo.txt"));
1049 1047 assert_eq!(
1050 1048 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1051 1049 VisitChildrenSet::Set(set)
1052 1050 );
1053 1051
1054 1052 assert_eq!(
1055 1053 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1056 1054 VisitChildrenSet::Empty
1057 1055 );
1058 1056 assert_eq!(
1059 1057 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
1060 1058 VisitChildrenSet::Empty
1061 1059 );
1062 1060 assert_eq!(
1063 1061 matcher.visit_children_set(HgPath::new(b"folder")),
1064 1062 VisitChildrenSet::Empty
1065 1063 );
1066 1064 }
1067 1065
1068 1066 #[test]
1069 1067 fn test_filematcher_visit_children_set_files_and_dirs() {
1070 1068 let files = vec![
1071 1069 HgPathBuf::from_bytes(b"rootfile.txt"),
1072 1070 HgPathBuf::from_bytes(b"a/file1.txt"),
1073 1071 HgPathBuf::from_bytes(b"a/b/file2.txt"),
1074 1072 // No file in a/b/c
1075 1073 HgPathBuf::from_bytes(b"a/b/c/d/file4.txt"),
1076 1074 ];
1077 1075 let matcher = FileMatcher::new(files).unwrap();
1078 1076
1079 1077 let mut set = HashSet::new();
1080 1078 set.insert(HgPathBuf::from_bytes(b"a"));
1081 1079 set.insert(HgPathBuf::from_bytes(b"rootfile.txt"));
1082 1080 assert_eq!(
1083 1081 matcher.visit_children_set(HgPath::new(b"")),
1084 1082 VisitChildrenSet::Set(set)
1085 1083 );
1086 1084
1087 1085 let mut set = HashSet::new();
1088 1086 set.insert(HgPathBuf::from_bytes(b"b"));
1089 1087 set.insert(HgPathBuf::from_bytes(b"file1.txt"));
1090 1088 assert_eq!(
1091 1089 matcher.visit_children_set(HgPath::new(b"a")),
1092 1090 VisitChildrenSet::Set(set)
1093 1091 );
1094 1092
1095 1093 let mut set = HashSet::new();
1096 1094 set.insert(HgPathBuf::from_bytes(b"c"));
1097 1095 set.insert(HgPathBuf::from_bytes(b"file2.txt"));
1098 1096 assert_eq!(
1099 1097 matcher.visit_children_set(HgPath::new(b"a/b")),
1100 1098 VisitChildrenSet::Set(set)
1101 1099 );
1102 1100
1103 1101 let mut set = HashSet::new();
1104 1102 set.insert(HgPathBuf::from_bytes(b"d"));
1105 1103 assert_eq!(
1106 1104 matcher.visit_children_set(HgPath::new(b"a/b/c")),
1107 1105 VisitChildrenSet::Set(set)
1108 1106 );
1109 1107 let mut set = HashSet::new();
1110 1108 set.insert(HgPathBuf::from_bytes(b"file4.txt"));
1111 1109 assert_eq!(
1112 1110 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
1113 1111 VisitChildrenSet::Set(set)
1114 1112 );
1115 1113
1116 1114 assert_eq!(
1117 1115 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
1118 1116 VisitChildrenSet::Empty
1119 1117 );
1120 1118 assert_eq!(
1121 1119 matcher.visit_children_set(HgPath::new(b"folder")),
1122 1120 VisitChildrenSet::Empty
1123 1121 );
1124 1122 }
1125 1123
1126 1124 #[test]
1127 1125 fn test_includematcher() {
1128 1126 // VisitchildrensetPrefix
1129 1127 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1130 1128 PatternSyntax::RelPath,
1131 1129 b"dir/subdir",
1132 1130 Path::new(""),
1133 1131 )])
1134 1132 .unwrap();
1135 1133
1136 1134 let mut set = HashSet::new();
1137 1135 set.insert(HgPathBuf::from_bytes(b"dir"));
1138 1136 assert_eq!(
1139 1137 matcher.visit_children_set(HgPath::new(b"")),
1140 1138 VisitChildrenSet::Set(set)
1141 1139 );
1142 1140
1143 1141 let mut set = HashSet::new();
1144 1142 set.insert(HgPathBuf::from_bytes(b"subdir"));
1145 1143 assert_eq!(
1146 1144 matcher.visit_children_set(HgPath::new(b"dir")),
1147 1145 VisitChildrenSet::Set(set)
1148 1146 );
1149 1147 assert_eq!(
1150 1148 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1151 1149 VisitChildrenSet::Recursive
1152 1150 );
1153 1151 // OPT: This should probably be 'all' if its parent is?
1154 1152 assert_eq!(
1155 1153 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1156 1154 VisitChildrenSet::This
1157 1155 );
1158 1156 assert_eq!(
1159 1157 matcher.visit_children_set(HgPath::new(b"folder")),
1160 1158 VisitChildrenSet::Empty
1161 1159 );
1162 1160
1163 1161 // VisitchildrensetRootfilesin
1164 1162 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1165 1163 PatternSyntax::RootFiles,
1166 1164 b"dir/subdir",
1167 1165 Path::new(""),
1168 1166 )])
1169 1167 .unwrap();
1170 1168
1171 1169 let mut set = HashSet::new();
1172 1170 set.insert(HgPathBuf::from_bytes(b"dir"));
1173 1171 assert_eq!(
1174 1172 matcher.visit_children_set(HgPath::new(b"")),
1175 1173 VisitChildrenSet::Set(set)
1176 1174 );
1177 1175
1178 1176 let mut set = HashSet::new();
1179 1177 set.insert(HgPathBuf::from_bytes(b"subdir"));
1180 1178 assert_eq!(
1181 1179 matcher.visit_children_set(HgPath::new(b"dir")),
1182 1180 VisitChildrenSet::Set(set)
1183 1181 );
1184 1182
1185 1183 assert_eq!(
1186 1184 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1187 1185 VisitChildrenSet::This
1188 1186 );
1189 1187 assert_eq!(
1190 1188 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1191 1189 VisitChildrenSet::Empty
1192 1190 );
1193 1191 assert_eq!(
1194 1192 matcher.visit_children_set(HgPath::new(b"folder")),
1195 1193 VisitChildrenSet::Empty
1196 1194 );
1197 1195
1198 1196 // VisitchildrensetGlob
1199 1197 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1200 1198 PatternSyntax::Glob,
1201 1199 b"dir/z*",
1202 1200 Path::new(""),
1203 1201 )])
1204 1202 .unwrap();
1205 1203
1206 1204 let mut set = HashSet::new();
1207 1205 set.insert(HgPathBuf::from_bytes(b"dir"));
1208 1206 assert_eq!(
1209 1207 matcher.visit_children_set(HgPath::new(b"")),
1210 1208 VisitChildrenSet::Set(set)
1211 1209 );
1212 1210 assert_eq!(
1213 1211 matcher.visit_children_set(HgPath::new(b"folder")),
1214 1212 VisitChildrenSet::Empty
1215 1213 );
1216 1214 assert_eq!(
1217 1215 matcher.visit_children_set(HgPath::new(b"dir")),
1218 1216 VisitChildrenSet::This
1219 1217 );
1220 1218 // OPT: these should probably be set().
1221 1219 assert_eq!(
1222 1220 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1223 1221 VisitChildrenSet::This
1224 1222 );
1225 1223 assert_eq!(
1226 1224 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1227 1225 VisitChildrenSet::This
1228 1226 );
1229 1227
1230 1228 // Test multiple patterns
1231 1229 let matcher = IncludeMatcher::new(vec![
1232 1230 IgnorePattern::new(PatternSyntax::RelPath, b"foo", Path::new("")),
1233 1231 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1234 1232 ])
1235 1233 .unwrap();
1236 1234
1237 1235 assert_eq!(
1238 1236 matcher.visit_children_set(HgPath::new(b"")),
1239 1237 VisitChildrenSet::This
1240 1238 );
1241 1239
1242 1240 // Test multiple patterns
1243 1241 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1244 1242 PatternSyntax::Glob,
1245 1243 b"**/*.exe",
1246 1244 Path::new(""),
1247 1245 )])
1248 1246 .unwrap();
1249 1247
1250 1248 assert_eq!(
1251 1249 matcher.visit_children_set(HgPath::new(b"")),
1252 1250 VisitChildrenSet::This
1253 1251 );
1254 1252 }
1255 1253
1256 1254 #[test]
1257 1255 fn test_unionmatcher() {
1258 1256 // Path + Rootfiles
1259 1257 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1260 1258 PatternSyntax::RelPath,
1261 1259 b"dir/subdir",
1262 1260 Path::new(""),
1263 1261 )])
1264 1262 .unwrap();
1265 1263 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1266 1264 PatternSyntax::RootFiles,
1267 1265 b"dir",
1268 1266 Path::new(""),
1269 1267 )])
1270 1268 .unwrap();
1271 1269 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1272 1270
1273 1271 let mut set = HashSet::new();
1274 1272 set.insert(HgPathBuf::from_bytes(b"dir"));
1275 1273 assert_eq!(
1276 1274 matcher.visit_children_set(HgPath::new(b"")),
1277 1275 VisitChildrenSet::Set(set)
1278 1276 );
1279 1277 assert_eq!(
1280 1278 matcher.visit_children_set(HgPath::new(b"dir")),
1281 1279 VisitChildrenSet::This
1282 1280 );
1283 1281 assert_eq!(
1284 1282 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1285 1283 VisitChildrenSet::Recursive
1286 1284 );
1287 1285 assert_eq!(
1288 1286 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1289 1287 VisitChildrenSet::Empty
1290 1288 );
1291 1289 assert_eq!(
1292 1290 matcher.visit_children_set(HgPath::new(b"folder")),
1293 1291 VisitChildrenSet::Empty
1294 1292 );
1295 1293 assert_eq!(
1296 1294 matcher.visit_children_set(HgPath::new(b"folder")),
1297 1295 VisitChildrenSet::Empty
1298 1296 );
1299 1297
1300 1298 // OPT: These next two could be 'all' instead of 'this'.
1301 1299 assert_eq!(
1302 1300 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1303 1301 VisitChildrenSet::This
1304 1302 );
1305 1303 assert_eq!(
1306 1304 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1307 1305 VisitChildrenSet::This
1308 1306 );
1309 1307
1310 1308 // Path + unrelated Path
1311 1309 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1312 1310 PatternSyntax::RelPath,
1313 1311 b"dir/subdir",
1314 1312 Path::new(""),
1315 1313 )])
1316 1314 .unwrap();
1317 1315 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1318 1316 PatternSyntax::RelPath,
1319 1317 b"folder",
1320 1318 Path::new(""),
1321 1319 )])
1322 1320 .unwrap();
1323 1321 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1324 1322
1325 1323 let mut set = HashSet::new();
1326 1324 set.insert(HgPathBuf::from_bytes(b"folder"));
1327 1325 set.insert(HgPathBuf::from_bytes(b"dir"));
1328 1326 assert_eq!(
1329 1327 matcher.visit_children_set(HgPath::new(b"")),
1330 1328 VisitChildrenSet::Set(set)
1331 1329 );
1332 1330 let mut set = HashSet::new();
1333 1331 set.insert(HgPathBuf::from_bytes(b"subdir"));
1334 1332 assert_eq!(
1335 1333 matcher.visit_children_set(HgPath::new(b"dir")),
1336 1334 VisitChildrenSet::Set(set)
1337 1335 );
1338 1336
1339 1337 assert_eq!(
1340 1338 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1341 1339 VisitChildrenSet::Recursive
1342 1340 );
1343 1341 assert_eq!(
1344 1342 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1345 1343 VisitChildrenSet::Empty
1346 1344 );
1347 1345
1348 1346 assert_eq!(
1349 1347 matcher.visit_children_set(HgPath::new(b"folder")),
1350 1348 VisitChildrenSet::Recursive
1351 1349 );
1352 1350 // OPT: These next two could be 'all' instead of 'this'.
1353 1351 assert_eq!(
1354 1352 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1355 1353 VisitChildrenSet::This
1356 1354 );
1357 1355 assert_eq!(
1358 1356 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1359 1357 VisitChildrenSet::This
1360 1358 );
1361 1359
1362 1360 // Path + subpath
1363 1361 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1364 1362 PatternSyntax::RelPath,
1365 1363 b"dir/subdir/x",
1366 1364 Path::new(""),
1367 1365 )])
1368 1366 .unwrap();
1369 1367 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1370 1368 PatternSyntax::RelPath,
1371 1369 b"dir/subdir",
1372 1370 Path::new(""),
1373 1371 )])
1374 1372 .unwrap();
1375 1373 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1376 1374
1377 1375 let mut set = HashSet::new();
1378 1376 set.insert(HgPathBuf::from_bytes(b"dir"));
1379 1377 assert_eq!(
1380 1378 matcher.visit_children_set(HgPath::new(b"")),
1381 1379 VisitChildrenSet::Set(set)
1382 1380 );
1383 1381 let mut set = HashSet::new();
1384 1382 set.insert(HgPathBuf::from_bytes(b"subdir"));
1385 1383 assert_eq!(
1386 1384 matcher.visit_children_set(HgPath::new(b"dir")),
1387 1385 VisitChildrenSet::Set(set)
1388 1386 );
1389 1387
1390 1388 assert_eq!(
1391 1389 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1392 1390 VisitChildrenSet::Recursive
1393 1391 );
1394 1392 assert_eq!(
1395 1393 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1396 1394 VisitChildrenSet::Empty
1397 1395 );
1398 1396
1399 1397 assert_eq!(
1400 1398 matcher.visit_children_set(HgPath::new(b"folder")),
1401 1399 VisitChildrenSet::Empty
1402 1400 );
1403 1401 assert_eq!(
1404 1402 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1405 1403 VisitChildrenSet::Recursive
1406 1404 );
1407 1405 // OPT: this should probably be 'all' not 'this'.
1408 1406 assert_eq!(
1409 1407 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1410 1408 VisitChildrenSet::This
1411 1409 );
1412 1410 }
1413 1411
1414 1412 #[test]
1415 1413 fn test_intersectionmatcher() {
1416 1414 // Include path + Include rootfiles
1417 1415 let m1 = Box::new(
1418 1416 IncludeMatcher::new(vec![IgnorePattern::new(
1419 1417 PatternSyntax::RelPath,
1420 1418 b"dir/subdir",
1421 1419 Path::new(""),
1422 1420 )])
1423 1421 .unwrap(),
1424 1422 );
1425 1423 let m2 = Box::new(
1426 1424 IncludeMatcher::new(vec![IgnorePattern::new(
1427 1425 PatternSyntax::RootFiles,
1428 1426 b"dir",
1429 1427 Path::new(""),
1430 1428 )])
1431 1429 .unwrap(),
1432 1430 );
1433 1431 let matcher = IntersectionMatcher::new(m1, m2);
1434 1432
1435 1433 let mut set = HashSet::new();
1436 1434 set.insert(HgPathBuf::from_bytes(b"dir"));
1437 1435 assert_eq!(
1438 1436 matcher.visit_children_set(HgPath::new(b"")),
1439 1437 VisitChildrenSet::Set(set)
1440 1438 );
1441 1439 assert_eq!(
1442 1440 matcher.visit_children_set(HgPath::new(b"dir")),
1443 1441 VisitChildrenSet::This
1444 1442 );
1445 1443 assert_eq!(
1446 1444 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1447 1445 VisitChildrenSet::Empty
1448 1446 );
1449 1447 assert_eq!(
1450 1448 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1451 1449 VisitChildrenSet::Empty
1452 1450 );
1453 1451 assert_eq!(
1454 1452 matcher.visit_children_set(HgPath::new(b"folder")),
1455 1453 VisitChildrenSet::Empty
1456 1454 );
1457 1455 assert_eq!(
1458 1456 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1459 1457 VisitChildrenSet::Empty
1460 1458 );
1461 1459 assert_eq!(
1462 1460 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1463 1461 VisitChildrenSet::Empty
1464 1462 );
1465 1463
1466 1464 // Non intersecting paths
1467 1465 let m1 = Box::new(
1468 1466 IncludeMatcher::new(vec![IgnorePattern::new(
1469 1467 PatternSyntax::RelPath,
1470 1468 b"dir/subdir",
1471 1469 Path::new(""),
1472 1470 )])
1473 1471 .unwrap(),
1474 1472 );
1475 1473 let m2 = Box::new(
1476 1474 IncludeMatcher::new(vec![IgnorePattern::new(
1477 1475 PatternSyntax::RelPath,
1478 1476 b"folder",
1479 1477 Path::new(""),
1480 1478 )])
1481 1479 .unwrap(),
1482 1480 );
1483 1481 let matcher = IntersectionMatcher::new(m1, m2);
1484 1482
1485 1483 assert_eq!(
1486 1484 matcher.visit_children_set(HgPath::new(b"")),
1487 1485 VisitChildrenSet::Empty
1488 1486 );
1489 1487 assert_eq!(
1490 1488 matcher.visit_children_set(HgPath::new(b"dir")),
1491 1489 VisitChildrenSet::Empty
1492 1490 );
1493 1491 assert_eq!(
1494 1492 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1495 1493 VisitChildrenSet::Empty
1496 1494 );
1497 1495 assert_eq!(
1498 1496 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1499 1497 VisitChildrenSet::Empty
1500 1498 );
1501 1499 assert_eq!(
1502 1500 matcher.visit_children_set(HgPath::new(b"folder")),
1503 1501 VisitChildrenSet::Empty
1504 1502 );
1505 1503 assert_eq!(
1506 1504 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1507 1505 VisitChildrenSet::Empty
1508 1506 );
1509 1507 assert_eq!(
1510 1508 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1511 1509 VisitChildrenSet::Empty
1512 1510 );
1513 1511
1514 1512 // Nested paths
1515 1513 let m1 = Box::new(
1516 1514 IncludeMatcher::new(vec![IgnorePattern::new(
1517 1515 PatternSyntax::RelPath,
1518 1516 b"dir/subdir/x",
1519 1517 Path::new(""),
1520 1518 )])
1521 1519 .unwrap(),
1522 1520 );
1523 1521 let m2 = Box::new(
1524 1522 IncludeMatcher::new(vec![IgnorePattern::new(
1525 1523 PatternSyntax::RelPath,
1526 1524 b"dir/subdir",
1527 1525 Path::new(""),
1528 1526 )])
1529 1527 .unwrap(),
1530 1528 );
1531 1529 let matcher = IntersectionMatcher::new(m1, m2);
1532 1530
1533 1531 let mut set = HashSet::new();
1534 1532 set.insert(HgPathBuf::from_bytes(b"dir"));
1535 1533 assert_eq!(
1536 1534 matcher.visit_children_set(HgPath::new(b"")),
1537 1535 VisitChildrenSet::Set(set)
1538 1536 );
1539 1537
1540 1538 let mut set = HashSet::new();
1541 1539 set.insert(HgPathBuf::from_bytes(b"subdir"));
1542 1540 assert_eq!(
1543 1541 matcher.visit_children_set(HgPath::new(b"dir")),
1544 1542 VisitChildrenSet::Set(set)
1545 1543 );
1546 1544 let mut set = HashSet::new();
1547 1545 set.insert(HgPathBuf::from_bytes(b"x"));
1548 1546 assert_eq!(
1549 1547 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1550 1548 VisitChildrenSet::Set(set)
1551 1549 );
1552 1550 assert_eq!(
1553 1551 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1554 1552 VisitChildrenSet::Empty
1555 1553 );
1556 1554 assert_eq!(
1557 1555 matcher.visit_children_set(HgPath::new(b"folder")),
1558 1556 VisitChildrenSet::Empty
1559 1557 );
1560 1558 assert_eq!(
1561 1559 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1562 1560 VisitChildrenSet::Empty
1563 1561 );
1564 1562 // OPT: this should probably be 'all' not 'this'.
1565 1563 assert_eq!(
1566 1564 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1567 1565 VisitChildrenSet::This
1568 1566 );
1569 1567
1570 1568 // Diverging paths
1571 1569 let m1 = Box::new(
1572 1570 IncludeMatcher::new(vec![IgnorePattern::new(
1573 1571 PatternSyntax::RelPath,
1574 1572 b"dir/subdir/x",
1575 1573 Path::new(""),
1576 1574 )])
1577 1575 .unwrap(),
1578 1576 );
1579 1577 let m2 = Box::new(
1580 1578 IncludeMatcher::new(vec![IgnorePattern::new(
1581 1579 PatternSyntax::RelPath,
1582 1580 b"dir/subdir/z",
1583 1581 Path::new(""),
1584 1582 )])
1585 1583 .unwrap(),
1586 1584 );
1587 1585 let matcher = IntersectionMatcher::new(m1, m2);
1588 1586
1589 1587 // OPT: these next two could probably be Empty as well.
1590 1588 let mut set = HashSet::new();
1591 1589 set.insert(HgPathBuf::from_bytes(b"dir"));
1592 1590 assert_eq!(
1593 1591 matcher.visit_children_set(HgPath::new(b"")),
1594 1592 VisitChildrenSet::Set(set)
1595 1593 );
1596 1594 // OPT: these next two could probably be Empty as well.
1597 1595 let mut set = HashSet::new();
1598 1596 set.insert(HgPathBuf::from_bytes(b"subdir"));
1599 1597 assert_eq!(
1600 1598 matcher.visit_children_set(HgPath::new(b"dir")),
1601 1599 VisitChildrenSet::Set(set)
1602 1600 );
1603 1601 assert_eq!(
1604 1602 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1605 1603 VisitChildrenSet::Empty
1606 1604 );
1607 1605 assert_eq!(
1608 1606 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1609 1607 VisitChildrenSet::Empty
1610 1608 );
1611 1609 assert_eq!(
1612 1610 matcher.visit_children_set(HgPath::new(b"folder")),
1613 1611 VisitChildrenSet::Empty
1614 1612 );
1615 1613 assert_eq!(
1616 1614 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1617 1615 VisitChildrenSet::Empty
1618 1616 );
1619 1617 assert_eq!(
1620 1618 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1621 1619 VisitChildrenSet::Empty
1622 1620 );
1623 1621 }
1624 1622
1625 1623 #[test]
1626 1624 fn test_differencematcher() {
1627 1625 // Two alwaysmatchers should function like a nevermatcher
1628 1626 let m1 = AlwaysMatcher;
1629 1627 let m2 = AlwaysMatcher;
1630 1628 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
1631 1629
1632 1630 for case in &[
1633 1631 &b""[..],
1634 1632 b"dir",
1635 1633 b"dir/subdir",
1636 1634 b"dir/subdir/z",
1637 1635 b"dir/foo",
1638 1636 b"dir/subdir/x",
1639 1637 b"folder",
1640 1638 ] {
1641 1639 assert_eq!(
1642 1640 matcher.visit_children_set(HgPath::new(case)),
1643 1641 VisitChildrenSet::Empty
1644 1642 );
1645 1643 }
1646 1644
1647 1645 // One always and one never should behave the same as an always
1648 1646 let m1 = AlwaysMatcher;
1649 1647 let m2 = NeverMatcher;
1650 1648 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
1651 1649
1652 1650 for case in &[
1653 1651 &b""[..],
1654 1652 b"dir",
1655 1653 b"dir/subdir",
1656 1654 b"dir/subdir/z",
1657 1655 b"dir/foo",
1658 1656 b"dir/subdir/x",
1659 1657 b"folder",
1660 1658 ] {
1661 1659 assert_eq!(
1662 1660 matcher.visit_children_set(HgPath::new(case)),
1663 1661 VisitChildrenSet::Recursive
1664 1662 );
1665 1663 }
1666 1664
1667 1665 // Two include matchers
1668 1666 let m1 = Box::new(
1669 1667 IncludeMatcher::new(vec![IgnorePattern::new(
1670 1668 PatternSyntax::RelPath,
1671 1669 b"dir/subdir",
1672 1670 Path::new("/repo"),
1673 1671 )])
1674 1672 .unwrap(),
1675 1673 );
1676 1674 let m2 = Box::new(
1677 1675 IncludeMatcher::new(vec![IgnorePattern::new(
1678 1676 PatternSyntax::RootFiles,
1679 1677 b"dir",
1680 1678 Path::new("/repo"),
1681 1679 )])
1682 1680 .unwrap(),
1683 1681 );
1684 1682
1685 1683 let matcher = DifferenceMatcher::new(m1, m2);
1686 1684
1687 1685 let mut set = HashSet::new();
1688 1686 set.insert(HgPathBuf::from_bytes(b"dir"));
1689 1687 assert_eq!(
1690 1688 matcher.visit_children_set(HgPath::new(b"")),
1691 1689 VisitChildrenSet::Set(set)
1692 1690 );
1693 1691
1694 1692 let mut set = HashSet::new();
1695 1693 set.insert(HgPathBuf::from_bytes(b"subdir"));
1696 1694 assert_eq!(
1697 1695 matcher.visit_children_set(HgPath::new(b"dir")),
1698 1696 VisitChildrenSet::Set(set)
1699 1697 );
1700 1698 assert_eq!(
1701 1699 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1702 1700 VisitChildrenSet::Recursive
1703 1701 );
1704 1702 assert_eq!(
1705 1703 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1706 1704 VisitChildrenSet::Empty
1707 1705 );
1708 1706 assert_eq!(
1709 1707 matcher.visit_children_set(HgPath::new(b"folder")),
1710 1708 VisitChildrenSet::Empty
1711 1709 );
1712 1710 assert_eq!(
1713 1711 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1714 1712 VisitChildrenSet::This
1715 1713 );
1716 1714 assert_eq!(
1717 1715 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1718 1716 VisitChildrenSet::This
1719 1717 );
1720 1718 }
1721 1719 }
@@ -1,25 +1,25 b''
1 1 [package]
2 2 name = "rhg"
3 3 version = "0.1.0"
4 4 authors = [
5 5 "Antoine Cezar <antoine.cezar@octobus.net>",
6 6 "Raphaël Gomès <raphael.gomes@octobus.net>",
7 7 ]
8 8 edition = "2021"
9 9
10 10 [dependencies]
11 11 atty = "0.2.14"
12 12 hg-core = { path = "../hg-core"}
13 13 chrono = "0.4.23"
14 14 clap = { version = "4.0.24", features = ["cargo"] }
15 15 derive_more = "0.99.17"
16 16 home = "0.5.4"
17 17 lazy_static = "1.4.0"
18 18 log = "0.4.17"
19 micro-timer = "0.4.0"
19 logging_timer = "1.1.0"
20 20 regex = "1.7.0"
21 21 env_logger = "0.9.3"
22 22 format-bytes = "0.3.0"
23 23 users = "0.11.0"
24 24 which = "4.3.0"
25 25 rayon = "1.5.3"
@@ -1,118 +1,117 b''
1 1 use crate::error::CommandError;
2 2 use clap::Arg;
3 3 use format_bytes::format_bytes;
4 4 use hg::operations::cat;
5 5 use hg::utils::hg_path::HgPathBuf;
6 use micro_timer::timed;
7 6 use std::ffi::OsString;
8 7 use std::os::unix::prelude::OsStrExt;
9 8
10 9 pub const HELP_TEXT: &str = "
11 10 Output the current or given revision of files
12 11 ";
13 12
14 13 pub fn args() -> clap::Command {
15 14 clap::command!("cat")
16 15 .arg(
17 16 Arg::new("rev")
18 17 .help("search the repository as it is in REV")
19 18 .short('r')
20 19 .long("rev")
21 20 .value_name("REV"),
22 21 )
23 22 .arg(
24 23 clap::Arg::new("files")
25 24 .required(true)
26 25 .num_args(1..)
27 26 .value_name("FILE")
28 27 .value_parser(clap::value_parser!(std::ffi::OsString))
29 28 .help("Files to output"),
30 29 )
31 30 .about(HELP_TEXT)
32 31 }
33 32
34 #[timed]
33 #[logging_timer::time("trace")]
35 34 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
36 35 let cat_enabled_default = true;
37 36 let cat_enabled = invocation.config.get_option(b"rhg", b"cat")?;
38 37 if !cat_enabled.unwrap_or(cat_enabled_default) {
39 38 return Err(CommandError::unsupported(
40 39 "cat is disabled in rhg (enable it with 'rhg.cat = true' \
41 40 or enable fallback with 'rhg.on-unsupported = fallback')",
42 41 ));
43 42 }
44 43
45 44 let rev = invocation.subcommand_args.get_one::<String>("rev");
46 45 let file_args =
47 46 match invocation.subcommand_args.get_many::<OsString>("files") {
48 47 Some(files) => files
49 48 .filter(|s| !s.is_empty())
50 49 .map(|s| s.as_os_str())
51 50 .collect(),
52 51 None => vec![],
53 52 };
54 53
55 54 let repo = invocation.repo?;
56 55 let cwd = hg::utils::current_dir()?;
57 56 let working_directory = repo.working_directory_path();
58 57 let working_directory = cwd.join(working_directory); // Make it absolute
59 58
60 59 let mut files = vec![];
61 60 for file in file_args {
62 61 if file.as_bytes().starts_with(b"set:") {
63 62 let message = "fileset";
64 63 return Err(CommandError::unsupported(message));
65 64 }
66 65
67 66 let normalized = cwd.join(&file);
68 67 // TODO: actually normalize `..` path segments etc?
69 68 let dotted = normalized.components().any(|c| c.as_os_str() == "..");
70 69 if file.as_bytes() == b"." || dotted {
71 70 let message = "`..` or `.` path segment";
72 71 return Err(CommandError::unsupported(message));
73 72 }
74 73 let relative_path = working_directory
75 74 .strip_prefix(&cwd)
76 75 .unwrap_or(&working_directory);
77 76 let stripped = normalized
78 77 .strip_prefix(&working_directory)
79 78 .map_err(|_| {
80 79 CommandError::abort(format!(
81 80 "abort: {} not under root '{}'\n(consider using '--cwd {}')",
82 81 String::from_utf8_lossy(file.as_bytes()),
83 82 working_directory.display(),
84 83 relative_path.display(),
85 84 ))
86 85 })?;
87 86 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
88 87 .map_err(|e| CommandError::abort(e.to_string()))?;
89 88 files.push(hg_file);
90 89 }
91 90 let files = files.iter().map(|file| file.as_ref()).collect();
92 91 // TODO probably move this to a util function like `repo.default_rev` or
93 92 // something when it's used somewhere else
94 93 let rev = match rev {
95 94 Some(r) => r.to_string(),
96 95 None => format!("{:x}", repo.dirstate_parents()?.p1),
97 96 };
98 97
99 98 let output = cat(&repo, &rev, files).map_err(|e| (e, rev.as_str()))?;
100 99 for (_file, contents) in output.results {
101 100 invocation.ui.write_stdout(&contents)?;
102 101 }
103 102 if !output.missing.is_empty() {
104 103 let short = format!("{:x}", output.node.short()).into_bytes();
105 104 for path in &output.missing {
106 105 invocation.ui.write_stderr(&format_bytes!(
107 106 b"{}: no such file in rev {}\n",
108 107 path.as_bytes(),
109 108 short
110 109 ))?;
111 110 }
112 111 }
113 112 if output.found_any {
114 113 Ok(())
115 114 } else {
116 115 Err(CommandError::Unsuccessful)
117 116 }
118 117 }
@@ -1,72 +1,71 b''
1 1 use crate::error::CommandError;
2 2 use clap::Arg;
3 3 use clap::ArgGroup;
4 4 use hg::operations::{debug_data, DebugDataKind};
5 use micro_timer::timed;
6 5
7 6 pub const HELP_TEXT: &str = "
8 7 Dump the contents of a data file revision
9 8 ";
10 9
11 10 pub fn args() -> clap::Command {
12 11 clap::command!("debugdata")
13 12 .arg(
14 13 Arg::new("changelog")
15 14 .help("open changelog")
16 15 .short('c')
17 16 .action(clap::ArgAction::SetTrue),
18 17 )
19 18 .arg(
20 19 Arg::new("manifest")
21 20 .help("open manifest")
22 21 .short('m')
23 22 .action(clap::ArgAction::SetTrue),
24 23 )
25 24 .group(
26 25 ArgGroup::new("revlog")
27 26 .args(&["changelog", "manifest"])
28 27 .required(true),
29 28 )
30 29 .arg(
31 30 Arg::new("rev")
32 31 .help("revision")
33 32 .required(true)
34 33 .value_name("REV"),
35 34 )
36 35 .about(HELP_TEXT)
37 36 }
38 37
39 #[timed]
38 #[logging_timer::time("trace")]
40 39 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
41 40 let args = invocation.subcommand_args;
42 41 let rev = args
43 42 .get_one::<String>("rev")
44 43 .expect("rev should be a required argument");
45 44 let kind = match (
46 45 args.get_one::<bool>("changelog").unwrap(),
47 46 args.get_one::<bool>("manifest").unwrap(),
48 47 ) {
49 48 (true, false) => DebugDataKind::Changelog,
50 49 (false, true) => DebugDataKind::Manifest,
51 50 (true, true) => {
52 51 unreachable!("Should not happen since options are exclusive")
53 52 }
54 53 (false, false) => {
55 54 unreachable!("Should not happen since options are required")
56 55 }
57 56 };
58 57
59 58 let repo = invocation.repo?;
60 59 if repo.has_narrow() {
61 60 return Err(CommandError::unsupported(
62 61 "support for ellipsis nodes is missing and repo has narrow enabled",
63 62 ));
64 63 }
65 64 let data = debug_data(repo, rev, kind).map_err(|e| (e, rev.as_ref()))?;
66 65
67 66 let mut stdout = invocation.ui.stdout_buffer();
68 67 stdout.write_all(&data)?;
69 68 stdout.flush()?;
70 69
71 70 Ok(())
72 71 }
General Comments 0
You need to be logged in to leave comments. Login now