##// END OF EJS Templates
rust: fix unsound `OwningDirstateMap`...
Raphaël Gomès -
r50249:dd6b67d5 stable
parent child Browse files
Show More
@@ -1,1153 +1,1213 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 name = "Inflector"
7 version = "0.11.4"
8 source = "registry+https://github.com/rust-lang/crates.io-index"
9 checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
10
11 [[package]]
6 12 name = "adler"
7 13 version = "0.2.3"
8 14 source = "registry+https://github.com/rust-lang/crates.io-index"
9 15 checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
10 16
11 17 [[package]]
12 18 name = "aho-corasick"
13 19 version = "0.7.15"
14 20 source = "registry+https://github.com/rust-lang/crates.io-index"
15 21 checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
16 22 dependencies = [
17 23 "memchr",
18 24 ]
19 25
20 26 [[package]]
27 name = "aliasable"
28 version = "0.1.3"
29 source = "registry+https://github.com/rust-lang/crates.io-index"
30 checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
31
32 [[package]]
21 33 name = "ansi_term"
22 34 version = "0.11.0"
23 35 source = "registry+https://github.com/rust-lang/crates.io-index"
24 36 checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
25 37 dependencies = [
26 38 "winapi",
27 39 ]
28 40
29 41 [[package]]
30 42 name = "atty"
31 43 version = "0.2.14"
32 44 source = "registry+https://github.com/rust-lang/crates.io-index"
33 45 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
34 46 dependencies = [
35 47 "hermit-abi",
36 48 "libc",
37 49 "winapi",
38 50 ]
39 51
40 52 [[package]]
41 53 name = "autocfg"
42 54 version = "1.0.1"
43 55 source = "registry+https://github.com/rust-lang/crates.io-index"
44 56 checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
45 57
46 58 [[package]]
47 59 name = "bitflags"
48 60 version = "1.2.1"
49 61 source = "registry+https://github.com/rust-lang/crates.io-index"
50 62 checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
51 63
52 64 [[package]]
53 65 name = "bitmaps"
54 66 version = "2.1.0"
55 67 source = "registry+https://github.com/rust-lang/crates.io-index"
56 68 checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
57 69 dependencies = [
58 70 "typenum",
59 71 ]
60 72
61 73 [[package]]
62 74 name = "block-buffer"
63 75 version = "0.9.0"
64 76 source = "registry+https://github.com/rust-lang/crates.io-index"
65 77 checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
66 78 dependencies = [
67 79 "generic-array",
68 80 ]
69 81
70 82 [[package]]
71 83 name = "byteorder"
72 84 version = "1.3.4"
73 85 source = "registry+https://github.com/rust-lang/crates.io-index"
74 86 checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
75 87
76 88 [[package]]
77 89 name = "bytes-cast"
78 90 version = "0.2.0"
79 91 source = "registry+https://github.com/rust-lang/crates.io-index"
80 92 checksum = "0d434f9a4ecbe987e7ccfda7274b6f82ea52c9b63742565a65cb5e8ba0f2c452"
81 93 dependencies = [
82 94 "bytes-cast-derive",
83 95 ]
84 96
85 97 [[package]]
86 98 name = "bytes-cast-derive"
87 99 version = "0.1.0"
88 100 source = "registry+https://github.com/rust-lang/crates.io-index"
89 101 checksum = "cb936af9de38476664d6b58e529aff30d482e4ce1c5e150293d00730b0d81fdb"
90 102 dependencies = [
91 103 "proc-macro2",
92 104 "quote",
93 105 "syn",
94 106 ]
95 107
96 108 [[package]]
97 109 name = "cc"
98 110 version = "1.0.66"
99 111 source = "registry+https://github.com/rust-lang/crates.io-index"
100 112 checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
101 113 dependencies = [
102 114 "jobserver",
103 115 ]
104 116
105 117 [[package]]
106 118 name = "cfg-if"
107 119 version = "0.1.10"
108 120 source = "registry+https://github.com/rust-lang/crates.io-index"
109 121 checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
110 122
111 123 [[package]]
112 124 name = "cfg-if"
113 125 version = "1.0.0"
114 126 source = "registry+https://github.com/rust-lang/crates.io-index"
115 127 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
116 128
117 129 [[package]]
118 130 name = "chrono"
119 131 version = "0.4.19"
120 132 source = "registry+https://github.com/rust-lang/crates.io-index"
121 133 checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
122 134 dependencies = [
123 135 "libc",
124 136 "num-integer",
125 137 "num-traits",
126 138 "time",
127 139 "winapi",
128 140 ]
129 141
130 142 [[package]]
131 143 name = "clap"
132 144 version = "2.33.3"
133 145 source = "registry+https://github.com/rust-lang/crates.io-index"
134 146 checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
135 147 dependencies = [
136 148 "ansi_term",
137 149 "atty",
138 150 "bitflags",
139 151 "strsim",
140 152 "textwrap",
141 153 "unicode-width",
142 154 "vec_map",
143 155 ]
144 156
145 157 [[package]]
146 158 name = "const_fn"
147 159 version = "0.4.4"
148 160 source = "registry+https://github.com/rust-lang/crates.io-index"
149 161 checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
150 162
151 163 [[package]]
152 164 name = "cpufeatures"
153 165 version = "0.1.4"
154 166 source = "registry+https://github.com/rust-lang/crates.io-index"
155 167 checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
156 168 dependencies = [
157 169 "libc",
158 170 ]
159 171
160 172 [[package]]
161 173 name = "cpython"
162 174 version = "0.7.0"
163 175 source = "registry+https://github.com/rust-lang/crates.io-index"
164 176 checksum = "b7d46ba8ace7f3a1d204ac5060a706d0a68de6b42eafb6a586cc08bebcffe664"
165 177 dependencies = [
166 178 "libc",
167 179 "num-traits",
168 180 "paste",
169 181 "python27-sys",
170 182 "python3-sys",
171 183 ]
172 184
173 185 [[package]]
174 186 name = "crc32fast"
175 187 version = "1.2.1"
176 188 source = "registry+https://github.com/rust-lang/crates.io-index"
177 189 checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
178 190 dependencies = [
179 191 "cfg-if 1.0.0",
180 192 ]
181 193
182 194 [[package]]
183 195 name = "crossbeam-channel"
184 196 version = "0.4.4"
185 197 source = "registry+https://github.com/rust-lang/crates.io-index"
186 198 checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
187 199 dependencies = [
188 200 "crossbeam-utils 0.7.2",
189 201 "maybe-uninit",
190 202 ]
191 203
192 204 [[package]]
193 205 name = "crossbeam-channel"
194 206 version = "0.5.0"
195 207 source = "registry+https://github.com/rust-lang/crates.io-index"
196 208 checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
197 209 dependencies = [
198 210 "cfg-if 1.0.0",
199 211 "crossbeam-utils 0.8.1",
200 212 ]
201 213
202 214 [[package]]
203 215 name = "crossbeam-deque"
204 216 version = "0.8.0"
205 217 source = "registry+https://github.com/rust-lang/crates.io-index"
206 218 checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
207 219 dependencies = [
208 220 "cfg-if 1.0.0",
209 221 "crossbeam-epoch",
210 222 "crossbeam-utils 0.8.1",
211 223 ]
212 224
213 225 [[package]]
214 226 name = "crossbeam-epoch"
215 227 version = "0.9.1"
216 228 source = "registry+https://github.com/rust-lang/crates.io-index"
217 229 checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
218 230 dependencies = [
219 231 "cfg-if 1.0.0",
220 232 "const_fn",
221 233 "crossbeam-utils 0.8.1",
222 234 "lazy_static",
223 235 "memoffset",
224 236 "scopeguard",
225 237 ]
226 238
227 239 [[package]]
228 240 name = "crossbeam-utils"
229 241 version = "0.7.2"
230 242 source = "registry+https://github.com/rust-lang/crates.io-index"
231 243 checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
232 244 dependencies = [
233 245 "autocfg",
234 246 "cfg-if 0.1.10",
235 247 "lazy_static",
236 248 ]
237 249
238 250 [[package]]
239 251 name = "crossbeam-utils"
240 252 version = "0.8.1"
241 253 source = "registry+https://github.com/rust-lang/crates.io-index"
242 254 checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
243 255 dependencies = [
244 256 "autocfg",
245 257 "cfg-if 1.0.0",
246 258 "lazy_static",
247 259 ]
248 260
249 261 [[package]]
250 262 name = "ctor"
251 263 version = "0.1.16"
252 264 source = "registry+https://github.com/rust-lang/crates.io-index"
253 265 checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484"
254 266 dependencies = [
255 267 "quote",
256 268 "syn",
257 269 ]
258 270
259 271 [[package]]
260 272 name = "derive_more"
261 273 version = "0.99.11"
262 274 source = "registry+https://github.com/rust-lang/crates.io-index"
263 275 checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
264 276 dependencies = [
265 277 "proc-macro2",
266 278 "quote",
267 279 "syn",
268 280 ]
269 281
270 282 [[package]]
271 283 name = "difference"
272 284 version = "2.0.0"
273 285 source = "registry+https://github.com/rust-lang/crates.io-index"
274 286 checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
275 287
276 288 [[package]]
277 289 name = "digest"
278 290 version = "0.9.0"
279 291 source = "registry+https://github.com/rust-lang/crates.io-index"
280 292 checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
281 293 dependencies = [
282 294 "generic-array",
283 295 ]
284 296
285 297 [[package]]
286 298 name = "either"
287 299 version = "1.6.1"
288 300 source = "registry+https://github.com/rust-lang/crates.io-index"
289 301 checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
290 302
291 303 [[package]]
292 304 name = "env_logger"
293 305 version = "0.7.1"
294 306 source = "registry+https://github.com/rust-lang/crates.io-index"
295 307 checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
296 308 dependencies = [
297 309 "atty",
298 310 "humantime",
299 311 "log",
300 312 "regex",
301 313 "termcolor",
302 314 ]
303 315
304 316 [[package]]
305 317 name = "flate2"
306 318 version = "1.0.19"
307 319 source = "registry+https://github.com/rust-lang/crates.io-index"
308 320 checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
309 321 dependencies = [
310 322 "cfg-if 1.0.0",
311 323 "crc32fast",
312 324 "libc",
313 325 "libz-sys",
314 326 "miniz_oxide",
315 327 ]
316 328
317 329 [[package]]
318 330 name = "format-bytes"
319 331 version = "0.3.0"
320 332 source = "registry+https://github.com/rust-lang/crates.io-index"
321 333 checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd"
322 334 dependencies = [
323 335 "format-bytes-macros",
324 336 ]
325 337
326 338 [[package]]
327 339 name = "format-bytes-macros"
328 340 version = "0.4.0"
329 341 source = "registry+https://github.com/rust-lang/crates.io-index"
330 342 checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f"
331 343 dependencies = [
332 344 "proc-macro2",
333 345 "quote",
334 346 "syn",
335 347 ]
336 348
337 349 [[package]]
338 350 name = "generic-array"
339 351 version = "0.14.4"
340 352 source = "registry+https://github.com/rust-lang/crates.io-index"
341 353 checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
342 354 dependencies = [
343 355 "typenum",
344 356 "version_check",
345 357 ]
346 358
347 359 [[package]]
348 360 name = "getrandom"
349 361 version = "0.1.15"
350 362 source = "registry+https://github.com/rust-lang/crates.io-index"
351 363 checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
352 364 dependencies = [
353 365 "cfg-if 0.1.10",
354 366 "libc",
355 367 "wasi 0.9.0+wasi-snapshot-preview1",
356 368 ]
357 369
358 370 [[package]]
359 371 name = "getrandom"
360 372 version = "0.2.4"
361 373 source = "registry+https://github.com/rust-lang/crates.io-index"
362 374 checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
363 375 dependencies = [
364 376 "cfg-if 1.0.0",
365 377 "libc",
366 378 "wasi 0.10.0+wasi-snapshot-preview1",
367 379 ]
368 380
369 381 [[package]]
370 382 name = "glob"
371 383 version = "0.3.0"
372 384 source = "registry+https://github.com/rust-lang/crates.io-index"
373 385 checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
374 386
375 387 [[package]]
376 388 name = "hermit-abi"
377 389 version = "0.1.17"
378 390 source = "registry+https://github.com/rust-lang/crates.io-index"
379 391 checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
380 392 dependencies = [
381 393 "libc",
382 394 ]
383 395
384 396 [[package]]
385 397 name = "hex"
386 398 version = "0.4.3"
387 399 source = "registry+https://github.com/rust-lang/crates.io-index"
388 400 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
389 401
390 402 [[package]]
391 403 name = "hg-core"
392 404 version = "0.1.0"
393 405 dependencies = [
394 406 "bitflags",
395 407 "byteorder",
396 408 "bytes-cast",
397 409 "clap",
398 410 "crossbeam-channel 0.4.4",
399 411 "derive_more",
400 412 "flate2",
401 413 "format-bytes",
402 414 "home",
403 415 "im-rc",
404 416 "itertools",
405 417 "lazy_static",
406 418 "libc",
407 419 "log",
408 420 "memmap2",
409 421 "micro-timer",
422 "ouroboros",
410 423 "pretty_assertions",
411 424 "rand 0.8.4",
412 425 "rand_distr",
413 426 "rand_pcg",
414 427 "rayon",
415 428 "regex",
416 429 "same-file",
417 430 "sha-1",
418 "stable_deref_trait",
419 431 "tempfile",
420 432 "twox-hash",
421 433 "zstd",
422 434 ]
423 435
424 436 [[package]]
425 437 name = "hg-cpython"
426 438 version = "0.1.0"
427 439 dependencies = [
428 440 "cpython",
429 441 "crossbeam-channel 0.4.4",
430 442 "env_logger",
431 443 "hg-core",
432 444 "libc",
433 445 "log",
434 446 "stable_deref_trait",
435 447 "vcsgraph",
436 448 ]
437 449
438 450 [[package]]
439 451 name = "home"
440 452 version = "0.5.3"
441 453 source = "registry+https://github.com/rust-lang/crates.io-index"
442 454 checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
443 455 dependencies = [
444 456 "winapi",
445 457 ]
446 458
447 459 [[package]]
448 460 name = "humantime"
449 461 version = "1.3.0"
450 462 source = "registry+https://github.com/rust-lang/crates.io-index"
451 463 checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
452 464 dependencies = [
453 465 "quick-error",
454 466 ]
455 467
456 468 [[package]]
457 469 name = "im-rc"
458 470 version = "15.0.0"
459 471 source = "registry+https://github.com/rust-lang/crates.io-index"
460 472 checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f"
461 473 dependencies = [
462 474 "bitmaps",
463 475 "rand_core 0.5.1",
464 476 "rand_xoshiro",
465 477 "sized-chunks",
466 478 "typenum",
467 479 "version_check",
468 480 ]
469 481
470 482 [[package]]
471 483 name = "itertools"
472 484 version = "0.9.0"
473 485 source = "registry+https://github.com/rust-lang/crates.io-index"
474 486 checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
475 487 dependencies = [
476 488 "either",
477 489 ]
478 490
479 491 [[package]]
480 492 name = "jobserver"
481 493 version = "0.1.21"
482 494 source = "registry+https://github.com/rust-lang/crates.io-index"
483 495 checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
484 496 dependencies = [
485 497 "libc",
486 498 ]
487 499
488 500 [[package]]
489 501 name = "lazy_static"
490 502 version = "1.4.0"
491 503 source = "registry+https://github.com/rust-lang/crates.io-index"
492 504 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
493 505
494 506 [[package]]
495 507 name = "libc"
496 508 version = "0.2.81"
497 509 source = "registry+https://github.com/rust-lang/crates.io-index"
498 510 checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
499 511
500 512 [[package]]
501 513 name = "libm"
502 514 version = "0.2.1"
503 515 source = "registry+https://github.com/rust-lang/crates.io-index"
504 516 checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
505 517
506 518 [[package]]
507 519 name = "libz-sys"
508 520 version = "1.1.2"
509 521 source = "registry+https://github.com/rust-lang/crates.io-index"
510 522 checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655"
511 523 dependencies = [
512 524 "cc",
513 525 "pkg-config",
514 526 "vcpkg",
515 527 ]
516 528
517 529 [[package]]
518 530 name = "log"
519 531 version = "0.4.11"
520 532 source = "registry+https://github.com/rust-lang/crates.io-index"
521 533 checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
522 534 dependencies = [
523 535 "cfg-if 0.1.10",
524 536 ]
525 537
526 538 [[package]]
527 539 name = "maybe-uninit"
528 540 version = "2.0.0"
529 541 source = "registry+https://github.com/rust-lang/crates.io-index"
530 542 checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
531 543
532 544 [[package]]
533 545 name = "memchr"
534 546 version = "2.3.4"
535 547 source = "registry+https://github.com/rust-lang/crates.io-index"
536 548 checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
537 549
538 550 [[package]]
539 551 name = "memmap2"
540 552 version = "0.4.0"
541 553 source = "registry+https://github.com/rust-lang/crates.io-index"
542 554 checksum = "de5d3112c080d58ce560081baeaab7e1e864ca21795ddbf533d5b1842bb1ecf8"
543 555 dependencies = [
544 556 "libc",
545 557 "stable_deref_trait",
546 558 ]
547 559
548 560 [[package]]
549 561 name = "memoffset"
550 562 version = "0.6.1"
551 563 source = "registry+https://github.com/rust-lang/crates.io-index"
552 564 checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
553 565 dependencies = [
554 566 "autocfg",
555 567 ]
556 568
557 569 [[package]]
558 570 name = "micro-timer"
559 571 version = "0.3.1"
560 572 source = "registry+https://github.com/rust-lang/crates.io-index"
561 573 checksum = "2620153e1d903d26b72b89f0e9c48d8c4756cba941c185461dddc234980c298c"
562 574 dependencies = [
563 575 "micro-timer-macros",
564 576 "scopeguard",
565 577 ]
566 578
567 579 [[package]]
568 580 name = "micro-timer-macros"
569 581 version = "0.3.1"
570 582 source = "registry+https://github.com/rust-lang/crates.io-index"
571 583 checksum = "e28a3473e6abd6e9aab36aaeef32ad22ae0bd34e79f376643594c2b152ec1c5d"
572 584 dependencies = [
573 585 "proc-macro2",
574 586 "quote",
575 587 "scopeguard",
576 588 "syn",
577 589 ]
578 590
579 591 [[package]]
580 592 name = "miniz_oxide"
581 593 version = "0.4.3"
582 594 source = "registry+https://github.com/rust-lang/crates.io-index"
583 595 checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
584 596 dependencies = [
585 597 "adler",
586 598 "autocfg",
587 599 ]
588 600
589 601 [[package]]
590 602 name = "num-integer"
591 603 version = "0.1.44"
592 604 source = "registry+https://github.com/rust-lang/crates.io-index"
593 605 checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
594 606 dependencies = [
595 607 "autocfg",
596 608 "num-traits",
597 609 ]
598 610
599 611 [[package]]
600 612 name = "num-traits"
601 613 version = "0.2.14"
602 614 source = "registry+https://github.com/rust-lang/crates.io-index"
603 615 checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
604 616 dependencies = [
605 617 "autocfg",
606 618 "libm",
607 619 ]
608 620
609 621 [[package]]
610 622 name = "num_cpus"
611 623 version = "1.13.0"
612 624 source = "registry+https://github.com/rust-lang/crates.io-index"
613 625 checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
614 626 dependencies = [
615 627 "hermit-abi",
616 628 "libc",
617 629 ]
618 630
619 631 [[package]]
620 632 name = "opaque-debug"
621 633 version = "0.3.0"
622 634 source = "registry+https://github.com/rust-lang/crates.io-index"
623 635 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
624 636
625 637 [[package]]
638 name = "ouroboros"
639 version = "0.15.0"
640 source = "registry+https://github.com/rust-lang/crates.io-index"
641 checksum = "9f31a3b678685b150cba82b702dcdc5e155893f63610cf388d30cd988d4ca2bf"
642 dependencies = [
643 "aliasable",
644 "ouroboros_macro",
645 "stable_deref_trait",
646 ]
647
648 [[package]]
649 name = "ouroboros_macro"
650 version = "0.15.0"
651 source = "registry+https://github.com/rust-lang/crates.io-index"
652 checksum = "084fd65d5dd8b3772edccb5ffd1e4b7eba43897ecd0f9401e330e8c542959408"
653 dependencies = [
654 "Inflector",
655 "proc-macro-error",
656 "proc-macro2",
657 "quote",
658 "syn",
659 ]
660
661 [[package]]
626 662 name = "output_vt100"
627 663 version = "0.1.2"
628 664 source = "registry+https://github.com/rust-lang/crates.io-index"
629 665 checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
630 666 dependencies = [
631 667 "winapi",
632 668 ]
633 669
634 670 [[package]]
635 671 name = "paste"
636 672 version = "1.0.5"
637 673 source = "registry+https://github.com/rust-lang/crates.io-index"
638 674 checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
639 675
640 676 [[package]]
641 677 name = "pkg-config"
642 678 version = "0.3.19"
643 679 source = "registry+https://github.com/rust-lang/crates.io-index"
644 680 checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
645 681
646 682 [[package]]
647 683 name = "ppv-lite86"
648 684 version = "0.2.10"
649 685 source = "registry+https://github.com/rust-lang/crates.io-index"
650 686 checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
651 687
652 688 [[package]]
653 689 name = "pretty_assertions"
654 690 version = "0.6.1"
655 691 source = "registry+https://github.com/rust-lang/crates.io-index"
656 692 checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
657 693 dependencies = [
658 694 "ansi_term",
659 695 "ctor",
660 696 "difference",
661 697 "output_vt100",
662 698 ]
663 699
664 700 [[package]]
701 name = "proc-macro-error"
702 version = "1.0.4"
703 source = "registry+https://github.com/rust-lang/crates.io-index"
704 checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
705 dependencies = [
706 "proc-macro-error-attr",
707 "proc-macro2",
708 "quote",
709 "syn",
710 "version_check",
711 ]
712
713 [[package]]
714 name = "proc-macro-error-attr"
715 version = "1.0.4"
716 source = "registry+https://github.com/rust-lang/crates.io-index"
717 checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
718 dependencies = [
719 "proc-macro2",
720 "quote",
721 "version_check",
722 ]
723
724 [[package]]
665 725 name = "proc-macro2"
666 726 version = "1.0.24"
667 727 source = "registry+https://github.com/rust-lang/crates.io-index"
668 728 checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
669 729 dependencies = [
670 730 "unicode-xid",
671 731 ]
672 732
673 733 [[package]]
674 734 name = "python27-sys"
675 735 version = "0.7.0"
676 736 source = "registry+https://github.com/rust-lang/crates.io-index"
677 737 checksum = "94670354e264300dde81a5864cbb6bfc9d56ac3dcf3a278c32cb52f816f4dfd1"
678 738 dependencies = [
679 739 "libc",
680 740 "regex",
681 741 ]
682 742
683 743 [[package]]
684 744 name = "python3-sys"
685 745 version = "0.7.0"
686 746 source = "registry+https://github.com/rust-lang/crates.io-index"
687 747 checksum = "b18b32e64c103d5045f44644d7ddddd65336f7a0521f6fde673240a9ecceb77e"
688 748 dependencies = [
689 749 "libc",
690 750 "regex",
691 751 ]
692 752
693 753 [[package]]
694 754 name = "quick-error"
695 755 version = "1.2.3"
696 756 source = "registry+https://github.com/rust-lang/crates.io-index"
697 757 checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
698 758
699 759 [[package]]
700 760 name = "quote"
701 761 version = "1.0.7"
702 762 source = "registry+https://github.com/rust-lang/crates.io-index"
703 763 checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
704 764 dependencies = [
705 765 "proc-macro2",
706 766 ]
707 767
708 768 [[package]]
709 769 name = "rand"
710 770 version = "0.7.3"
711 771 source = "registry+https://github.com/rust-lang/crates.io-index"
712 772 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
713 773 dependencies = [
714 774 "getrandom 0.1.15",
715 775 "libc",
716 776 "rand_chacha 0.2.2",
717 777 "rand_core 0.5.1",
718 778 "rand_hc 0.2.0",
719 779 ]
720 780
721 781 [[package]]
722 782 name = "rand"
723 783 version = "0.8.4"
724 784 source = "registry+https://github.com/rust-lang/crates.io-index"
725 785 checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
726 786 dependencies = [
727 787 "libc",
728 788 "rand_chacha 0.3.1",
729 789 "rand_core 0.6.3",
730 790 "rand_hc 0.3.1",
731 791 ]
732 792
733 793 [[package]]
734 794 name = "rand_chacha"
735 795 version = "0.2.2"
736 796 source = "registry+https://github.com/rust-lang/crates.io-index"
737 797 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
738 798 dependencies = [
739 799 "ppv-lite86",
740 800 "rand_core 0.5.1",
741 801 ]
742 802
743 803 [[package]]
744 804 name = "rand_chacha"
745 805 version = "0.3.1"
746 806 source = "registry+https://github.com/rust-lang/crates.io-index"
747 807 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
748 808 dependencies = [
749 809 "ppv-lite86",
750 810 "rand_core 0.6.3",
751 811 ]
752 812
753 813 [[package]]
754 814 name = "rand_core"
755 815 version = "0.5.1"
756 816 source = "registry+https://github.com/rust-lang/crates.io-index"
757 817 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
758 818 dependencies = [
759 819 "getrandom 0.1.15",
760 820 ]
761 821
762 822 [[package]]
763 823 name = "rand_core"
764 824 version = "0.6.3"
765 825 source = "registry+https://github.com/rust-lang/crates.io-index"
766 826 checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
767 827 dependencies = [
768 828 "getrandom 0.2.4",
769 829 ]
770 830
771 831 [[package]]
772 832 name = "rand_distr"
773 833 version = "0.4.2"
774 834 source = "registry+https://github.com/rust-lang/crates.io-index"
775 835 checksum = "964d548f8e7d12e102ef183a0de7e98180c9f8729f555897a857b96e48122d2f"
776 836 dependencies = [
777 837 "num-traits",
778 838 "rand 0.8.4",
779 839 ]
780 840
781 841 [[package]]
782 842 name = "rand_hc"
783 843 version = "0.2.0"
784 844 source = "registry+https://github.com/rust-lang/crates.io-index"
785 845 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
786 846 dependencies = [
787 847 "rand_core 0.5.1",
788 848 ]
789 849
790 850 [[package]]
791 851 name = "rand_hc"
792 852 version = "0.3.1"
793 853 source = "registry+https://github.com/rust-lang/crates.io-index"
794 854 checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
795 855 dependencies = [
796 856 "rand_core 0.6.3",
797 857 ]
798 858
799 859 [[package]]
800 860 name = "rand_pcg"
801 861 version = "0.3.1"
802 862 source = "registry+https://github.com/rust-lang/crates.io-index"
803 863 checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
804 864 dependencies = [
805 865 "rand_core 0.6.3",
806 866 ]
807 867
808 868 [[package]]
809 869 name = "rand_xoshiro"
810 870 version = "0.4.0"
811 871 source = "registry+https://github.com/rust-lang/crates.io-index"
812 872 checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
813 873 dependencies = [
814 874 "rand_core 0.5.1",
815 875 ]
816 876
817 877 [[package]]
818 878 name = "rayon"
819 879 version = "1.5.0"
820 880 source = "registry+https://github.com/rust-lang/crates.io-index"
821 881 checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
822 882 dependencies = [
823 883 "autocfg",
824 884 "crossbeam-deque",
825 885 "either",
826 886 "rayon-core",
827 887 ]
828 888
829 889 [[package]]
830 890 name = "rayon-core"
831 891 version = "1.9.0"
832 892 source = "registry+https://github.com/rust-lang/crates.io-index"
833 893 checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
834 894 dependencies = [
835 895 "crossbeam-channel 0.5.0",
836 896 "crossbeam-deque",
837 897 "crossbeam-utils 0.8.1",
838 898 "lazy_static",
839 899 "num_cpus",
840 900 ]
841 901
842 902 [[package]]
843 903 name = "redox_syscall"
844 904 version = "0.1.57"
845 905 source = "registry+https://github.com/rust-lang/crates.io-index"
846 906 checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
847 907
848 908 [[package]]
849 909 name = "regex"
850 910 version = "1.4.2"
851 911 source = "registry+https://github.com/rust-lang/crates.io-index"
852 912 checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
853 913 dependencies = [
854 914 "aho-corasick",
855 915 "memchr",
856 916 "regex-syntax",
857 917 "thread_local",
858 918 ]
859 919
860 920 [[package]]
861 921 name = "regex-syntax"
862 922 version = "0.6.21"
863 923 source = "registry+https://github.com/rust-lang/crates.io-index"
864 924 checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
865 925
866 926 [[package]]
867 927 name = "remove_dir_all"
868 928 version = "0.5.3"
869 929 source = "registry+https://github.com/rust-lang/crates.io-index"
870 930 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
871 931 dependencies = [
872 932 "winapi",
873 933 ]
874 934
875 935 [[package]]
876 936 name = "rhg"
877 937 version = "0.1.0"
878 938 dependencies = [
879 939 "atty",
880 940 "chrono",
881 941 "clap",
882 942 "derive_more",
883 943 "env_logger",
884 944 "format-bytes",
885 945 "hg-core",
886 946 "home",
887 947 "lazy_static",
888 948 "log",
889 949 "micro-timer",
890 950 "regex",
891 951 "users",
892 952 ]
893 953
894 954 [[package]]
895 955 name = "same-file"
896 956 version = "1.0.6"
897 957 source = "registry+https://github.com/rust-lang/crates.io-index"
898 958 checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
899 959 dependencies = [
900 960 "winapi-util",
901 961 ]
902 962
903 963 [[package]]
904 964 name = "scopeguard"
905 965 version = "1.1.0"
906 966 source = "registry+https://github.com/rust-lang/crates.io-index"
907 967 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
908 968
909 969 [[package]]
910 970 name = "sha-1"
911 971 version = "0.9.6"
912 972 source = "registry+https://github.com/rust-lang/crates.io-index"
913 973 checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16"
914 974 dependencies = [
915 975 "block-buffer",
916 976 "cfg-if 1.0.0",
917 977 "cpufeatures",
918 978 "digest",
919 979 "opaque-debug",
920 980 ]
921 981
922 982 [[package]]
923 983 name = "sized-chunks"
924 984 version = "0.6.2"
925 985 source = "registry+https://github.com/rust-lang/crates.io-index"
926 986 checksum = "1ec31ceca5644fa6d444cc77548b88b67f46db6f7c71683b0f9336e671830d2f"
927 987 dependencies = [
928 988 "bitmaps",
929 989 "typenum",
930 990 ]
931 991
932 992 [[package]]
933 993 name = "stable_deref_trait"
934 994 version = "1.2.0"
935 995 source = "registry+https://github.com/rust-lang/crates.io-index"
936 996 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
937 997
938 998 [[package]]
939 999 name = "static_assertions"
940 1000 version = "1.1.0"
941 1001 source = "registry+https://github.com/rust-lang/crates.io-index"
942 1002 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
943 1003
944 1004 [[package]]
945 1005 name = "strsim"
946 1006 version = "0.8.0"
947 1007 source = "registry+https://github.com/rust-lang/crates.io-index"
948 1008 checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
949 1009
950 1010 [[package]]
951 1011 name = "syn"
952 1012 version = "1.0.54"
953 1013 source = "registry+https://github.com/rust-lang/crates.io-index"
954 1014 checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
955 1015 dependencies = [
956 1016 "proc-macro2",
957 1017 "quote",
958 1018 "unicode-xid",
959 1019 ]
960 1020
961 1021 [[package]]
962 1022 name = "tempfile"
963 1023 version = "3.1.0"
964 1024 source = "registry+https://github.com/rust-lang/crates.io-index"
965 1025 checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
966 1026 dependencies = [
967 1027 "cfg-if 0.1.10",
968 1028 "libc",
969 1029 "rand 0.7.3",
970 1030 "redox_syscall",
971 1031 "remove_dir_all",
972 1032 "winapi",
973 1033 ]
974 1034
975 1035 [[package]]
976 1036 name = "termcolor"
977 1037 version = "1.1.2"
978 1038 source = "registry+https://github.com/rust-lang/crates.io-index"
979 1039 checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
980 1040 dependencies = [
981 1041 "winapi-util",
982 1042 ]
983 1043
984 1044 [[package]]
985 1045 name = "textwrap"
986 1046 version = "0.11.0"
987 1047 source = "registry+https://github.com/rust-lang/crates.io-index"
988 1048 checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
989 1049 dependencies = [
990 1050 "unicode-width",
991 1051 ]
992 1052
993 1053 [[package]]
994 1054 name = "thread_local"
995 1055 version = "1.0.1"
996 1056 source = "registry+https://github.com/rust-lang/crates.io-index"
997 1057 checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
998 1058 dependencies = [
999 1059 "lazy_static",
1000 1060 ]
1001 1061
1002 1062 [[package]]
1003 1063 name = "time"
1004 1064 version = "0.1.44"
1005 1065 source = "registry+https://github.com/rust-lang/crates.io-index"
1006 1066 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
1007 1067 dependencies = [
1008 1068 "libc",
1009 1069 "wasi 0.10.0+wasi-snapshot-preview1",
1010 1070 "winapi",
1011 1071 ]
1012 1072
1013 1073 [[package]]
1014 1074 name = "twox-hash"
1015 1075 version = "1.6.0"
1016 1076 source = "registry+https://github.com/rust-lang/crates.io-index"
1017 1077 checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59"
1018 1078 dependencies = [
1019 1079 "cfg-if 0.1.10",
1020 1080 "rand 0.7.3",
1021 1081 "static_assertions",
1022 1082 ]
1023 1083
1024 1084 [[package]]
1025 1085 name = "typenum"
1026 1086 version = "1.12.0"
1027 1087 source = "registry+https://github.com/rust-lang/crates.io-index"
1028 1088 checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
1029 1089
1030 1090 [[package]]
1031 1091 name = "unicode-width"
1032 1092 version = "0.1.8"
1033 1093 source = "registry+https://github.com/rust-lang/crates.io-index"
1034 1094 checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
1035 1095
1036 1096 [[package]]
1037 1097 name = "unicode-xid"
1038 1098 version = "0.2.1"
1039 1099 source = "registry+https://github.com/rust-lang/crates.io-index"
1040 1100 checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1041 1101
1042 1102 [[package]]
1043 1103 name = "users"
1044 1104 version = "0.11.0"
1045 1105 source = "registry+https://github.com/rust-lang/crates.io-index"
1046 1106 checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
1047 1107 dependencies = [
1048 1108 "libc",
1049 1109 "log",
1050 1110 ]
1051 1111
1052 1112 [[package]]
1053 1113 name = "vcpkg"
1054 1114 version = "0.2.11"
1055 1115 source = "registry+https://github.com/rust-lang/crates.io-index"
1056 1116 checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
1057 1117
1058 1118 [[package]]
1059 1119 name = "vcsgraph"
1060 1120 version = "0.2.0"
1061 1121 source = "registry+https://github.com/rust-lang/crates.io-index"
1062 1122 checksum = "4cb68c231e2575f7503a7c19213875f9d4ec2e84e963a56ce3de4b6bee351ef7"
1063 1123 dependencies = [
1064 1124 "hex",
1065 1125 "rand 0.7.3",
1066 1126 "sha-1",
1067 1127 ]
1068 1128
1069 1129 [[package]]
1070 1130 name = "vec_map"
1071 1131 version = "0.8.2"
1072 1132 source = "registry+https://github.com/rust-lang/crates.io-index"
1073 1133 checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
1074 1134
1075 1135 [[package]]
1076 1136 name = "version_check"
1077 1137 version = "0.9.2"
1078 1138 source = "registry+https://github.com/rust-lang/crates.io-index"
1079 1139 checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1080 1140
1081 1141 [[package]]
1082 1142 name = "wasi"
1083 1143 version = "0.9.0+wasi-snapshot-preview1"
1084 1144 source = "registry+https://github.com/rust-lang/crates.io-index"
1085 1145 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1086 1146
1087 1147 [[package]]
1088 1148 name = "wasi"
1089 1149 version = "0.10.0+wasi-snapshot-preview1"
1090 1150 source = "registry+https://github.com/rust-lang/crates.io-index"
1091 1151 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
1092 1152
1093 1153 [[package]]
1094 1154 name = "winapi"
1095 1155 version = "0.3.9"
1096 1156 source = "registry+https://github.com/rust-lang/crates.io-index"
1097 1157 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1098 1158 dependencies = [
1099 1159 "winapi-i686-pc-windows-gnu",
1100 1160 "winapi-x86_64-pc-windows-gnu",
1101 1161 ]
1102 1162
1103 1163 [[package]]
1104 1164 name = "winapi-i686-pc-windows-gnu"
1105 1165 version = "0.4.0"
1106 1166 source = "registry+https://github.com/rust-lang/crates.io-index"
1107 1167 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1108 1168
1109 1169 [[package]]
1110 1170 name = "winapi-util"
1111 1171 version = "0.1.5"
1112 1172 source = "registry+https://github.com/rust-lang/crates.io-index"
1113 1173 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1114 1174 dependencies = [
1115 1175 "winapi",
1116 1176 ]
1117 1177
1118 1178 [[package]]
1119 1179 name = "winapi-x86_64-pc-windows-gnu"
1120 1180 version = "0.4.0"
1121 1181 source = "registry+https://github.com/rust-lang/crates.io-index"
1122 1182 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1123 1183
1124 1184 [[package]]
1125 1185 name = "zstd"
1126 1186 version = "0.5.3+zstd.1.4.5"
1127 1187 source = "registry+https://github.com/rust-lang/crates.io-index"
1128 1188 checksum = "01b32eaf771efa709e8308605bbf9319bf485dc1503179ec0469b611937c0cd8"
1129 1189 dependencies = [
1130 1190 "zstd-safe",
1131 1191 ]
1132 1192
1133 1193 [[package]]
1134 1194 name = "zstd-safe"
1135 1195 version = "2.0.5+zstd.1.4.5"
1136 1196 source = "registry+https://github.com/rust-lang/crates.io-index"
1137 1197 checksum = "1cfb642e0d27f64729a639c52db457e0ae906e7bc6f5fe8f5c453230400f1055"
1138 1198 dependencies = [
1139 1199 "libc",
1140 1200 "zstd-sys",
1141 1201 ]
1142 1202
1143 1203 [[package]]
1144 1204 name = "zstd-sys"
1145 1205 version = "1.4.17+zstd.1.4.5"
1146 1206 source = "registry+https://github.com/rust-lang/crates.io-index"
1147 1207 checksum = "b89249644df056b522696b1bb9e7c18c87e8ffa3e2f0dc3b0155875d6498f01b"
1148 1208 dependencies = [
1149 1209 "cc",
1150 1210 "glob",
1151 1211 "itertools",
1152 1212 "libc",
1153 1213 ]
@@ -1,47 +1,47 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 = "2018"
7 7
8 8 [lib]
9 9 name = "hg"
10 10
11 11 [dependencies]
12 12 bitflags = "1.2"
13 13 bytes-cast = "0.2"
14 14 byteorder = "1.3.4"
15 15 derive_more = "0.99"
16 16 home = "0.5"
17 17 im-rc = "15.0.*"
18 18 itertools = "0.9"
19 19 lazy_static = "1.4.0"
20 20 libc = "0.2"
21 ouroboros = "0.15.0"
21 22 rand = "0.8.4"
22 23 rand_pcg = "0.3.1"
23 24 rand_distr = "0.4.2"
24 25 rayon = "1.3.0"
25 26 regex = "1.3.9"
26 27 sha-1 = "0.9.6"
27 28 twox-hash = "1.5.0"
28 29 same-file = "1.0.6"
29 stable_deref_trait = "1.2.0"
30 30 tempfile = "3.1.0"
31 31 crossbeam-channel = "0.4"
32 32 micro-timer = "0.3.0"
33 33 log = "0.4.8"
34 34 memmap2 = {version = "0.4", features = ["stable_deref_trait"]}
35 35 zstd = "0.5.3"
36 36 format-bytes = "0.3.0"
37 37
38 38 # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until
39 39 # we have a clearer view of which backend is the fastest.
40 40 [dependencies.flate2]
41 41 version = "1.0.16"
42 42 features = ["zlib"]
43 43 default-features = false
44 44
45 45 [dev-dependencies]
46 46 clap = "*"
47 47 pretty_assertions = "0.6.1"
@@ -1,151 +1,149 b''
1 1 // status.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 //! Rust implementation of dirstate.status (dirstate.py).
9 9 //! It is currently missing a lot of functionality compared to the Python one
10 10 //! and will only be triggered in narrow cases.
11 11
12 12 use crate::dirstate::entry::TruncatedTimestamp;
13 13 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
14 14 use crate::{
15 15 utils::hg_path::{HgPath, HgPathError},
16 16 PatternError,
17 17 };
18 18
19 19 use std::{borrow::Cow, fmt};
20 20
21 21 /// Wrong type of file from a `BadMatch`
22 22 /// Note: a lot of those don't exist on all platforms.
23 23 #[derive(Debug, Copy, Clone)]
24 24 pub enum BadType {
25 25 CharacterDevice,
26 26 BlockDevice,
27 27 FIFO,
28 28 Socket,
29 29 Directory,
30 30 Unknown,
31 31 }
32 32
33 33 impl fmt::Display for BadType {
34 34 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35 35 f.write_str(match self {
36 36 BadType::CharacterDevice => "character device",
37 37 BadType::BlockDevice => "block device",
38 38 BadType::FIFO => "fifo",
39 39 BadType::Socket => "socket",
40 40 BadType::Directory => "directory",
41 41 BadType::Unknown => "unknown",
42 42 })
43 43 }
44 44 }
45 45
46 46 /// Was explicitly matched but cannot be found/accessed
47 47 #[derive(Debug, Copy, Clone)]
48 48 pub enum BadMatch {
49 49 OsError(i32),
50 50 BadType(BadType),
51 51 }
52 52
53 53 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait + 'static>`, so add
54 54 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
55 55 pub type IgnoreFnType<'a> =
56 56 Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
57 57
58 58 /// We have a good mix of owned (from directory traversal) and borrowed (from
59 59 /// the dirstate/explicit) paths, this comes up a lot.
60 60 pub type HgPathCow<'a> = Cow<'a, HgPath>;
61 61
62 62 #[derive(Debug, Copy, Clone)]
63 63 pub struct StatusOptions {
64 64 /// Whether we are on a filesystem with UNIX-like exec flags
65 65 pub check_exec: bool,
66 66 pub list_clean: bool,
67 67 pub list_unknown: bool,
68 68 pub list_ignored: bool,
69 69 /// Whether to populate `StatusPath::copy_source`
70 70 pub list_copies: bool,
71 71 /// Whether to collect traversed dirs for applying a callback later.
72 72 /// Used by `hg purge` for example.
73 73 pub collect_traversed_dirs: bool,
74 74 }
75 75
76 76 #[derive(Default)]
77 77 pub struct DirstateStatus<'a> {
78 78 /// The current time at the start of the `status()` algorithm, as measured
79 79 /// and possibly truncated by the filesystem.
80 80 pub filesystem_time_at_status_start: Option<TruncatedTimestamp>,
81 81
82 82 /// Tracked files whose contents have changed since the parent revision
83 83 pub modified: Vec<StatusPath<'a>>,
84 84
85 85 /// Newly-tracked files that were not present in the parent
86 86 pub added: Vec<StatusPath<'a>>,
87 87
88 88 /// Previously-tracked files that have been (re)moved with an hg command
89 89 pub removed: Vec<StatusPath<'a>>,
90 90
91 91 /// (Still) tracked files that are missing, (re)moved with an non-hg
92 92 /// command
93 93 pub deleted: Vec<StatusPath<'a>>,
94 94
95 95 /// Tracked files that are up to date with the parent.
96 96 /// Only pupulated if `StatusOptions::list_clean` is true.
97 97 pub clean: Vec<StatusPath<'a>>,
98 98
99 99 /// Files in the working directory that are ignored with `.hgignore`.
100 100 /// Only pupulated if `StatusOptions::list_ignored` is true.
101 101 pub ignored: Vec<StatusPath<'a>>,
102 102
103 103 /// Files in the working directory that are neither tracked nor ignored.
104 104 /// Only pupulated if `StatusOptions::list_unknown` is true.
105 105 pub unknown: Vec<StatusPath<'a>>,
106 106
107 107 /// Was explicitly matched but cannot be found/accessed
108 108 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
109 109
110 110 /// Either clean or modified, but we can’t tell from filesystem metadata
111 111 /// alone. The file contents need to be read and compared with that in
112 112 /// the parent.
113 113 pub unsure: Vec<StatusPath<'a>>,
114 114
115 115 /// Only filled if `collect_traversed_dirs` is `true`
116 116 pub traversed: Vec<HgPathCow<'a>>,
117 117
118 118 /// Whether `status()` made changed to the `DirstateMap` that should be
119 119 /// written back to disk
120 120 pub dirty: bool,
121 121 }
122 122
123 123 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
124 124 pub struct StatusPath<'a> {
125 125 pub path: HgPathCow<'a>,
126 126 pub copy_source: Option<HgPathCow<'a>>,
127 127 }
128 128
129 129 #[derive(Debug, derive_more::From)]
130 130 pub enum StatusError {
131 131 /// An invalid path that cannot be represented in Mercurial was found
132 132 Path(HgPathError),
133 133 /// An invalid "ignore" pattern was found
134 134 Pattern(PatternError),
135 135 /// Corrupted dirstate
136 136 DirstateV2ParseError(DirstateV2ParseError),
137 137 }
138 138
139 pub type StatusResult<T> = Result<T, StatusError>;
140
141 139 impl fmt::Display for StatusError {
142 140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
143 141 match self {
144 142 StatusError::Path(error) => error.fmt(f),
145 143 StatusError::Pattern(error) => error.fmt(f),
146 144 StatusError::DirstateV2ParseError(_) => {
147 145 f.write_str("dirstate-v2 parse error")
148 146 }
149 147 }
150 148 }
151 149 }
@@ -1,1158 +1,1180 b''
1 1 use bytes_cast::BytesCast;
2 2 use micro_timer::timed;
3 3 use std::borrow::Cow;
4 4 use std::path::PathBuf;
5 5
6 6 use super::on_disk;
7 7 use super::on_disk::DirstateV2ParseError;
8 8 use super::owning::OwningDirstateMap;
9 9 use super::path_with_basename::WithBasename;
10 10 use crate::dirstate::parsers::pack_entry;
11 11 use crate::dirstate::parsers::packed_entry_size;
12 12 use crate::dirstate::parsers::parse_dirstate_entries;
13 13 use crate::dirstate::CopyMapIter;
14 14 use crate::dirstate::StateMapIter;
15 15 use crate::dirstate::TruncatedTimestamp;
16 16 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
17 17 use crate::dirstate::SIZE_NON_NORMAL;
18 18 use crate::matchers::Matcher;
19 19 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 20 use crate::DirstateEntry;
21 21 use crate::DirstateError;
22 22 use crate::DirstateParents;
23 23 use crate::DirstateStatus;
24 24 use crate::EntryState;
25 25 use crate::FastHashMap;
26 26 use crate::PatternFileWarning;
27 27 use crate::StatusError;
28 28 use crate::StatusOptions;
29 29
30 30 /// Append to an existing data file if the amount of unreachable data (not used
31 31 /// anymore) is less than this fraction of the total amount of existing data.
32 32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33 33
34 34 pub struct DirstateMap<'on_disk> {
35 35 /// Contents of the `.hg/dirstate` file
36 36 pub(super) on_disk: &'on_disk [u8],
37 37
38 38 pub(super) root: ChildNodes<'on_disk>,
39 39
40 40 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
41 41 pub(super) nodes_with_entry_count: u32,
42 42
43 43 /// Number of nodes anywhere in the tree that have
44 44 /// `.copy_source.is_some()`.
45 45 pub(super) nodes_with_copy_source_count: u32,
46 46
47 47 /// See on_disk::Header
48 48 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
49 49
50 50 /// How many bytes of `on_disk` are not used anymore
51 51 pub(super) unreachable_bytes: u32,
52 52 }
53 53
54 54 /// Using a plain `HgPathBuf` of the full path from the repository root as a
55 55 /// map key would also work: all paths in a given map have the same parent
56 56 /// path, so comparing full paths gives the same result as comparing base
57 57 /// names. However `HashMap` would waste time always re-hashing the same
58 58 /// string prefix.
59 59 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
60 60
61 61 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
62 62 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
63 63 pub(super) enum BorrowedPath<'tree, 'on_disk> {
64 64 InMemory(&'tree HgPathBuf),
65 65 OnDisk(&'on_disk HgPath),
66 66 }
67 67
68 68 pub(super) enum ChildNodes<'on_disk> {
69 69 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
70 70 OnDisk(&'on_disk [on_disk::Node]),
71 71 }
72 72
73 73 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
74 74 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
75 75 OnDisk(&'on_disk [on_disk::Node]),
76 76 }
77 77
78 78 pub(super) enum NodeRef<'tree, 'on_disk> {
79 79 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
80 80 OnDisk(&'on_disk on_disk::Node),
81 81 }
82 82
83 83 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
84 84 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
85 85 match *self {
86 86 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
87 87 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
88 88 }
89 89 }
90 90 }
91 91
92 92 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
93 93 type Target = HgPath;
94 94
95 95 fn deref(&self) -> &HgPath {
96 96 match *self {
97 97 BorrowedPath::InMemory(in_memory) => in_memory,
98 98 BorrowedPath::OnDisk(on_disk) => on_disk,
99 99 }
100 100 }
101 101 }
102 102
103 103 impl Default for ChildNodes<'_> {
104 104 fn default() -> Self {
105 105 ChildNodes::InMemory(Default::default())
106 106 }
107 107 }
108 108
109 109 impl<'on_disk> ChildNodes<'on_disk> {
110 110 pub(super) fn as_ref<'tree>(
111 111 &'tree self,
112 112 ) -> ChildNodesRef<'tree, 'on_disk> {
113 113 match self {
114 114 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
115 115 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
116 116 }
117 117 }
118 118
119 119 pub(super) fn is_empty(&self) -> bool {
120 120 match self {
121 121 ChildNodes::InMemory(nodes) => nodes.is_empty(),
122 122 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
123 123 }
124 124 }
125 125
126 126 fn make_mut(
127 127 &mut self,
128 128 on_disk: &'on_disk [u8],
129 129 unreachable_bytes: &mut u32,
130 130 ) -> Result<
131 131 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
132 132 DirstateV2ParseError,
133 133 > {
134 134 match self {
135 135 ChildNodes::InMemory(nodes) => Ok(nodes),
136 136 ChildNodes::OnDisk(nodes) => {
137 137 *unreachable_bytes +=
138 138 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
139 139 let nodes = nodes
140 140 .iter()
141 141 .map(|node| {
142 142 Ok((
143 143 node.path(on_disk)?,
144 144 node.to_in_memory_node(on_disk)?,
145 145 ))
146 146 })
147 147 .collect::<Result<_, _>>()?;
148 148 *self = ChildNodes::InMemory(nodes);
149 149 match self {
150 150 ChildNodes::InMemory(nodes) => Ok(nodes),
151 151 ChildNodes::OnDisk(_) => unreachable!(),
152 152 }
153 153 }
154 154 }
155 155 }
156 156 }
157 157
158 158 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
159 159 pub(super) fn get(
160 160 &self,
161 161 base_name: &HgPath,
162 162 on_disk: &'on_disk [u8],
163 163 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
164 164 match self {
165 165 ChildNodesRef::InMemory(nodes) => Ok(nodes
166 166 .get_key_value(base_name)
167 167 .map(|(k, v)| NodeRef::InMemory(k, v))),
168 168 ChildNodesRef::OnDisk(nodes) => {
169 169 let mut parse_result = Ok(());
170 170 let search_result = nodes.binary_search_by(|node| {
171 171 match node.base_name(on_disk) {
172 172 Ok(node_base_name) => node_base_name.cmp(base_name),
173 173 Err(e) => {
174 174 parse_result = Err(e);
175 175 // Dummy comparison result, `search_result` won’t
176 176 // be used since `parse_result` is an error
177 177 std::cmp::Ordering::Equal
178 178 }
179 179 }
180 180 });
181 181 parse_result.map(|()| {
182 182 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
183 183 })
184 184 }
185 185 }
186 186 }
187 187
188 188 /// Iterate in undefined order
189 189 pub(super) fn iter(
190 190 &self,
191 191 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
192 192 match self {
193 193 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
194 194 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
195 195 ),
196 196 ChildNodesRef::OnDisk(nodes) => {
197 197 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
198 198 }
199 199 }
200 200 }
201 201
202 202 /// Iterate in parallel in undefined order
203 203 pub(super) fn par_iter(
204 204 &self,
205 205 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
206 206 {
207 207 use rayon::prelude::*;
208 208 match self {
209 209 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
210 210 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
211 211 ),
212 212 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
213 213 nodes.par_iter().map(NodeRef::OnDisk),
214 214 ),
215 215 }
216 216 }
217 217
218 218 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
219 219 match self {
220 220 ChildNodesRef::InMemory(nodes) => {
221 221 let mut vec: Vec<_> = nodes
222 222 .iter()
223 223 .map(|(k, v)| NodeRef::InMemory(k, v))
224 224 .collect();
225 225 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
226 226 match node {
227 227 NodeRef::InMemory(path, _node) => path.base_name(),
228 228 NodeRef::OnDisk(_) => unreachable!(),
229 229 }
230 230 }
231 231 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
232 232 // value: https://github.com/rust-lang/rust/issues/34162
233 233 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
234 234 vec
235 235 }
236 236 ChildNodesRef::OnDisk(nodes) => {
237 237 // Nodes on disk are already sorted
238 238 nodes.iter().map(NodeRef::OnDisk).collect()
239 239 }
240 240 }
241 241 }
242 242 }
243 243
244 244 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
245 245 pub(super) fn full_path(
246 246 &self,
247 247 on_disk: &'on_disk [u8],
248 248 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
249 249 match self {
250 250 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
251 251 NodeRef::OnDisk(node) => node.full_path(on_disk),
252 252 }
253 253 }
254 254
255 255 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
256 256 /// HgPath>` detached from `'tree`
257 257 pub(super) fn full_path_borrowed(
258 258 &self,
259 259 on_disk: &'on_disk [u8],
260 260 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
261 261 match self {
262 262 NodeRef::InMemory(path, _node) => match path.full_path() {
263 263 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
264 264 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
265 265 },
266 266 NodeRef::OnDisk(node) => {
267 267 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
268 268 }
269 269 }
270 270 }
271 271
272 272 pub(super) fn base_name(
273 273 &self,
274 274 on_disk: &'on_disk [u8],
275 275 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
276 276 match self {
277 277 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
278 278 NodeRef::OnDisk(node) => node.base_name(on_disk),
279 279 }
280 280 }
281 281
282 282 pub(super) fn children(
283 283 &self,
284 284 on_disk: &'on_disk [u8],
285 285 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
286 286 match self {
287 287 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
288 288 NodeRef::OnDisk(node) => {
289 289 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
290 290 }
291 291 }
292 292 }
293 293
294 294 pub(super) fn has_copy_source(&self) -> bool {
295 295 match self {
296 296 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
297 297 NodeRef::OnDisk(node) => node.has_copy_source(),
298 298 }
299 299 }
300 300
301 301 pub(super) fn copy_source(
302 302 &self,
303 303 on_disk: &'on_disk [u8],
304 304 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
305 305 match self {
306 306 NodeRef::InMemory(_path, node) => {
307 307 Ok(node.copy_source.as_ref().map(|s| &**s))
308 308 }
309 309 NodeRef::OnDisk(node) => node.copy_source(on_disk),
310 310 }
311 311 }
312 312 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
313 313 /// HgPath>` detached from `'tree`
314 314 pub(super) fn copy_source_borrowed(
315 315 &self,
316 316 on_disk: &'on_disk [u8],
317 317 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
318 318 {
319 319 Ok(match self {
320 320 NodeRef::InMemory(_path, node) => {
321 321 node.copy_source.as_ref().map(|source| match source {
322 322 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
323 323 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
324 324 })
325 325 }
326 326 NodeRef::OnDisk(node) => node
327 327 .copy_source(on_disk)?
328 328 .map(|source| BorrowedPath::OnDisk(source)),
329 329 })
330 330 }
331 331
332 332 pub(super) fn entry(
333 333 &self,
334 334 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
335 335 match self {
336 336 NodeRef::InMemory(_path, node) => {
337 337 Ok(node.data.as_entry().copied())
338 338 }
339 339 NodeRef::OnDisk(node) => node.entry(),
340 340 }
341 341 }
342 342
343 343 pub(super) fn state(
344 344 &self,
345 345 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
346 346 Ok(self.entry()?.map(|e| e.state()))
347 347 }
348 348
349 349 pub(super) fn cached_directory_mtime(
350 350 &self,
351 351 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
352 352 match self {
353 353 NodeRef::InMemory(_path, node) => Ok(match node.data {
354 354 NodeData::CachedDirectory { mtime } => Some(mtime),
355 355 _ => None,
356 356 }),
357 357 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
358 358 }
359 359 }
360 360
361 361 pub(super) fn descendants_with_entry_count(&self) -> u32 {
362 362 match self {
363 363 NodeRef::InMemory(_path, node) => {
364 364 node.descendants_with_entry_count
365 365 }
366 366 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
367 367 }
368 368 }
369 369
370 370 pub(super) fn tracked_descendants_count(&self) -> u32 {
371 371 match self {
372 372 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
373 373 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
374 374 }
375 375 }
376 376 }
377 377
378 378 /// Represents a file or a directory
379 379 #[derive(Default)]
380 380 pub(super) struct Node<'on_disk> {
381 381 pub(super) data: NodeData,
382 382
383 383 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
384 384
385 385 pub(super) children: ChildNodes<'on_disk>,
386 386
387 387 /// How many (non-inclusive) descendants of this node have an entry.
388 388 pub(super) descendants_with_entry_count: u32,
389 389
390 390 /// How many (non-inclusive) descendants of this node have an entry whose
391 391 /// state is "tracked".
392 392 pub(super) tracked_descendants_count: u32,
393 393 }
394 394
395 395 pub(super) enum NodeData {
396 396 Entry(DirstateEntry),
397 397 CachedDirectory { mtime: TruncatedTimestamp },
398 398 None,
399 399 }
400 400
401 401 impl Default for NodeData {
402 402 fn default() -> Self {
403 403 NodeData::None
404 404 }
405 405 }
406 406
407 407 impl NodeData {
408 408 fn has_entry(&self) -> bool {
409 409 match self {
410 410 NodeData::Entry(_) => true,
411 411 _ => false,
412 412 }
413 413 }
414 414
415 415 fn as_entry(&self) -> Option<&DirstateEntry> {
416 416 match self {
417 417 NodeData::Entry(entry) => Some(entry),
418 418 _ => None,
419 419 }
420 420 }
421 421 }
422 422
423 423 impl<'on_disk> DirstateMap<'on_disk> {
424 424 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
425 425 Self {
426 426 on_disk,
427 427 root: ChildNodes::default(),
428 428 nodes_with_entry_count: 0,
429 429 nodes_with_copy_source_count: 0,
430 430 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
431 431 unreachable_bytes: 0,
432 432 }
433 433 }
434 434
435 435 #[timed]
436 436 pub fn new_v2(
437 437 on_disk: &'on_disk [u8],
438 438 data_size: usize,
439 439 metadata: &[u8],
440 440 ) -> Result<Self, DirstateError> {
441 441 if let Some(data) = on_disk.get(..data_size) {
442 442 Ok(on_disk::read(data, metadata)?)
443 443 } else {
444 444 Err(DirstateV2ParseError.into())
445 445 }
446 446 }
447 447
448 448 #[timed]
449 449 pub fn new_v1(
450 450 on_disk: &'on_disk [u8],
451 451 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
452 452 let mut map = Self::empty(on_disk);
453 453 if map.on_disk.is_empty() {
454 454 return Ok((map, None));
455 455 }
456 456
457 457 let parents = parse_dirstate_entries(
458 458 map.on_disk,
459 459 |path, entry, copy_source| {
460 460 let tracked = entry.state().is_tracked();
461 461 let node = Self::get_or_insert_node(
462 462 map.on_disk,
463 463 &mut map.unreachable_bytes,
464 464 &mut map.root,
465 465 path,
466 466 WithBasename::to_cow_borrowed,
467 467 |ancestor| {
468 468 if tracked {
469 469 ancestor.tracked_descendants_count += 1
470 470 }
471 471 ancestor.descendants_with_entry_count += 1
472 472 },
473 473 )?;
474 474 assert!(
475 475 !node.data.has_entry(),
476 476 "duplicate dirstate entry in read"
477 477 );
478 478 assert!(
479 479 node.copy_source.is_none(),
480 480 "duplicate dirstate entry in read"
481 481 );
482 482 node.data = NodeData::Entry(*entry);
483 483 node.copy_source = copy_source.map(Cow::Borrowed);
484 484 map.nodes_with_entry_count += 1;
485 485 if copy_source.is_some() {
486 486 map.nodes_with_copy_source_count += 1
487 487 }
488 488 Ok(())
489 489 },
490 490 )?;
491 491 let parents = Some(parents.clone());
492 492
493 493 Ok((map, parents))
494 494 }
495 495
496 496 /// Assuming dirstate-v2 format, returns whether the next write should
497 497 /// append to the existing data file that contains `self.on_disk` (true),
498 498 /// or create a new data file from scratch (false).
499 499 pub(super) fn write_should_append(&self) -> bool {
500 500 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
501 501 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
502 502 }
503 503
504 504 fn get_node<'tree>(
505 505 &'tree self,
506 506 path: &HgPath,
507 507 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
508 508 let mut children = self.root.as_ref();
509 509 let mut components = path.components();
510 510 let mut component =
511 511 components.next().expect("expected at least one components");
512 512 loop {
513 513 if let Some(child) = children.get(component, self.on_disk)? {
514 514 if let Some(next_component) = components.next() {
515 515 component = next_component;
516 516 children = child.children(self.on_disk)?;
517 517 } else {
518 518 return Ok(Some(child));
519 519 }
520 520 } else {
521 521 return Ok(None);
522 522 }
523 523 }
524 524 }
525 525
526 526 /// Returns a mutable reference to the node at `path` if it exists
527 527 ///
528 528 /// This takes `root` instead of `&mut self` so that callers can mutate
529 529 /// other fields while the returned borrow is still valid
530 530 fn get_node_mut<'tree>(
531 531 on_disk: &'on_disk [u8],
532 532 unreachable_bytes: &mut u32,
533 533 root: &'tree mut ChildNodes<'on_disk>,
534 534 path: &HgPath,
535 535 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
536 536 let mut children = root;
537 537 let mut components = path.components();
538 538 let mut component =
539 539 components.next().expect("expected at least one components");
540 540 loop {
541 541 if let Some(child) = children
542 542 .make_mut(on_disk, unreachable_bytes)?
543 543 .get_mut(component)
544 544 {
545 545 if let Some(next_component) = components.next() {
546 546 component = next_component;
547 547 children = &mut child.children;
548 548 } else {
549 549 return Ok(Some(child));
550 550 }
551 551 } else {
552 552 return Ok(None);
553 553 }
554 554 }
555 555 }
556 556
557 557 pub(super) fn get_or_insert<'tree, 'path>(
558 558 &'tree mut self,
559 559 path: &HgPath,
560 560 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
561 561 Self::get_or_insert_node(
562 562 self.on_disk,
563 563 &mut self.unreachable_bytes,
564 564 &mut self.root,
565 565 path,
566 566 WithBasename::to_cow_owned,
567 567 |_| {},
568 568 )
569 569 }
570 570
571 571 fn get_or_insert_node<'tree, 'path>(
572 572 on_disk: &'on_disk [u8],
573 573 unreachable_bytes: &mut u32,
574 574 root: &'tree mut ChildNodes<'on_disk>,
575 575 path: &'path HgPath,
576 576 to_cow: impl Fn(
577 577 WithBasename<&'path HgPath>,
578 578 ) -> WithBasename<Cow<'on_disk, HgPath>>,
579 579 mut each_ancestor: impl FnMut(&mut Node),
580 580 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
581 581 let mut child_nodes = root;
582 582 let mut inclusive_ancestor_paths =
583 583 WithBasename::inclusive_ancestors_of(path);
584 584 let mut ancestor_path = inclusive_ancestor_paths
585 585 .next()
586 586 .expect("expected at least one inclusive ancestor");
587 587 loop {
588 588 // TODO: can we avoid allocating an owned key in cases where the
589 589 // map already contains that key, without introducing double
590 590 // lookup?
591 591 let child_node = child_nodes
592 592 .make_mut(on_disk, unreachable_bytes)?
593 593 .entry(to_cow(ancestor_path))
594 594 .or_default();
595 595 if let Some(next) = inclusive_ancestor_paths.next() {
596 596 each_ancestor(child_node);
597 597 ancestor_path = next;
598 598 child_nodes = &mut child_node.children;
599 599 } else {
600 600 return Ok(child_node);
601 601 }
602 602 }
603 603 }
604 604
605 605 fn add_or_remove_file(
606 606 &mut self,
607 607 path: &HgPath,
608 608 old_state: Option<EntryState>,
609 609 new_entry: DirstateEntry,
610 610 ) -> Result<(), DirstateV2ParseError> {
611 611 let had_entry = old_state.is_some();
612 612 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
613 613 let tracked_count_increment =
614 614 match (was_tracked, new_entry.state().is_tracked()) {
615 615 (false, true) => 1,
616 616 (true, false) => -1,
617 617 _ => 0,
618 618 };
619 619
620 620 let node = Self::get_or_insert_node(
621 621 self.on_disk,
622 622 &mut self.unreachable_bytes,
623 623 &mut self.root,
624 624 path,
625 625 WithBasename::to_cow_owned,
626 626 |ancestor| {
627 627 if !had_entry {
628 628 ancestor.descendants_with_entry_count += 1;
629 629 }
630 630
631 631 // We can’t use `+= increment` because the counter is unsigned,
632 632 // and we want debug builds to detect accidental underflow
633 633 // through zero
634 634 match tracked_count_increment {
635 635 1 => ancestor.tracked_descendants_count += 1,
636 636 -1 => ancestor.tracked_descendants_count -= 1,
637 637 _ => {}
638 638 }
639 639 },
640 640 )?;
641 641 if !had_entry {
642 642 self.nodes_with_entry_count += 1
643 643 }
644 644 node.data = NodeData::Entry(new_entry);
645 645 Ok(())
646 646 }
647 647
648 648 fn iter_nodes<'tree>(
649 649 &'tree self,
650 650 ) -> impl Iterator<
651 651 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
652 652 > + 'tree {
653 653 // Depth first tree traversal.
654 654 //
655 655 // If we could afford internal iteration and recursion,
656 656 // this would look like:
657 657 //
658 658 // ```
659 659 // fn traverse_children(
660 660 // children: &ChildNodes,
661 661 // each: &mut impl FnMut(&Node),
662 662 // ) {
663 663 // for child in children.values() {
664 664 // traverse_children(&child.children, each);
665 665 // each(child);
666 666 // }
667 667 // }
668 668 // ```
669 669 //
670 670 // However we want an external iterator and therefore can’t use the
671 671 // call stack. Use an explicit stack instead:
672 672 let mut stack = Vec::new();
673 673 let mut iter = self.root.as_ref().iter();
674 674 std::iter::from_fn(move || {
675 675 while let Some(child_node) = iter.next() {
676 676 let children = match child_node.children(self.on_disk) {
677 677 Ok(children) => children,
678 678 Err(error) => return Some(Err(error)),
679 679 };
680 680 // Pseudo-recursion
681 681 let new_iter = children.iter();
682 682 let old_iter = std::mem::replace(&mut iter, new_iter);
683 683 stack.push((child_node, old_iter));
684 684 }
685 685 // Found the end of a `children.iter()` iterator.
686 686 if let Some((child_node, next_iter)) = stack.pop() {
687 687 // "Return" from pseudo-recursion by restoring state from the
688 688 // explicit stack
689 689 iter = next_iter;
690 690
691 691 Some(Ok(child_node))
692 692 } else {
693 693 // Reached the bottom of the stack, we’re done
694 694 None
695 695 }
696 696 })
697 697 }
698 698
699 699 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
700 700 if let Cow::Borrowed(path) = path {
701 701 *unreachable_bytes += path.len() as u32
702 702 }
703 703 }
704 704 }
705 705
706 706 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
707 707 ///
708 708 /// The callback is only called for incoming `Ok` values. Errors are passed
709 709 /// through as-is. In order to let it use the `?` operator the callback is
710 710 /// expected to return a `Result` of `Option`, instead of an `Option` of
711 711 /// `Result`.
712 712 fn filter_map_results<'a, I, F, A, B, E>(
713 713 iter: I,
714 714 f: F,
715 715 ) -> impl Iterator<Item = Result<B, E>> + 'a
716 716 where
717 717 I: Iterator<Item = Result<A, E>> + 'a,
718 718 F: Fn(A) -> Result<Option<B>, E> + 'a,
719 719 {
720 720 iter.filter_map(move |result| match result {
721 721 Ok(node) => f(node).transpose(),
722 722 Err(e) => Some(Err(e)),
723 723 })
724 724 }
725 725
726 726 impl OwningDirstateMap {
727 727 pub fn clear(&mut self) {
728 let map = self.get_map_mut();
729 map.root = Default::default();
730 map.nodes_with_entry_count = 0;
731 map.nodes_with_copy_source_count = 0;
728 self.with_dmap_mut(|map| {
729 map.root = Default::default();
730 map.nodes_with_entry_count = 0;
731 map.nodes_with_copy_source_count = 0;
732 });
732 733 }
733 734
734 735 pub fn set_entry(
735 736 &mut self,
736 737 filename: &HgPath,
737 738 entry: DirstateEntry,
738 739 ) -> Result<(), DirstateV2ParseError> {
739 let map = self.get_map_mut();
740 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
741 Ok(())
740 self.with_dmap_mut(|map| {
741 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
742 Ok(())
743 })
742 744 }
743 745
744 746 pub fn add_file(
745 747 &mut self,
746 748 filename: &HgPath,
747 749 entry: DirstateEntry,
748 750 ) -> Result<(), DirstateError> {
749 751 let old_state = self.get(filename)?.map(|e| e.state());
750 let map = self.get_map_mut();
751 Ok(map.add_or_remove_file(filename, old_state, entry)?)
752 self.with_dmap_mut(|map| {
753 Ok(map.add_or_remove_file(filename, old_state, entry)?)
754 })
752 755 }
753 756
754 757 pub fn remove_file(
755 758 &mut self,
756 759 filename: &HgPath,
757 760 in_merge: bool,
758 761 ) -> Result<(), DirstateError> {
759 762 let old_entry_opt = self.get(filename)?;
760 763 let old_state = old_entry_opt.map(|e| e.state());
761 764 let mut size = 0;
762 765 if in_merge {
763 766 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
764 767 // during a merge. So I (marmoute) am not sure we need the
765 768 // conditionnal at all. Adding double checking this with assert
766 769 // would be nice.
767 770 if let Some(old_entry) = old_entry_opt {
768 771 // backup the previous state
769 772 if old_entry.state() == EntryState::Merged {
770 773 size = SIZE_NON_NORMAL;
771 774 } else if old_entry.state() == EntryState::Normal
772 775 && old_entry.size() == SIZE_FROM_OTHER_PARENT
773 776 {
774 777 // other parent
775 778 size = SIZE_FROM_OTHER_PARENT;
776 779 }
777 780 }
778 781 }
779 782 if size == 0 {
780 783 self.copy_map_remove(filename)?;
781 784 }
782 let map = self.get_map_mut();
783 let entry = DirstateEntry::new_removed(size);
784 Ok(map.add_or_remove_file(filename, old_state, entry)?)
785 self.with_dmap_mut(|map| {
786 let entry = DirstateEntry::new_removed(size);
787 Ok(map.add_or_remove_file(filename, old_state, entry)?)
788 })
785 789 }
786 790
787 791 pub fn drop_entry_and_copy_source(
788 792 &mut self,
789 793 filename: &HgPath,
790 794 ) -> Result<(), DirstateError> {
791 795 let was_tracked = self
792 796 .get(filename)?
793 797 .map_or(false, |e| e.state().is_tracked());
794 let map = self.get_map_mut();
795 798 struct Dropped {
796 799 was_tracked: bool,
797 800 had_entry: bool,
798 801 had_copy_source: bool,
799 802 }
800 803
801 804 /// If this returns `Ok(Some((dropped, removed)))`, then
802 805 ///
803 806 /// * `dropped` is about the leaf node that was at `filename`
804 807 /// * `removed` is whether this particular level of recursion just
805 808 /// removed a node in `nodes`.
806 809 fn recur<'on_disk>(
807 810 on_disk: &'on_disk [u8],
808 811 unreachable_bytes: &mut u32,
809 812 nodes: &mut ChildNodes<'on_disk>,
810 813 path: &HgPath,
811 814 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
812 815 let (first_path_component, rest_of_path) =
813 816 path.split_first_component();
814 817 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
815 818 let node = if let Some(node) = nodes.get_mut(first_path_component)
816 819 {
817 820 node
818 821 } else {
819 822 return Ok(None);
820 823 };
821 824 let dropped;
822 825 if let Some(rest) = rest_of_path {
823 826 if let Some((d, removed)) = recur(
824 827 on_disk,
825 828 unreachable_bytes,
826 829 &mut node.children,
827 830 rest,
828 831 )? {
829 832 dropped = d;
830 833 if dropped.had_entry {
831 834 node.descendants_with_entry_count -= 1;
832 835 }
833 836 if dropped.was_tracked {
834 837 node.tracked_descendants_count -= 1;
835 838 }
836 839
837 840 // Directory caches must be invalidated when removing a
838 841 // child node
839 842 if removed {
840 843 if let NodeData::CachedDirectory { .. } = &node.data {
841 844 node.data = NodeData::None
842 845 }
843 846 }
844 847 } else {
845 848 return Ok(None);
846 849 }
847 850 } else {
848 851 let had_entry = node.data.has_entry();
849 852 if had_entry {
850 853 node.data = NodeData::None
851 854 }
852 855 if let Some(source) = &node.copy_source {
853 856 DirstateMap::count_dropped_path(unreachable_bytes, source);
854 857 node.copy_source = None
855 858 }
856 859 dropped = Dropped {
857 860 was_tracked: node
858 861 .data
859 862 .as_entry()
860 863 .map_or(false, |entry| entry.state().is_tracked()),
861 864 had_entry,
862 865 had_copy_source: node.copy_source.take().is_some(),
863 866 };
864 867 }
865 868 // After recursion, for both leaf (rest_of_path is None) nodes and
866 869 // parent nodes, remove a node if it just became empty.
867 870 let remove = !node.data.has_entry()
868 871 && node.copy_source.is_none()
869 872 && node.children.is_empty();
870 873 if remove {
871 874 let (key, _) =
872 875 nodes.remove_entry(first_path_component).unwrap();
873 876 DirstateMap::count_dropped_path(
874 877 unreachable_bytes,
875 878 key.full_path(),
876 879 )
877 880 }
878 881 Ok(Some((dropped, remove)))
879 882 }
880 883
881 if let Some((dropped, _removed)) = recur(
882 map.on_disk,
883 &mut map.unreachable_bytes,
884 &mut map.root,
885 filename,
886 )? {
887 if dropped.had_entry {
888 map.nodes_with_entry_count -= 1
884 self.with_dmap_mut(|map| {
885 if let Some((dropped, _removed)) = recur(
886 map.on_disk,
887 &mut map.unreachable_bytes,
888 &mut map.root,
889 filename,
890 )? {
891 if dropped.had_entry {
892 map.nodes_with_entry_count -= 1
893 }
894 if dropped.had_copy_source {
895 map.nodes_with_copy_source_count -= 1
896 }
897 } else {
898 debug_assert!(!was_tracked);
889 899 }
890 if dropped.had_copy_source {
891 map.nodes_with_copy_source_count -= 1
892 }
893 } else {
894 debug_assert!(!was_tracked);
895 }
896 Ok(())
900 Ok(())
901 })
897 902 }
898 903
899 904 pub fn has_tracked_dir(
900 905 &mut self,
901 906 directory: &HgPath,
902 907 ) -> Result<bool, DirstateError> {
903 let map = self.get_map_mut();
904 if let Some(node) = map.get_node(directory)? {
905 // A node without a `DirstateEntry` was created to hold child
906 // nodes, and is therefore a directory.
907 let state = node.state()?;
908 Ok(state.is_none() && node.tracked_descendants_count() > 0)
909 } else {
910 Ok(false)
911 }
908 self.with_dmap_mut(|map| {
909 if let Some(node) = map.get_node(directory)? {
910 // A node without a `DirstateEntry` was created to hold child
911 // nodes, and is therefore a directory.
912 let state = node.state()?;
913 Ok(state.is_none() && node.tracked_descendants_count() > 0)
914 } else {
915 Ok(false)
916 }
917 })
912 918 }
913 919
914 920 pub fn has_dir(
915 921 &mut self,
916 922 directory: &HgPath,
917 923 ) -> Result<bool, DirstateError> {
918 let map = self.get_map_mut();
919 if let Some(node) = map.get_node(directory)? {
920 // A node without a `DirstateEntry` was created to hold child
921 // nodes, and is therefore a directory.
922 let state = node.state()?;
923 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
924 } else {
925 Ok(false)
926 }
924 self.with_dmap_mut(|map| {
925 if let Some(node) = map.get_node(directory)? {
926 // A node without a `DirstateEntry` was created to hold child
927 // nodes, and is therefore a directory.
928 let state = node.state()?;
929 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
930 } else {
931 Ok(false)
932 }
933 })
927 934 }
928 935
929 936 #[timed]
930 937 pub fn pack_v1(
931 938 &self,
932 939 parents: DirstateParents,
933 940 ) -> Result<Vec<u8>, DirstateError> {
934 941 let map = self.get_map();
935 942 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
936 943 // reallocations
937 944 let mut size = parents.as_bytes().len();
938 945 for node in map.iter_nodes() {
939 946 let node = node?;
940 947 if node.entry()?.is_some() {
941 948 size += packed_entry_size(
942 949 node.full_path(map.on_disk)?,
943 950 node.copy_source(map.on_disk)?,
944 951 );
945 952 }
946 953 }
947 954
948 955 let mut packed = Vec::with_capacity(size);
949 956 packed.extend(parents.as_bytes());
950 957
951 958 for node in map.iter_nodes() {
952 959 let node = node?;
953 960 if let Some(entry) = node.entry()? {
954 961 pack_entry(
955 962 node.full_path(map.on_disk)?,
956 963 &entry,
957 964 node.copy_source(map.on_disk)?,
958 965 &mut packed,
959 966 );
960 967 }
961 968 }
962 969 Ok(packed)
963 970 }
964 971
965 972 /// Returns new data and metadata together with whether that data should be
966 973 /// appended to the existing data file whose content is at
967 974 /// `map.on_disk` (true), instead of written to a new data file
968 975 /// (false).
969 976 #[timed]
970 977 pub fn pack_v2(
971 978 &self,
972 979 can_append: bool,
973 980 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
974 981 let map = self.get_map();
975 982 on_disk::write(map, can_append)
976 983 }
977 984
978 pub fn status<'a>(
979 &'a mut self,
980 matcher: &'a (dyn Matcher + Sync),
985 /// `callback` allows the caller to process and do something with the
986 /// results of the status. This is needed to do so efficiently (i.e.
987 /// without cloning the `DirstateStatus` object with its paths) because
988 /// we need to borrow from `Self`.
989 pub fn with_status<R>(
990 &mut self,
991 matcher: &(dyn Matcher + Sync),
981 992 root_dir: PathBuf,
982 993 ignore_files: Vec<PathBuf>,
983 994 options: StatusOptions,
984 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
985 {
986 let map = self.get_map_mut();
987 super::status::status(map, matcher, root_dir, ignore_files, options)
995 callback: impl for<'r> FnOnce(
996 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
997 ) -> R,
998 ) -> R {
999 self.with_dmap_mut(|map| {
1000 callback(super::status::status(
1001 map,
1002 matcher,
1003 root_dir,
1004 ignore_files,
1005 options,
1006 ))
1007 })
988 1008 }
989 1009
990 1010 pub fn copy_map_len(&self) -> usize {
991 1011 let map = self.get_map();
992 1012 map.nodes_with_copy_source_count as usize
993 1013 }
994 1014
995 1015 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
996 1016 let map = self.get_map();
997 1017 Box::new(filter_map_results(map.iter_nodes(), move |node| {
998 1018 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
999 1019 Some((node.full_path(map.on_disk)?, source))
1000 1020 } else {
1001 1021 None
1002 1022 })
1003 1023 }))
1004 1024 }
1005 1025
1006 1026 pub fn copy_map_contains_key(
1007 1027 &self,
1008 1028 key: &HgPath,
1009 1029 ) -> Result<bool, DirstateV2ParseError> {
1010 1030 let map = self.get_map();
1011 1031 Ok(if let Some(node) = map.get_node(key)? {
1012 1032 node.has_copy_source()
1013 1033 } else {
1014 1034 false
1015 1035 })
1016 1036 }
1017 1037
1018 1038 pub fn copy_map_get(
1019 1039 &self,
1020 1040 key: &HgPath,
1021 1041 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1022 1042 let map = self.get_map();
1023 1043 if let Some(node) = map.get_node(key)? {
1024 1044 if let Some(source) = node.copy_source(map.on_disk)? {
1025 1045 return Ok(Some(source));
1026 1046 }
1027 1047 }
1028 1048 Ok(None)
1029 1049 }
1030 1050
1031 1051 pub fn copy_map_remove(
1032 1052 &mut self,
1033 1053 key: &HgPath,
1034 1054 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1035 let map = self.get_map_mut();
1036 let count = &mut map.nodes_with_copy_source_count;
1037 let unreachable_bytes = &mut map.unreachable_bytes;
1038 Ok(DirstateMap::get_node_mut(
1039 map.on_disk,
1040 unreachable_bytes,
1041 &mut map.root,
1042 key,
1043 )?
1044 .and_then(|node| {
1045 if let Some(source) = &node.copy_source {
1046 *count -= 1;
1047 DirstateMap::count_dropped_path(unreachable_bytes, source);
1048 }
1049 node.copy_source.take().map(Cow::into_owned)
1050 }))
1055 self.with_dmap_mut(|map| {
1056 let count = &mut map.nodes_with_copy_source_count;
1057 let unreachable_bytes = &mut map.unreachable_bytes;
1058 Ok(DirstateMap::get_node_mut(
1059 map.on_disk,
1060 unreachable_bytes,
1061 &mut map.root,
1062 key,
1063 )?
1064 .and_then(|node| {
1065 if let Some(source) = &node.copy_source {
1066 *count -= 1;
1067 DirstateMap::count_dropped_path(unreachable_bytes, source);
1068 }
1069 node.copy_source.take().map(Cow::into_owned)
1070 }))
1071 })
1051 1072 }
1052 1073
1053 1074 pub fn copy_map_insert(
1054 1075 &mut self,
1055 1076 key: HgPathBuf,
1056 1077 value: HgPathBuf,
1057 1078 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1058 let map = self.get_map_mut();
1059 let node = DirstateMap::get_or_insert_node(
1060 map.on_disk,
1061 &mut map.unreachable_bytes,
1062 &mut map.root,
1063 &key,
1064 WithBasename::to_cow_owned,
1065 |_ancestor| {},
1066 )?;
1067 if node.copy_source.is_none() {
1068 map.nodes_with_copy_source_count += 1
1069 }
1070 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1079 self.with_dmap_mut(|map| {
1080 let node = DirstateMap::get_or_insert_node(
1081 map.on_disk,
1082 &mut map.unreachable_bytes,
1083 &mut map.root,
1084 &key,
1085 WithBasename::to_cow_owned,
1086 |_ancestor| {},
1087 )?;
1088 if node.copy_source.is_none() {
1089 map.nodes_with_copy_source_count += 1
1090 }
1091 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1092 })
1071 1093 }
1072 1094
1073 1095 pub fn len(&self) -> usize {
1074 1096 let map = self.get_map();
1075 1097 map.nodes_with_entry_count as usize
1076 1098 }
1077 1099
1078 1100 pub fn contains_key(
1079 1101 &self,
1080 1102 key: &HgPath,
1081 1103 ) -> Result<bool, DirstateV2ParseError> {
1082 1104 Ok(self.get(key)?.is_some())
1083 1105 }
1084 1106
1085 1107 pub fn get(
1086 1108 &self,
1087 1109 key: &HgPath,
1088 1110 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1089 1111 let map = self.get_map();
1090 1112 Ok(if let Some(node) = map.get_node(key)? {
1091 1113 node.entry()?
1092 1114 } else {
1093 1115 None
1094 1116 })
1095 1117 }
1096 1118
1097 1119 pub fn iter(&self) -> StateMapIter<'_> {
1098 1120 let map = self.get_map();
1099 1121 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1100 1122 Ok(if let Some(entry) = node.entry()? {
1101 1123 Some((node.full_path(map.on_disk)?, entry))
1102 1124 } else {
1103 1125 None
1104 1126 })
1105 1127 }))
1106 1128 }
1107 1129
1108 1130 pub fn iter_tracked_dirs(
1109 1131 &mut self,
1110 1132 ) -> Result<
1111 1133 Box<
1112 1134 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1113 1135 + Send
1114 1136 + '_,
1115 1137 >,
1116 1138 DirstateError,
1117 1139 > {
1118 let map = self.get_map_mut();
1140 let map = self.get_map();
1119 1141 let on_disk = map.on_disk;
1120 1142 Ok(Box::new(filter_map_results(
1121 1143 map.iter_nodes(),
1122 1144 move |node| {
1123 1145 Ok(if node.tracked_descendants_count() > 0 {
1124 1146 Some(node.full_path(on_disk)?)
1125 1147 } else {
1126 1148 None
1127 1149 })
1128 1150 },
1129 1151 )))
1130 1152 }
1131 1153
1132 1154 pub fn debug_iter(
1133 1155 &self,
1134 1156 all: bool,
1135 1157 ) -> Box<
1136 1158 dyn Iterator<
1137 1159 Item = Result<
1138 1160 (&HgPath, (u8, i32, i32, i32)),
1139 1161 DirstateV2ParseError,
1140 1162 >,
1141 1163 > + Send
1142 1164 + '_,
1143 1165 > {
1144 1166 let map = self.get_map();
1145 1167 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1146 1168 let debug_tuple = if let Some(entry) = node.entry()? {
1147 1169 entry.debug_tuple()
1148 1170 } else if !all {
1149 1171 return Ok(None);
1150 1172 } else if let Some(mtime) = node.cached_directory_mtime()? {
1151 1173 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1152 1174 } else {
1153 1175 (b' ', 0, -1, -1)
1154 1176 };
1155 1177 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1156 1178 }))
1157 1179 }
1158 1180 }
@@ -1,142 +1,89 b''
1 use crate::{DirstateError, DirstateParents};
2
1 3 use super::dirstate_map::DirstateMap;
2 use stable_deref_trait::StableDeref;
3 4 use std::ops::Deref;
4 5
5 /*
6 // /!\ This is unsound and can cause use after free. It will be fixed in the
7 // next patch
8
9 // If we change `value` from its current use of `HgPathBuf` to `&HgPath`,
10 // nothing here tells that `value` will outlive `OwningDirstateMap`
11 pub fn copy_map_insert<'a,'owned>(
12 &'owned mut self,
13 key: &HgPath,
14 value: &'a HgPath,
15 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
16 // `'local` is smaller than `'a` here
17 let map: &'local mut DirstateMap<'local> = self.get_map_mut();
18 let node: &'local mut Node<'local> = DirstateMap::get_or_insert_node(
19 map.on_disk,
20 &mut map.unreachable_bytes,
21 &mut map.root,
22 &key,
23 WithBasename::to_cow_owned,
24 |_ancestor| {},
25 )?;
26 if node.copy_source.is_none() {
27 map.nodes_with_copy_source_count += 1
28 }
29 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
30 // and right here ----------^^^^^^^^^^^^
31 // we are storing `&'a HgPath` in `Node<'local>` which is possible
32 // because to the compiler, `'a` is longer than ``local`.
33 // It is wrong because nothing proves that `&'a HgPath` will outlive `self`.
34 }
35
36 // All of this is caused by the wrong cast of the DirstateMap pointer that
37 // fakes the lifetime of `DirstateMap` and ensures the compiler that it lives
38 // as long as `on_disk`, which is only true for its immutable data.
39 // This will be fixed in the next commit.
40 */
6 use ouroboros::self_referencing;
41 7
42 8 /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it
43 9 /// borrows.
44 ///
45 /// This is similar to [`OwningRef`] which is more limited because it
46 /// represents exactly one `&T` reference next to the value it borrows, as
47 /// opposed to a struct that may contain an arbitrary number of references in
48 /// arbitrarily-nested data structures.
49 ///
50 /// [`OwningRef`]: https://docs.rs/owning_ref/0.4.1/owning_ref/struct.OwningRef.html
10 #[self_referencing]
51 11 pub struct OwningDirstateMap {
52 /// Owned handle to a bytes buffer with a stable address.
53 ///
54 /// See <https://docs.rs/owning_ref/0.4.1/owning_ref/trait.StableAddress.html>.
55 12 on_disk: Box<dyn Deref<Target = [u8]> + Send>,
56
57 /// Pointer for `Box<DirstateMap<'on_disk>>`, typed-erased because the
58 /// language cannot represent a lifetime referencing a sibling field.
59 /// This is not quite a self-referencial struct (moving this struct is not
60 /// a problem as it doesn’t change the address of the bytes buffer owned
61 /// by `on_disk`) but touches similar borrow-checker limitations.
62 ptr: *mut (),
13 #[borrows(on_disk)]
14 #[covariant]
15 map: DirstateMap<'this>,
63 16 }
64 17
65 18 impl OwningDirstateMap {
66 19 pub fn new_empty<OnDisk>(on_disk: OnDisk) -> Self
67 20 where
68 OnDisk: Deref<Target = [u8]> + StableDeref + Send + 'static,
21 OnDisk: Deref<Target = [u8]> + Send + 'static,
69 22 {
70 23 let on_disk = Box::new(on_disk);
71 let bytes: &'_ [u8] = &on_disk;
72 let map = DirstateMap::empty(bytes);
73 24
74 // Like in `bytes` above, this `'_` lifetime parameter borrows from
75 // the bytes buffer owned by `on_disk`.
76 let ptr: *mut DirstateMap<'_> = Box::into_raw(Box::new(map));
77
78 // Erase the pointed type entirely in order to erase the lifetime.
79 let ptr: *mut () = ptr.cast();
80
81 Self { on_disk, ptr }
25 OwningDirstateMapBuilder {
26 on_disk,
27 map_builder: |bytes| DirstateMap::empty(&bytes),
28 }
29 .build()
82 30 }
83 31
84 pub fn get_pair_mut<'a>(
85 &'a mut self,
86 ) -> (&'a [u8], &'a mut DirstateMap<'a>) {
87 // SAFETY: We cast the type-erased pointer back to the same type it had
88 // in `new`, except with a different lifetime parameter. This time we
89 // connect the lifetime to that of `self`. This cast is valid because
90 // `self` owns the same `on_disk` whose buffer `DirstateMap`
91 // references. That buffer has a stable memory address because our
92 // `Self::new_empty` counstructor requires `StableDeref`.
93 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
94 // SAFETY: we dereference that pointer, connecting the lifetime of the
95 // new `&mut` to that of `self`. This is valid because the
96 // raw pointer is to a boxed value, and `self` owns that box.
97 (&self.on_disk, unsafe { &mut *ptr })
98 }
32 pub fn new_v1<OnDisk>(
33 on_disk: OnDisk,
34 ) -> Result<(Self, DirstateParents), DirstateError>
35 where
36 OnDisk: Deref<Target = [u8]> + Send + 'static,
37 {
38 let on_disk = Box::new(on_disk);
39 let mut parents = DirstateParents::NULL;
99 40
100 pub fn get_map_mut<'a>(&'a mut self) -> &'a mut DirstateMap<'a> {
101 self.get_pair_mut().1
41 Ok((
42 OwningDirstateMapTryBuilder {
43 on_disk,
44 map_builder: |bytes| {
45 DirstateMap::new_v1(&bytes).map(|(dmap, p)| {
46 parents = p.unwrap_or(DirstateParents::NULL);
47 dmap
48 })
49 },
50 }
51 .try_build()?,
52 parents,
53 ))
102 54 }
103 55
104 pub fn get_map<'a>(&'a self) -> &'a DirstateMap<'a> {
105 // SAFETY: same reasoning as in `get_pair_mut` above.
106 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
107 unsafe { &*ptr }
56 pub fn new_v2<OnDisk>(
57 on_disk: OnDisk,
58 data_size: usize,
59 metadata: &[u8],
60 ) -> Result<Self, DirstateError>
61 where
62 OnDisk: Deref<Target = [u8]> + Send + 'static,
63 {
64 let on_disk = Box::new(on_disk);
65
66 OwningDirstateMapTryBuilder {
67 on_disk,
68 map_builder: |bytes| {
69 DirstateMap::new_v2(&bytes, data_size, metadata)
70 },
71 }
72 .try_build()
108 73 }
109 74
110 pub fn on_disk<'a>(&'a self) -> &'a [u8] {
111 &self.on_disk
75 pub fn with_dmap_mut<R>(
76 &mut self,
77 f: impl FnOnce(&mut DirstateMap) -> R,
78 ) -> R {
79 self.with_map_mut(f)
80 }
81
82 pub fn get_map(&self) -> &DirstateMap {
83 self.borrow_map()
84 }
85
86 pub fn on_disk(&self) -> &[u8] {
87 self.borrow_on_disk()
112 88 }
113 89 }
114
115 impl Drop for OwningDirstateMap {
116 fn drop(&mut self) {
117 // Silence a "field is never read" warning, and demonstrate that this
118 // value is still alive.
119 let _: &Box<dyn Deref<Target = [u8]> + Send> = &self.on_disk;
120 // SAFETY: this cast is the same as in `get_mut`, and is valid for the
121 // same reason. `self.on_disk` still exists at this point, drop glue
122 // will drop it implicitly after this `drop` method returns.
123 let ptr: *mut DirstateMap<'_> = self.ptr.cast();
124 // SAFETY: `Box::from_raw` takes ownership of the box away from `self`.
125 // This is fine because drop glue does nothing for `*mut ()` and we’re
126 // in `drop`, so `get` and `get_mut` cannot be called again.
127 unsafe { drop(Box::from_raw(ptr)) }
128 }
129 }
130
131 fn _static_assert_is_send<T: Send>() {}
132
133 fn _static_assert_fields_are_send() {
134 _static_assert_is_send::<Box<DirstateMap<'_>>>();
135 }
136
137 // SAFETY: we don’t get this impl implicitly because `*mut (): !Send` because
138 // thread-safety of raw pointers is unknown in the general case. However this
139 // particular raw pointer represents a `Box<DirstateMap<'on_disk>>` that we
140 // own. Since that `Box` is `Send` as shown in above, it is sound to mark
141 // this struct as `Send` too.
142 unsafe impl Send for OwningDirstateMap {}
@@ -1,848 +1,849 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::NodeData;
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_path_from_bytes;
14 14 use crate::utils::hg_path::HgPath;
15 15 use crate::BadMatch;
16 16 use crate::DirstateStatus;
17 17 use crate::EntryState;
18 18 use crate::HgPathBuf;
19 19 use crate::HgPathCow;
20 20 use crate::PatternFileWarning;
21 21 use crate::StatusError;
22 22 use crate::StatusOptions;
23 23 use micro_timer::timed;
24 24 use rayon::prelude::*;
25 25 use sha1::{Digest, Sha1};
26 26 use std::borrow::Cow;
27 27 use std::io;
28 28 use std::path::Path;
29 29 use std::path::PathBuf;
30 30 use std::sync::Mutex;
31 31 use std::time::SystemTime;
32 32
33 33 /// Returns the status of the working directory compared to its parent
34 34 /// changeset.
35 35 ///
36 36 /// This algorithm is based on traversing the filesystem tree (`fs` in function
37 37 /// and variable names) and dirstate tree at the same time. The core of this
38 38 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
39 39 /// and its use of `itertools::merge_join_by`. When reaching a path that only
40 40 /// exists in one of the two trees, depending on information requested by
41 41 /// `options` we may need to traverse the remaining subtree.
42 42 #[timed]
43 pub fn status<'tree, 'on_disk: 'tree>(
44 dmap: &'tree mut DirstateMap<'on_disk>,
43 pub fn status<'dirstate>(
44 dmap: &'dirstate mut DirstateMap,
45 45 matcher: &(dyn Matcher + Sync),
46 46 root_dir: PathBuf,
47 47 ignore_files: Vec<PathBuf>,
48 48 options: StatusOptions,
49 ) -> Result<(DirstateStatus<'on_disk>, Vec<PatternFileWarning>), StatusError> {
49 ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError>
50 {
50 51 // Force the global rayon threadpool to not exceed 16 concurrent threads.
51 52 // This is a stop-gap measure until we figure out why using more than 16
52 53 // threads makes `status` slower for each additional thread.
53 54 // We use `ok()` in case the global threadpool has already been
54 55 // instantiated in `rhg` or some other caller.
55 56 // TODO find the underlying cause and fix it, then remove this.
56 57 rayon::ThreadPoolBuilder::new()
57 58 .num_threads(16)
58 59 .build_global()
59 60 .ok();
60 61
61 62 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
62 63 if options.list_ignored || options.list_unknown {
63 64 let mut hasher = Sha1::new();
64 65 let (ignore_fn, warnings) = get_ignore_function(
65 66 ignore_files,
66 67 &root_dir,
67 68 &mut |pattern_bytes| hasher.update(pattern_bytes),
68 69 )?;
69 70 let new_hash = *hasher.finalize().as_ref();
70 71 let changed = new_hash != dmap.ignore_patterns_hash;
71 72 dmap.ignore_patterns_hash = new_hash;
72 73 (ignore_fn, warnings, Some(changed))
73 74 } else {
74 75 (Box::new(|&_| true), vec![], None)
75 76 };
76 77
77 78 let filesystem_time_at_status_start =
78 79 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
79 80
80 81 // If the repository is under the current directory, prefer using a
81 82 // relative path, so the kernel needs to traverse fewer directory in every
82 83 // call to `read_dir` or `symlink_metadata`.
83 84 // This is effective in the common case where the current directory is the
84 85 // repository root.
85 86
86 87 // TODO: Better yet would be to use libc functions like `openat` and
87 88 // `fstatat` to remove such repeated traversals entirely, but the standard
88 89 // library does not provide APIs based on those.
89 90 // Maybe with a crate like https://crates.io/crates/openat instead?
90 91 let root_dir = if let Some(relative) = std::env::current_dir()
91 92 .ok()
92 93 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
93 94 {
94 95 relative
95 96 } else {
96 97 &root_dir
97 98 };
98 99
99 100 let outcome = DirstateStatus {
100 101 filesystem_time_at_status_start,
101 102 ..Default::default()
102 103 };
103 104 let common = StatusCommon {
104 105 dmap,
105 106 options,
106 107 matcher,
107 108 ignore_fn,
108 109 outcome: Mutex::new(outcome),
109 110 ignore_patterns_have_changed: patterns_changed,
110 111 new_cachable_directories: Default::default(),
111 112 outated_cached_directories: Default::default(),
112 113 filesystem_time_at_status_start,
113 114 };
114 115 let is_at_repo_root = true;
115 116 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
116 117 let has_ignored_ancestor = false;
117 118 let root_cached_mtime = None;
118 119 let root_dir_metadata = None;
119 120 // If the path we have for the repository root is a symlink, do follow it.
120 121 // (As opposed to symlinks within the working directory which are not
121 122 // followed, using `std::fs::symlink_metadata`.)
122 123 common.traverse_fs_directory_and_dirstate(
123 124 has_ignored_ancestor,
124 125 dmap.root.as_ref(),
125 126 hg_path,
126 127 &root_dir,
127 128 root_dir_metadata,
128 129 root_cached_mtime,
129 130 is_at_repo_root,
130 131 )?;
131 132 let mut outcome = common.outcome.into_inner().unwrap();
132 133 let new_cachable = common.new_cachable_directories.into_inner().unwrap();
133 134 let outdated = common.outated_cached_directories.into_inner().unwrap();
134 135
135 136 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
136 137 || !outdated.is_empty()
137 138 || !new_cachable.is_empty();
138 139
139 140 // Remove outdated mtimes before adding new mtimes, in case a given
140 141 // directory is both
141 142 for path in &outdated {
142 143 let node = dmap.get_or_insert(path)?;
143 144 if let NodeData::CachedDirectory { .. } = &node.data {
144 145 node.data = NodeData::None
145 146 }
146 147 }
147 148 for (path, mtime) in &new_cachable {
148 149 let node = dmap.get_or_insert(path)?;
149 150 match &node.data {
150 151 NodeData::Entry(_) => {} // Don’t overwrite an entry
151 152 NodeData::CachedDirectory { .. } | NodeData::None => {
152 153 node.data = NodeData::CachedDirectory { mtime: *mtime }
153 154 }
154 155 }
155 156 }
156 157
157 158 Ok((outcome, warnings))
158 159 }
159 160
160 161 /// Bag of random things needed by various parts of the algorithm. Reduces the
161 162 /// number of parameters passed to functions.
162 163 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
163 164 dmap: &'tree DirstateMap<'on_disk>,
164 165 options: StatusOptions,
165 166 matcher: &'a (dyn Matcher + Sync),
166 167 ignore_fn: IgnoreFnType<'a>,
167 168 outcome: Mutex<DirstateStatus<'on_disk>>,
168 169 new_cachable_directories:
169 170 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
170 171 outated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
171 172
172 173 /// Whether ignore files like `.hgignore` have changed since the previous
173 174 /// time a `status()` call wrote their hash to the dirstate. `None` means
174 175 /// we don’t know as this run doesn’t list either ignored or uknown files
175 176 /// and therefore isn’t reading `.hgignore`.
176 177 ignore_patterns_have_changed: Option<bool>,
177 178
178 179 /// The current time at the start of the `status()` algorithm, as measured
179 180 /// and possibly truncated by the filesystem.
180 181 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
181 182 }
182 183
183 184 enum Outcome {
184 185 Modified,
185 186 Added,
186 187 Removed,
187 188 Deleted,
188 189 Clean,
189 190 Ignored,
190 191 Unknown,
191 192 Unsure,
192 193 }
193 194
194 195 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
195 196 fn push_outcome(
196 197 &self,
197 198 which: Outcome,
198 199 dirstate_node: &NodeRef<'tree, 'on_disk>,
199 200 ) -> Result<(), DirstateV2ParseError> {
200 201 let path = dirstate_node
201 202 .full_path_borrowed(self.dmap.on_disk)?
202 203 .detach_from_tree();
203 204 let copy_source = if self.options.list_copies {
204 205 dirstate_node
205 206 .copy_source_borrowed(self.dmap.on_disk)?
206 207 .map(|source| source.detach_from_tree())
207 208 } else {
208 209 None
209 210 };
210 211 self.push_outcome_common(which, path, copy_source);
211 212 Ok(())
212 213 }
213 214
214 215 fn push_outcome_without_copy_source(
215 216 &self,
216 217 which: Outcome,
217 218 path: &BorrowedPath<'_, 'on_disk>,
218 219 ) {
219 220 self.push_outcome_common(which, path.detach_from_tree(), None)
220 221 }
221 222
222 223 fn push_outcome_common(
223 224 &self,
224 225 which: Outcome,
225 226 path: HgPathCow<'on_disk>,
226 227 copy_source: Option<HgPathCow<'on_disk>>,
227 228 ) {
228 229 let mut outcome = self.outcome.lock().unwrap();
229 230 let vec = match which {
230 231 Outcome::Modified => &mut outcome.modified,
231 232 Outcome::Added => &mut outcome.added,
232 233 Outcome::Removed => &mut outcome.removed,
233 234 Outcome::Deleted => &mut outcome.deleted,
234 235 Outcome::Clean => &mut outcome.clean,
235 236 Outcome::Ignored => &mut outcome.ignored,
236 237 Outcome::Unknown => &mut outcome.unknown,
237 238 Outcome::Unsure => &mut outcome.unsure,
238 239 };
239 240 vec.push(StatusPath { path, copy_source });
240 241 }
241 242
242 243 fn read_dir(
243 244 &self,
244 245 hg_path: &HgPath,
245 246 fs_path: &Path,
246 247 is_at_repo_root: bool,
247 248 ) -> Result<Vec<DirEntry>, ()> {
248 249 DirEntry::read_dir(fs_path, is_at_repo_root)
249 250 .map_err(|error| self.io_error(error, hg_path))
250 251 }
251 252
252 253 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
253 254 let errno = error.raw_os_error().expect("expected real OS error");
254 255 self.outcome
255 256 .lock()
256 257 .unwrap()
257 258 .bad
258 259 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
259 260 }
260 261
261 262 fn check_for_outdated_directory_cache(
262 263 &self,
263 264 dirstate_node: &NodeRef<'tree, 'on_disk>,
264 265 ) -> Result<(), DirstateV2ParseError> {
265 266 if self.ignore_patterns_have_changed == Some(true)
266 267 && dirstate_node.cached_directory_mtime()?.is_some()
267 268 {
268 269 self.outated_cached_directories.lock().unwrap().push(
269 270 dirstate_node
270 271 .full_path_borrowed(self.dmap.on_disk)?
271 272 .detach_from_tree(),
272 273 )
273 274 }
274 275 Ok(())
275 276 }
276 277
277 278 /// If this returns true, we can get accurate results by only using
278 279 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
279 280 /// need to call `read_dir`.
280 281 fn can_skip_fs_readdir(
281 282 &self,
282 283 directory_metadata: Option<&std::fs::Metadata>,
283 284 cached_directory_mtime: Option<TruncatedTimestamp>,
284 285 ) -> bool {
285 286 if !self.options.list_unknown && !self.options.list_ignored {
286 287 // All states that we care about listing have corresponding
287 288 // dirstate entries.
288 289 // This happens for example with `hg status -mard`.
289 290 return true;
290 291 }
291 292 if !self.options.list_ignored
292 293 && self.ignore_patterns_have_changed == Some(false)
293 294 {
294 295 if let Some(cached_mtime) = cached_directory_mtime {
295 296 // The dirstate contains a cached mtime for this directory, set
296 297 // by a previous run of the `status` algorithm which found this
297 298 // directory eligible for `read_dir` caching.
298 299 if let Some(meta) = directory_metadata {
299 300 if cached_mtime
300 301 .likely_equal_to_mtime_of(meta)
301 302 .unwrap_or(false)
302 303 {
303 304 // The mtime of that directory has not changed
304 305 // since then, which means that the results of
305 306 // `read_dir` should also be unchanged.
306 307 return true;
307 308 }
308 309 }
309 310 }
310 311 }
311 312 false
312 313 }
313 314
314 315 /// Returns whether all child entries of the filesystem directory have a
315 316 /// corresponding dirstate node or are ignored.
316 317 fn traverse_fs_directory_and_dirstate(
317 318 &self,
318 319 has_ignored_ancestor: bool,
319 320 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
320 321 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
321 322 directory_fs_path: &Path,
322 323 directory_metadata: Option<&std::fs::Metadata>,
323 324 cached_directory_mtime: Option<TruncatedTimestamp>,
324 325 is_at_repo_root: bool,
325 326 ) -> Result<bool, DirstateV2ParseError> {
326 327 if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime)
327 328 {
328 329 dirstate_nodes
329 330 .par_iter()
330 331 .map(|dirstate_node| {
331 332 let fs_path = directory_fs_path.join(get_path_from_bytes(
332 333 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
333 334 ));
334 335 match std::fs::symlink_metadata(&fs_path) {
335 336 Ok(fs_metadata) => self.traverse_fs_and_dirstate(
336 337 &fs_path,
337 338 &fs_metadata,
338 339 dirstate_node,
339 340 has_ignored_ancestor,
340 341 ),
341 342 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
342 343 self.traverse_dirstate_only(dirstate_node)
343 344 }
344 345 Err(error) => {
345 346 let hg_path =
346 347 dirstate_node.full_path(self.dmap.on_disk)?;
347 348 Ok(self.io_error(error, hg_path))
348 349 }
349 350 }
350 351 })
351 352 .collect::<Result<_, _>>()?;
352 353
353 354 // We don’t know, so conservatively say this isn’t the case
354 355 let children_all_have_dirstate_node_or_are_ignored = false;
355 356
356 357 return Ok(children_all_have_dirstate_node_or_are_ignored);
357 358 }
358 359
359 360 let mut fs_entries = if let Ok(entries) = self.read_dir(
360 361 directory_hg_path,
361 362 directory_fs_path,
362 363 is_at_repo_root,
363 364 ) {
364 365 entries
365 366 } else {
366 367 // Treat an unreadable directory (typically because of insufficient
367 368 // permissions) like an empty directory. `self.read_dir` has
368 369 // already called `self.io_error` so a warning will be emitted.
369 370 Vec::new()
370 371 };
371 372
372 373 // `merge_join_by` requires both its input iterators to be sorted:
373 374
374 375 let dirstate_nodes = dirstate_nodes.sorted();
375 376 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
376 377 // https://github.com/rust-lang/rust/issues/34162
377 378 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
378 379
379 380 // Propagate here any error that would happen inside the comparison
380 381 // callback below
381 382 for dirstate_node in &dirstate_nodes {
382 383 dirstate_node.base_name(self.dmap.on_disk)?;
383 384 }
384 385 itertools::merge_join_by(
385 386 dirstate_nodes,
386 387 &fs_entries,
387 388 |dirstate_node, fs_entry| {
388 389 // This `unwrap` never panics because we already propagated
389 390 // those errors above
390 391 dirstate_node
391 392 .base_name(self.dmap.on_disk)
392 393 .unwrap()
393 394 .cmp(&fs_entry.base_name)
394 395 },
395 396 )
396 397 .par_bridge()
397 398 .map(|pair| {
398 399 use itertools::EitherOrBoth::*;
399 400 let has_dirstate_node_or_is_ignored;
400 401 match pair {
401 402 Both(dirstate_node, fs_entry) => {
402 403 self.traverse_fs_and_dirstate(
403 404 &fs_entry.full_path,
404 405 &fs_entry.metadata,
405 406 dirstate_node,
406 407 has_ignored_ancestor,
407 408 )?;
408 409 has_dirstate_node_or_is_ignored = true
409 410 }
410 411 Left(dirstate_node) => {
411 412 self.traverse_dirstate_only(dirstate_node)?;
412 413 has_dirstate_node_or_is_ignored = true;
413 414 }
414 415 Right(fs_entry) => {
415 416 has_dirstate_node_or_is_ignored = self.traverse_fs_only(
416 417 has_ignored_ancestor,
417 418 directory_hg_path,
418 419 fs_entry,
419 420 )
420 421 }
421 422 }
422 423 Ok(has_dirstate_node_or_is_ignored)
423 424 })
424 425 .try_reduce(|| true, |a, b| Ok(a && b))
425 426 }
426 427
427 428 fn traverse_fs_and_dirstate(
428 429 &self,
429 430 fs_path: &Path,
430 431 fs_metadata: &std::fs::Metadata,
431 432 dirstate_node: NodeRef<'tree, 'on_disk>,
432 433 has_ignored_ancestor: bool,
433 434 ) -> Result<(), DirstateV2ParseError> {
434 435 self.check_for_outdated_directory_cache(&dirstate_node)?;
435 436 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
436 437 let file_type = fs_metadata.file_type();
437 438 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
438 439 if !file_or_symlink {
439 440 // If we previously had a file here, it was removed (with
440 441 // `hg rm` or similar) or deleted before it could be
441 442 // replaced by a directory or something else.
442 443 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
443 444 }
444 445 if file_type.is_dir() {
445 446 if self.options.collect_traversed_dirs {
446 447 self.outcome
447 448 .lock()
448 449 .unwrap()
449 450 .traversed
450 451 .push(hg_path.detach_from_tree())
451 452 }
452 453 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
453 454 let is_at_repo_root = false;
454 455 let children_all_have_dirstate_node_or_are_ignored = self
455 456 .traverse_fs_directory_and_dirstate(
456 457 is_ignored,
457 458 dirstate_node.children(self.dmap.on_disk)?,
458 459 hg_path,
459 460 fs_path,
460 461 Some(fs_metadata),
461 462 dirstate_node.cached_directory_mtime()?,
462 463 is_at_repo_root,
463 464 )?;
464 465 self.maybe_save_directory_mtime(
465 466 children_all_have_dirstate_node_or_are_ignored,
466 467 fs_metadata,
467 468 dirstate_node,
468 469 )?
469 470 } else {
470 471 if file_or_symlink && self.matcher.matches(hg_path) {
471 472 if let Some(state) = dirstate_node.state()? {
472 473 match state {
473 474 EntryState::Added => {
474 475 self.push_outcome(Outcome::Added, &dirstate_node)?
475 476 }
476 477 EntryState::Removed => self
477 478 .push_outcome(Outcome::Removed, &dirstate_node)?,
478 479 EntryState::Merged => self
479 480 .push_outcome(Outcome::Modified, &dirstate_node)?,
480 481 EntryState::Normal => self
481 482 .handle_normal_file(&dirstate_node, fs_metadata)?,
482 483 }
483 484 } else {
484 485 // `node.entry.is_none()` indicates a "directory"
485 486 // node, but the filesystem has a file
486 487 self.mark_unknown_or_ignored(
487 488 has_ignored_ancestor,
488 489 hg_path,
489 490 );
490 491 }
491 492 }
492 493
493 494 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
494 495 {
495 496 self.traverse_dirstate_only(child_node)?
496 497 }
497 498 }
498 499 Ok(())
499 500 }
500 501
501 502 fn maybe_save_directory_mtime(
502 503 &self,
503 504 children_all_have_dirstate_node_or_are_ignored: bool,
504 505 directory_metadata: &std::fs::Metadata,
505 506 dirstate_node: NodeRef<'tree, 'on_disk>,
506 507 ) -> Result<(), DirstateV2ParseError> {
507 508 if !children_all_have_dirstate_node_or_are_ignored {
508 509 return Ok(());
509 510 }
510 511 // All filesystem directory entries from `read_dir` have a
511 512 // corresponding node in the dirstate, so we can reconstitute the
512 513 // names of those entries without calling `read_dir` again.
513 514
514 515 // TODO: use let-else here and below when available:
515 516 // https://github.com/rust-lang/rust/issues/87335
516 517 let status_start = if let Some(status_start) =
517 518 &self.filesystem_time_at_status_start
518 519 {
519 520 status_start
520 521 } else {
521 522 return Ok(());
522 523 };
523 524
524 525 // Although the Rust standard library’s `SystemTime` type
525 526 // has nanosecond precision, the times reported for a
526 527 // directory’s (or file’s) modified time may have lower
527 528 // resolution based on the filesystem (for example ext3
528 529 // only stores integer seconds), kernel (see
529 530 // https://stackoverflow.com/a/14393315/1162888), etc.
530 531 let directory_mtime = if let Ok(option) =
531 532 TruncatedTimestamp::for_reliable_mtime_of(
532 533 directory_metadata,
533 534 status_start,
534 535 ) {
535 536 if let Some(directory_mtime) = option {
536 537 directory_mtime
537 538 } else {
538 539 // The directory was modified too recently,
539 540 // don’t cache its `read_dir` results.
540 541 //
541 542 // 1. A change to this directory (direct child was
542 543 // added or removed) cause its mtime to be set
543 544 // (possibly truncated) to `directory_mtime`
544 545 // 2. This `status` algorithm calls `read_dir`
545 546 // 3. An other change is made to the same directory is
546 547 // made so that calling `read_dir` agin would give
547 548 // different results, but soon enough after 1. that
548 549 // the mtime stays the same
549 550 //
550 551 // On a system where the time resolution poor, this
551 552 // scenario is not unlikely if all three steps are caused
552 553 // by the same script.
553 554 return Ok(());
554 555 }
555 556 } else {
556 557 // OS/libc does not support mtime?
557 558 return Ok(());
558 559 };
559 560 // We’ve observed (through `status_start`) that time has
560 561 // “progressed” since `directory_mtime`, so any further
561 562 // change to this directory is extremely likely to cause a
562 563 // different mtime.
563 564 //
564 565 // Having the same mtime again is not entirely impossible
565 566 // since the system clock is not monotonous. It could jump
566 567 // backward to some point before `directory_mtime`, then a
567 568 // directory change could potentially happen during exactly
568 569 // the wrong tick.
569 570 //
570 571 // We deem this scenario (unlike the previous one) to be
571 572 // unlikely enough in practice.
572 573
573 574 let is_up_to_date =
574 575 if let Some(cached) = dirstate_node.cached_directory_mtime()? {
575 576 cached.likely_equal(directory_mtime)
576 577 } else {
577 578 false
578 579 };
579 580 if !is_up_to_date {
580 581 let hg_path = dirstate_node
581 582 .full_path_borrowed(self.dmap.on_disk)?
582 583 .detach_from_tree();
583 584 self.new_cachable_directories
584 585 .lock()
585 586 .unwrap()
586 587 .push((hg_path, directory_mtime))
587 588 }
588 589 Ok(())
589 590 }
590 591
591 592 /// A file with `EntryState::Normal` in the dirstate was found in the
592 593 /// filesystem
593 594 fn handle_normal_file(
594 595 &self,
595 596 dirstate_node: &NodeRef<'tree, 'on_disk>,
596 597 fs_metadata: &std::fs::Metadata,
597 598 ) -> Result<(), DirstateV2ParseError> {
598 599 // Keep the low 31 bits
599 600 fn truncate_u64(value: u64) -> i32 {
600 601 (value & 0x7FFF_FFFF) as i32
601 602 }
602 603
603 604 let entry = dirstate_node
604 605 .entry()?
605 606 .expect("handle_normal_file called with entry-less node");
606 607 let mode_changed =
607 608 || self.options.check_exec && entry.mode_changed(fs_metadata);
608 609 let size = entry.size();
609 610 let size_changed = size != truncate_u64(fs_metadata.len());
610 611 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
611 612 // issue6456: Size returned may be longer due to encryption
612 613 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
613 614 self.push_outcome(Outcome::Unsure, dirstate_node)?
614 615 } else if dirstate_node.has_copy_source()
615 616 || entry.is_from_other_parent()
616 617 || (size >= 0 && (size_changed || mode_changed()))
617 618 {
618 619 self.push_outcome(Outcome::Modified, dirstate_node)?
619 620 } else {
620 621 let mtime_looks_clean;
621 622 if let Some(dirstate_mtime) = entry.truncated_mtime() {
622 623 let fs_mtime = TruncatedTimestamp::for_mtime_of(fs_metadata)
623 624 .expect("OS/libc does not support mtime?");
624 625 // There might be a change in the future if for example the
625 626 // internal clock become off while process run, but this is a
626 627 // case where the issues the user would face
627 628 // would be a lot worse and there is nothing we
628 629 // can really do.
629 630 mtime_looks_clean = fs_mtime.likely_equal(dirstate_mtime)
630 631 } else {
631 632 // No mtime in the dirstate entry
632 633 mtime_looks_clean = false
633 634 };
634 635 if !mtime_looks_clean {
635 636 self.push_outcome(Outcome::Unsure, dirstate_node)?
636 637 } else if self.options.list_clean {
637 638 self.push_outcome(Outcome::Clean, dirstate_node)?
638 639 }
639 640 }
640 641 Ok(())
641 642 }
642 643
643 644 /// A node in the dirstate tree has no corresponding filesystem entry
644 645 fn traverse_dirstate_only(
645 646 &self,
646 647 dirstate_node: NodeRef<'tree, 'on_disk>,
647 648 ) -> Result<(), DirstateV2ParseError> {
648 649 self.check_for_outdated_directory_cache(&dirstate_node)?;
649 650 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
650 651 dirstate_node
651 652 .children(self.dmap.on_disk)?
652 653 .par_iter()
653 654 .map(|child_node| self.traverse_dirstate_only(child_node))
654 655 .collect()
655 656 }
656 657
657 658 /// A node in the dirstate tree has no corresponding *file* on the
658 659 /// filesystem
659 660 ///
660 661 /// Does nothing on a "directory" node
661 662 fn mark_removed_or_deleted_if_file(
662 663 &self,
663 664 dirstate_node: &NodeRef<'tree, 'on_disk>,
664 665 ) -> Result<(), DirstateV2ParseError> {
665 666 if let Some(state) = dirstate_node.state()? {
666 667 let path = dirstate_node.full_path(self.dmap.on_disk)?;
667 668 if self.matcher.matches(path) {
668 669 if let EntryState::Removed = state {
669 670 self.push_outcome(Outcome::Removed, dirstate_node)?
670 671 } else {
671 672 self.push_outcome(Outcome::Deleted, &dirstate_node)?
672 673 }
673 674 }
674 675 }
675 676 Ok(())
676 677 }
677 678
678 679 /// Something in the filesystem has no corresponding dirstate node
679 680 ///
680 681 /// Returns whether that path is ignored
681 682 fn traverse_fs_only(
682 683 &self,
683 684 has_ignored_ancestor: bool,
684 685 directory_hg_path: &HgPath,
685 686 fs_entry: &DirEntry,
686 687 ) -> bool {
687 688 let hg_path = directory_hg_path.join(&fs_entry.base_name);
688 689 let file_type = fs_entry.metadata.file_type();
689 690 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
690 691 if file_type.is_dir() {
691 692 let is_ignored =
692 693 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
693 694 let traverse_children = if is_ignored {
694 695 // Descendants of an ignored directory are all ignored
695 696 self.options.list_ignored
696 697 } else {
697 698 // Descendants of an unknown directory may be either unknown or
698 699 // ignored
699 700 self.options.list_unknown || self.options.list_ignored
700 701 };
701 702 if traverse_children {
702 703 let is_at_repo_root = false;
703 704 if let Ok(children_fs_entries) = self.read_dir(
704 705 &hg_path,
705 706 &fs_entry.full_path,
706 707 is_at_repo_root,
707 708 ) {
708 709 children_fs_entries.par_iter().for_each(|child_fs_entry| {
709 710 self.traverse_fs_only(
710 711 is_ignored,
711 712 &hg_path,
712 713 child_fs_entry,
713 714 );
714 715 })
715 716 }
716 717 }
717 718 if self.options.collect_traversed_dirs {
718 719 self.outcome.lock().unwrap().traversed.push(hg_path.into())
719 720 }
720 721 is_ignored
721 722 } else {
722 723 if file_or_symlink {
723 724 if self.matcher.matches(&hg_path) {
724 725 self.mark_unknown_or_ignored(
725 726 has_ignored_ancestor,
726 727 &BorrowedPath::InMemory(&hg_path),
727 728 )
728 729 } else {
729 730 // We haven’t computed whether this path is ignored. It
730 731 // might not be, and a future run of status might have a
731 732 // different matcher that matches it. So treat it as not
732 733 // ignored. That is, inhibit readdir caching of the parent
733 734 // directory.
734 735 false
735 736 }
736 737 } else {
737 738 // This is neither a directory, a plain file, or a symlink.
738 739 // Treat it like an ignored file.
739 740 true
740 741 }
741 742 }
742 743 }
743 744
744 745 /// Returns whether that path is ignored
745 746 fn mark_unknown_or_ignored(
746 747 &self,
747 748 has_ignored_ancestor: bool,
748 749 hg_path: &BorrowedPath<'_, 'on_disk>,
749 750 ) -> bool {
750 751 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
751 752 if is_ignored {
752 753 if self.options.list_ignored {
753 754 self.push_outcome_without_copy_source(
754 755 Outcome::Ignored,
755 756 hg_path,
756 757 )
757 758 }
758 759 } else {
759 760 if self.options.list_unknown {
760 761 self.push_outcome_without_copy_source(
761 762 Outcome::Unknown,
762 763 hg_path,
763 764 )
764 765 }
765 766 }
766 767 is_ignored
767 768 }
768 769 }
769 770
770 771 struct DirEntry {
771 772 base_name: HgPathBuf,
772 773 full_path: PathBuf,
773 774 metadata: std::fs::Metadata,
774 775 }
775 776
776 777 impl DirEntry {
777 778 /// Returns **unsorted** entries in the given directory, with name and
778 779 /// metadata.
779 780 ///
780 781 /// If a `.hg` sub-directory is encountered:
781 782 ///
782 783 /// * At the repository root, ignore that sub-directory
783 784 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
784 785 /// list instead.
785 786 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
786 787 // `read_dir` returns a "not found" error for the empty path
787 788 let at_cwd = path == Path::new("");
788 789 let read_dir_path = if at_cwd { Path::new(".") } else { path };
789 790 let mut results = Vec::new();
790 791 for entry in read_dir_path.read_dir()? {
791 792 let entry = entry?;
792 793 let metadata = match entry.metadata() {
793 794 Ok(v) => v,
794 795 Err(e) => {
795 796 // race with file deletion?
796 797 if e.kind() == std::io::ErrorKind::NotFound {
797 798 continue;
798 799 } else {
799 800 return Err(e);
800 801 }
801 802 }
802 803 };
803 804 let file_name = entry.file_name();
804 805 // FIXME don't do this when cached
805 806 if file_name == ".hg" {
806 807 if is_at_repo_root {
807 808 // Skip the repo’s own .hg (might be a symlink)
808 809 continue;
809 810 } else if metadata.is_dir() {
810 811 // A .hg sub-directory at another location means a subrepo,
811 812 // skip it entirely.
812 813 return Ok(Vec::new());
813 814 }
814 815 }
815 816 let full_path = if at_cwd {
816 817 file_name.clone().into()
817 818 } else {
818 819 entry.path()
819 820 };
820 821 let base_name = get_bytes_from_os_string(file_name).into();
821 822 results.push(DirEntry {
822 823 base_name,
823 824 full_path,
824 825 metadata,
825 826 })
826 827 }
827 828 Ok(results)
828 829 }
829 830 }
830 831
831 832 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
832 833 /// of the give repository.
833 834 ///
834 835 /// This is similar to `SystemTime::now()`, with the result truncated to the
835 836 /// same time resolution as other files’ modification times. Using `.hg`
836 837 /// instead of the system’s default temporary directory (such as `/tmp`) makes
837 838 /// it more likely the temporary file is in the same disk partition as contents
838 839 /// of the working directory, which can matter since different filesystems may
839 840 /// store timestamps with different resolutions.
840 841 ///
841 842 /// This may fail, typically if we lack write permissions. In that case we
842 843 /// should continue the `status()` algoritm anyway and consider the current
843 844 /// date/time to be unknown.
844 845 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
845 846 tempfile::tempfile_in(repo_root.join(".hg"))?
846 847 .metadata()?
847 848 .modified()
848 849 }
@@ -1,540 +1,533 b''
1 1 use crate::changelog::Changelog;
2 2 use crate::config::{Config, ConfigError, ConfigParseError};
3 3 use crate::dirstate::DirstateParents;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
5 4 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
6 5 use crate::dirstate_tree::owning::OwningDirstateMap;
7 6 use crate::errors::HgResultExt;
8 7 use crate::errors::{HgError, IoResultExt};
9 8 use crate::exit_codes;
10 9 use crate::lock::{try_with_lock_no_wait, LockError};
11 10 use crate::manifest::{Manifest, Manifestlog};
12 11 use crate::revlog::filelog::Filelog;
13 12 use crate::revlog::revlog::RevlogError;
14 13 use crate::utils::files::get_path_from_bytes;
15 14 use crate::utils::hg_path::HgPath;
16 15 use crate::utils::SliceExt;
17 16 use crate::vfs::{is_dir, is_file, Vfs};
18 17 use crate::{requirements, NodePrefix};
19 18 use crate::{DirstateError, Revision};
20 19 use std::cell::{Ref, RefCell, RefMut};
21 20 use std::collections::HashSet;
22 21 use std::io::Seek;
23 22 use std::io::SeekFrom;
24 23 use std::io::Write as IoWrite;
25 24 use std::path::{Path, PathBuf};
26 25
27 26 /// A repository on disk
28 27 pub struct Repo {
29 28 working_directory: PathBuf,
30 29 dot_hg: PathBuf,
31 30 store: PathBuf,
32 31 requirements: HashSet<String>,
33 32 config: Config,
34 33 dirstate_parents: LazyCell<DirstateParents, HgError>,
35 34 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
36 35 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
37 36 changelog: LazyCell<Changelog, HgError>,
38 37 manifestlog: LazyCell<Manifestlog, HgError>,
39 38 }
40 39
41 40 #[derive(Debug, derive_more::From)]
42 41 pub enum RepoError {
43 42 NotFound {
44 43 at: PathBuf,
45 44 },
46 45 #[from]
47 46 ConfigParseError(ConfigParseError),
48 47 #[from]
49 48 Other(HgError),
50 49 }
51 50
52 51 impl From<ConfigError> for RepoError {
53 52 fn from(error: ConfigError) -> Self {
54 53 match error {
55 54 ConfigError::Parse(error) => error.into(),
56 55 ConfigError::Other(error) => error.into(),
57 56 }
58 57 }
59 58 }
60 59
61 60 impl Repo {
62 61 /// tries to find nearest repository root in current working directory or
63 62 /// its ancestors
64 63 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
65 64 let current_directory = crate::utils::current_dir()?;
66 65 // ancestors() is inclusive: it first yields `current_directory`
67 66 // as-is.
68 67 for ancestor in current_directory.ancestors() {
69 68 if is_dir(ancestor.join(".hg"))? {
70 69 return Ok(ancestor.to_path_buf());
71 70 }
72 71 }
73 72 return Err(RepoError::NotFound {
74 73 at: current_directory,
75 74 });
76 75 }
77 76
78 77 /// Find a repository, either at the given path (which must contain a `.hg`
79 78 /// sub-directory) or by searching the current directory and its
80 79 /// ancestors.
81 80 ///
82 81 /// A method with two very different "modes" like this usually a code smell
83 82 /// to make two methods instead, but in this case an `Option` is what rhg
84 83 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
85 84 /// Having two methods would just move that `if` to almost all callers.
86 85 pub fn find(
87 86 config: &Config,
88 87 explicit_path: Option<PathBuf>,
89 88 ) -> Result<Self, RepoError> {
90 89 if let Some(root) = explicit_path {
91 90 if is_dir(root.join(".hg"))? {
92 91 Self::new_at_path(root.to_owned(), config)
93 92 } else if is_file(&root)? {
94 93 Err(HgError::unsupported("bundle repository").into())
95 94 } else {
96 95 Err(RepoError::NotFound {
97 96 at: root.to_owned(),
98 97 })
99 98 }
100 99 } else {
101 100 let root = Self::find_repo_root()?;
102 101 Self::new_at_path(root, config)
103 102 }
104 103 }
105 104
106 105 /// To be called after checking that `.hg` is a sub-directory
107 106 fn new_at_path(
108 107 working_directory: PathBuf,
109 108 config: &Config,
110 109 ) -> Result<Self, RepoError> {
111 110 let dot_hg = working_directory.join(".hg");
112 111
113 112 let mut repo_config_files = Vec::new();
114 113 repo_config_files.push(dot_hg.join("hgrc"));
115 114 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
116 115
117 116 let hg_vfs = Vfs { base: &dot_hg };
118 117 let mut reqs = requirements::load_if_exists(hg_vfs)?;
119 118 let relative =
120 119 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
121 120 let shared =
122 121 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
123 122
124 123 // From `mercurial/localrepo.py`:
125 124 //
126 125 // if .hg/requires contains the sharesafe requirement, it means
127 126 // there exists a `.hg/store/requires` too and we should read it
128 127 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
129 128 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
130 129 // is not present, refer checkrequirementscompat() for that
131 130 //
132 131 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
133 132 // repository was shared the old way. We check the share source
134 133 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
135 134 // current repository needs to be reshared
136 135 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
137 136
138 137 let store_path;
139 138 if !shared {
140 139 store_path = dot_hg.join("store");
141 140 } else {
142 141 let bytes = hg_vfs.read("sharedpath")?;
143 142 let mut shared_path =
144 143 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
145 144 .to_owned();
146 145 if relative {
147 146 shared_path = dot_hg.join(shared_path)
148 147 }
149 148 if !is_dir(&shared_path)? {
150 149 return Err(HgError::corrupted(format!(
151 150 ".hg/sharedpath points to nonexistent directory {}",
152 151 shared_path.display()
153 152 ))
154 153 .into());
155 154 }
156 155
157 156 store_path = shared_path.join("store");
158 157
159 158 let source_is_share_safe =
160 159 requirements::load(Vfs { base: &shared_path })?
161 160 .contains(requirements::SHARESAFE_REQUIREMENT);
162 161
163 162 if share_safe && !source_is_share_safe {
164 163 return Err(match config
165 164 .get(b"share", b"safe-mismatch.source-not-safe")
166 165 {
167 166 Some(b"abort") | None => HgError::abort(
168 167 "abort: share source does not support share-safe requirement\n\
169 168 (see `hg help config.format.use-share-safe` for more information)",
170 169 exit_codes::ABORT,
171 170 ),
172 171 _ => HgError::unsupported("share-safe downgrade"),
173 172 }
174 173 .into());
175 174 } else if source_is_share_safe && !share_safe {
176 175 return Err(
177 176 match config.get(b"share", b"safe-mismatch.source-safe") {
178 177 Some(b"abort") | None => HgError::abort(
179 178 "abort: version mismatch: source uses share-safe \
180 179 functionality while the current share does not\n\
181 180 (see `hg help config.format.use-share-safe` for more information)",
182 181 exit_codes::ABORT,
183 182 ),
184 183 _ => HgError::unsupported("share-safe upgrade"),
185 184 }
186 185 .into(),
187 186 );
188 187 }
189 188
190 189 if share_safe {
191 190 repo_config_files.insert(0, shared_path.join("hgrc"))
192 191 }
193 192 }
194 193 if share_safe {
195 194 reqs.extend(requirements::load(Vfs { base: &store_path })?);
196 195 }
197 196
198 197 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
199 198 config.combine_with_repo(&repo_config_files)?
200 199 } else {
201 200 config.clone()
202 201 };
203 202
204 203 let repo = Self {
205 204 requirements: reqs,
206 205 working_directory,
207 206 store: store_path,
208 207 dot_hg,
209 208 config: repo_config,
210 209 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
211 210 dirstate_data_file_uuid: LazyCell::new(
212 211 Self::read_dirstate_data_file_uuid,
213 212 ),
214 213 dirstate_map: LazyCell::new(Self::new_dirstate_map),
215 214 changelog: LazyCell::new(Changelog::open),
216 215 manifestlog: LazyCell::new(Manifestlog::open),
217 216 };
218 217
219 218 requirements::check(&repo)?;
220 219
221 220 Ok(repo)
222 221 }
223 222
224 223 pub fn working_directory_path(&self) -> &Path {
225 224 &self.working_directory
226 225 }
227 226
228 227 pub fn requirements(&self) -> &HashSet<String> {
229 228 &self.requirements
230 229 }
231 230
232 231 pub fn config(&self) -> &Config {
233 232 &self.config
234 233 }
235 234
236 235 /// For accessing repository files (in `.hg`), except for the store
237 236 /// (`.hg/store`).
238 237 pub fn hg_vfs(&self) -> Vfs<'_> {
239 238 Vfs { base: &self.dot_hg }
240 239 }
241 240
242 241 /// For accessing repository store files (in `.hg/store`)
243 242 pub fn store_vfs(&self) -> Vfs<'_> {
244 243 Vfs { base: &self.store }
245 244 }
246 245
247 246 /// For accessing the working copy
248 247 pub fn working_directory_vfs(&self) -> Vfs<'_> {
249 248 Vfs {
250 249 base: &self.working_directory,
251 250 }
252 251 }
253 252
254 253 pub fn try_with_wlock_no_wait<R>(
255 254 &self,
256 255 f: impl FnOnce() -> R,
257 256 ) -> Result<R, LockError> {
258 257 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
259 258 }
260 259
261 260 pub fn has_dirstate_v2(&self) -> bool {
262 261 self.requirements
263 262 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
264 263 }
265 264
266 265 pub fn has_sparse(&self) -> bool {
267 266 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
268 267 }
269 268
270 269 pub fn has_narrow(&self) -> bool {
271 270 self.requirements.contains(requirements::NARROW_REQUIREMENT)
272 271 }
273 272
274 273 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
275 274 Ok(self
276 275 .hg_vfs()
277 276 .read("dirstate")
278 277 .io_not_found_as_none()?
279 278 .unwrap_or(Vec::new()))
280 279 }
281 280
282 281 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
283 282 Ok(*self.dirstate_parents.get_or_init(self)?)
284 283 }
285 284
286 285 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
287 286 let dirstate = self.dirstate_file_contents()?;
288 287 let parents = if dirstate.is_empty() {
289 288 if self.has_dirstate_v2() {
290 289 self.dirstate_data_file_uuid.set(None);
291 290 }
292 291 DirstateParents::NULL
293 292 } else if self.has_dirstate_v2() {
294 293 let docket =
295 294 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
296 295 self.dirstate_data_file_uuid
297 296 .set(Some(docket.uuid.to_owned()));
298 297 docket.parents()
299 298 } else {
300 299 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
301 300 .clone()
302 301 };
303 302 self.dirstate_parents.set(parents);
304 303 Ok(parents)
305 304 }
306 305
307 306 fn read_dirstate_data_file_uuid(
308 307 &self,
309 308 ) -> Result<Option<Vec<u8>>, HgError> {
310 309 assert!(
311 310 self.has_dirstate_v2(),
312 311 "accessing dirstate data file ID without dirstate-v2"
313 312 );
314 313 let dirstate = self.dirstate_file_contents()?;
315 314 if dirstate.is_empty() {
316 315 self.dirstate_parents.set(DirstateParents::NULL);
317 316 Ok(None)
318 317 } else {
319 318 let docket =
320 319 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
321 320 self.dirstate_parents.set(docket.parents());
322 321 Ok(Some(docket.uuid.to_owned()))
323 322 }
324 323 }
325 324
326 325 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
327 326 let dirstate_file_contents = self.dirstate_file_contents()?;
328 327 if dirstate_file_contents.is_empty() {
329 328 self.dirstate_parents.set(DirstateParents::NULL);
330 329 if self.has_dirstate_v2() {
331 330 self.dirstate_data_file_uuid.set(None);
332 331 }
333 332 Ok(OwningDirstateMap::new_empty(Vec::new()))
334 333 } else if self.has_dirstate_v2() {
335 334 let docket = crate::dirstate_tree::on_disk::read_docket(
336 335 &dirstate_file_contents,
337 336 )?;
338 337 self.dirstate_parents.set(docket.parents());
339 338 self.dirstate_data_file_uuid
340 339 .set(Some(docket.uuid.to_owned()));
341 340 let data_size = docket.data_size();
342 341 let metadata = docket.tree_metadata();
343 let mut map = if let Some(data_mmap) = self
342 if let Some(data_mmap) = self
344 343 .hg_vfs()
345 344 .mmap_open(docket.data_filename())
346 345 .io_not_found_as_none()?
347 346 {
348 OwningDirstateMap::new_empty(data_mmap)
347 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
349 348 } else {
350 OwningDirstateMap::new_empty(Vec::new())
351 };
352 let (on_disk, placeholder) = map.get_pair_mut();
353 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
354 Ok(map)
349 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
350 }
355 351 } else {
356 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
357 let (on_disk, placeholder) = map.get_pair_mut();
358 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
359 self.dirstate_parents
360 .set(parents.unwrap_or(DirstateParents::NULL));
361 *placeholder = inner;
352 let (map, parents) =
353 OwningDirstateMap::new_v1(dirstate_file_contents)?;
354 self.dirstate_parents.set(parents);
362 355 Ok(map)
363 356 }
364 357 }
365 358
366 359 pub fn dirstate_map(
367 360 &self,
368 361 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
369 362 self.dirstate_map.get_or_init(self)
370 363 }
371 364
372 365 pub fn dirstate_map_mut(
373 366 &self,
374 367 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
375 368 self.dirstate_map.get_mut_or_init(self)
376 369 }
377 370
378 371 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
379 372 self.changelog.get_or_init(self)
380 373 }
381 374
382 375 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
383 376 self.changelog.get_mut_or_init(self)
384 377 }
385 378
386 379 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
387 380 self.manifestlog.get_or_init(self)
388 381 }
389 382
390 383 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
391 384 self.manifestlog.get_mut_or_init(self)
392 385 }
393 386
394 387 /// Returns the manifest of the *changeset* with the given node ID
395 388 pub fn manifest_for_node(
396 389 &self,
397 390 node: impl Into<NodePrefix>,
398 391 ) -> Result<Manifest, RevlogError> {
399 392 self.manifestlog()?.data_for_node(
400 393 self.changelog()?
401 394 .data_for_node(node.into())?
402 395 .manifest_node()?
403 396 .into(),
404 397 )
405 398 }
406 399
407 400 /// Returns the manifest of the *changeset* with the given revision number
408 401 pub fn manifest_for_rev(
409 402 &self,
410 403 revision: Revision,
411 404 ) -> Result<Manifest, RevlogError> {
412 405 self.manifestlog()?.data_for_node(
413 406 self.changelog()?
414 407 .data_for_rev(revision)?
415 408 .manifest_node()?
416 409 .into(),
417 410 )
418 411 }
419 412
420 413 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
421 414 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
422 415 Ok(entry.state().is_tracked())
423 416 } else {
424 417 Ok(false)
425 418 }
426 419 }
427 420
428 421 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
429 422 Filelog::open(self, path)
430 423 }
431 424
432 425 /// Write to disk any updates that were made through `dirstate_map_mut`.
433 426 ///
434 427 /// The "wlock" must be held while calling this.
435 428 /// See for example `try_with_wlock_no_wait`.
436 429 ///
437 430 /// TODO: have a `WritableRepo` type only accessible while holding the
438 431 /// lock?
439 432 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
440 433 let map = self.dirstate_map()?;
441 434 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
442 435 // it’s unset
443 436 let parents = self.dirstate_parents()?;
444 437 let packed_dirstate = if self.has_dirstate_v2() {
445 438 let uuid = self.dirstate_data_file_uuid.get_or_init(self)?;
446 439 let mut uuid = uuid.as_ref();
447 440 let can_append = uuid.is_some();
448 441 let (data, tree_metadata, append) = map.pack_v2(can_append)?;
449 442 if !append {
450 443 uuid = None
451 444 }
452 445 let uuid = if let Some(uuid) = uuid {
453 446 std::str::from_utf8(uuid)
454 447 .map_err(|_| {
455 448 HgError::corrupted("non-UTF-8 dirstate data file ID")
456 449 })?
457 450 .to_owned()
458 451 } else {
459 452 DirstateDocket::new_uid()
460 453 };
461 454 let data_filename = format!("dirstate.{}", uuid);
462 455 let data_filename = self.hg_vfs().join(data_filename);
463 456 let mut options = std::fs::OpenOptions::new();
464 457 if append {
465 458 options.append(true);
466 459 } else {
467 460 options.write(true).create_new(true);
468 461 }
469 462 let data_size = (|| {
470 463 // TODO: loop and try another random ID if !append and this
471 464 // returns `ErrorKind::AlreadyExists`? Collision chance of two
472 465 // random IDs is one in 2**32
473 466 let mut file = options.open(&data_filename)?;
474 467 file.write_all(&data)?;
475 468 file.flush()?;
476 469 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+
477 470 file.seek(SeekFrom::Current(0))
478 471 })()
479 472 .when_writing_file(&data_filename)?;
480 473 DirstateDocket::serialize(
481 474 parents,
482 475 tree_metadata,
483 476 data_size,
484 477 uuid.as_bytes(),
485 478 )
486 479 .map_err(|_: std::num::TryFromIntError| {
487 480 HgError::corrupted("overflow in dirstate docket serialization")
488 481 })?
489 482 } else {
490 483 map.pack_v1(parents)?
491 484 };
492 485 self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?;
493 486 Ok(())
494 487 }
495 488 }
496 489
497 490 /// Lazily-initialized component of `Repo` with interior mutability
498 491 ///
499 492 /// This differs from `OnceCell` in that the value can still be "deinitialized"
500 493 /// later by setting its inner `Option` to `None`.
501 494 struct LazyCell<T, E> {
502 495 value: RefCell<Option<T>>,
503 496 // `Fn`s that don’t capture environment are zero-size, so this box does
504 497 // not allocate:
505 498 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
506 499 }
507 500
508 501 impl<T, E> LazyCell<T, E> {
509 502 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
510 503 Self {
511 504 value: RefCell::new(None),
512 505 init: Box::new(init),
513 506 }
514 507 }
515 508
516 509 fn set(&self, value: T) {
517 510 *self.value.borrow_mut() = Some(value)
518 511 }
519 512
520 513 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
521 514 let mut borrowed = self.value.borrow();
522 515 if borrowed.is_none() {
523 516 drop(borrowed);
524 517 // Only use `borrow_mut` if it is really needed to avoid panic in
525 518 // case there is another outstanding borrow but mutation is not
526 519 // needed.
527 520 *self.value.borrow_mut() = Some((self.init)(repo)?);
528 521 borrowed = self.value.borrow()
529 522 }
530 523 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
531 524 }
532 525
533 526 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
534 527 let mut borrowed = self.value.borrow_mut();
535 528 if borrowed.is_none() {
536 529 *borrowed = Some((self.init)(repo)?);
537 530 }
538 531 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
539 532 }
540 533 }
@@ -1,499 +1,490 b''
1 1 // dirstate_map.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 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
9 9 //! `hg-core` package.
10 10
11 11 use std::cell::{RefCell, RefMut};
12 12 use std::convert::TryInto;
13 13
14 14 use cpython::{
15 15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
16 16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
17 17 };
18 18
19 19 use crate::{
20 20 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
21 21 dirstate::item::DirstateItem,
22 22 pybytes_deref::PyBytesDeref,
23 23 };
24 24 use hg::{
25 25 dirstate::StateMapIter,
26 dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap,
27 26 dirstate_tree::on_disk::DirstateV2ParseError,
28 27 dirstate_tree::owning::OwningDirstateMap,
29 28 revlog::Node,
30 29 utils::files::normalize_case,
31 30 utils::hg_path::{HgPath, HgPathBuf},
32 31 DirstateEntry, DirstateError, DirstateParents, EntryState,
33 32 };
34 33
35 34 // TODO
36 35 // This object needs to share references to multiple members of its Rust
37 36 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
38 37 // Right now `CopyMap` is done, but it needs to have an explicit reference
39 38 // to `RustDirstateMap` which itself needs to have an encapsulation for
40 39 // every method in `CopyMap` (copymapcopy, etc.).
41 40 // This is ugly and hard to maintain.
42 41 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
43 42 // `py_class!` is already implemented and does not mention
44 43 // `RustDirstateMap`, rightfully so.
45 44 // All attributes also have to have a separate refcount data attribute for
46 45 // leaks, with all methods that go along for reference sharing.
47 46 py_class!(pub class DirstateMap |py| {
48 47 @shared data inner: OwningDirstateMap;
49 48
50 49 /// Returns a `(dirstate_map, parents)` tuple
51 50 @staticmethod
52 51 def new_v1(
53 52 on_disk: PyBytes,
54 53 ) -> PyResult<PyObject> {
55 54 let on_disk = PyBytesDeref::new(py, on_disk);
56 let mut map = OwningDirstateMap::new_empty(on_disk);
57 let (on_disk, map_placeholder) = map.get_pair_mut();
58
59 let (actual_map, parents) = TreeDirstateMap::new_v1(on_disk)
55 let (map, parents) = OwningDirstateMap::new_v1(on_disk)
60 56 .map_err(|e| dirstate_error(py, e))?;
61 *map_placeholder = actual_map;
62 57 let map = Self::create_instance(py, map)?;
63 let parents = parents.map(|p| {
64 let p1 = PyBytes::new(py, p.p1.as_bytes());
65 let p2 = PyBytes::new(py, p.p2.as_bytes());
66 (p1, p2)
67 });
58 let p1 = PyBytes::new(py, parents.p1.as_bytes());
59 let p2 = PyBytes::new(py, parents.p2.as_bytes());
60 let parents = (p1, p2);
68 61 Ok((map, parents).to_py_object(py).into_object())
69 62 }
70 63
71 64 /// Returns a DirstateMap
72 65 @staticmethod
73 66 def new_v2(
74 67 on_disk: PyBytes,
75 68 data_size: usize,
76 69 tree_metadata: PyBytes,
77 70 ) -> PyResult<PyObject> {
78 71 let dirstate_error = |e: DirstateError| {
79 72 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
80 73 };
81 74 let on_disk = PyBytesDeref::new(py, on_disk);
82 let mut map = OwningDirstateMap::new_empty(on_disk);
83 let (on_disk, map_placeholder) = map.get_pair_mut();
84 *map_placeholder = TreeDirstateMap::new_v2(
75 let map = OwningDirstateMap::new_v2(
85 76 on_disk, data_size, tree_metadata.data(py),
86 77 ).map_err(dirstate_error)?;
87 78 let map = Self::create_instance(py, map)?;
88 79 Ok(map.into_object())
89 80 }
90 81
91 82 def clear(&self) -> PyResult<PyObject> {
92 83 self.inner(py).borrow_mut().clear();
93 84 Ok(py.None())
94 85 }
95 86
96 87 def get(
97 88 &self,
98 89 key: PyObject,
99 90 default: Option<PyObject> = None
100 91 ) -> PyResult<Option<PyObject>> {
101 92 let key = key.extract::<PyBytes>(py)?;
102 93 match self
103 94 .inner(py)
104 95 .borrow()
105 96 .get(HgPath::new(key.data(py)))
106 97 .map_err(|e| v2_error(py, e))?
107 98 {
108 99 Some(entry) => {
109 100 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
110 101 },
111 102 None => Ok(default)
112 103 }
113 104 }
114 105
115 106 def set_dirstate_item(
116 107 &self,
117 108 path: PyObject,
118 109 item: DirstateItem
119 110 ) -> PyResult<PyObject> {
120 111 let f = path.extract::<PyBytes>(py)?;
121 112 let filename = HgPath::new(f.data(py));
122 113 self.inner(py)
123 114 .borrow_mut()
124 115 .set_entry(filename, item.get_entry(py))
125 116 .map_err(|e| v2_error(py, e))?;
126 117 Ok(py.None())
127 118 }
128 119
129 120 def addfile(
130 121 &self,
131 122 f: PyBytes,
132 123 item: DirstateItem,
133 124 ) -> PyResult<PyNone> {
134 125 let filename = HgPath::new(f.data(py));
135 126 let entry = item.get_entry(py);
136 127 self.inner(py)
137 128 .borrow_mut()
138 129 .add_file(filename, entry)
139 130 .map_err(|e |dirstate_error(py, e))?;
140 131 Ok(PyNone)
141 132 }
142 133
143 134 def removefile(
144 135 &self,
145 136 f: PyObject,
146 137 in_merge: PyObject
147 138 ) -> PyResult<PyObject> {
148 139 self.inner(py).borrow_mut()
149 140 .remove_file(
150 141 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
151 142 in_merge.extract::<PyBool>(py)?.is_true(),
152 143 )
153 144 .or_else(|_| {
154 145 Err(PyErr::new::<exc::OSError, _>(
155 146 py,
156 147 "Dirstate error".to_string(),
157 148 ))
158 149 })?;
159 150 Ok(py.None())
160 151 }
161 152
162 153 def drop_item_and_copy_source(
163 154 &self,
164 155 f: PyBytes,
165 156 ) -> PyResult<PyNone> {
166 157 self.inner(py)
167 158 .borrow_mut()
168 159 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
169 160 .map_err(|e |dirstate_error(py, e))?;
170 161 Ok(PyNone)
171 162 }
172 163
173 164 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
174 165 let d = d.extract::<PyBytes>(py)?;
175 166 Ok(self.inner(py).borrow_mut()
176 167 .has_tracked_dir(HgPath::new(d.data(py)))
177 168 .map_err(|e| {
178 169 PyErr::new::<exc::ValueError, _>(py, e.to_string())
179 170 })?
180 171 .to_py_object(py))
181 172 }
182 173
183 174 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
184 175 let d = d.extract::<PyBytes>(py)?;
185 176 Ok(self.inner(py).borrow_mut()
186 177 .has_dir(HgPath::new(d.data(py)))
187 178 .map_err(|e| {
188 179 PyErr::new::<exc::ValueError, _>(py, e.to_string())
189 180 })?
190 181 .to_py_object(py))
191 182 }
192 183
193 184 def write_v1(
194 185 &self,
195 186 p1: PyObject,
196 187 p2: PyObject,
197 188 ) -> PyResult<PyBytes> {
198 189 let inner = self.inner(py).borrow();
199 190 let parents = DirstateParents {
200 191 p1: extract_node_id(py, &p1)?,
201 192 p2: extract_node_id(py, &p2)?,
202 193 };
203 194 let result = inner.pack_v1(parents);
204 195 match result {
205 196 Ok(packed) => Ok(PyBytes::new(py, &packed)),
206 197 Err(_) => Err(PyErr::new::<exc::OSError, _>(
207 198 py,
208 199 "Dirstate error".to_string(),
209 200 )),
210 201 }
211 202 }
212 203
213 204 /// Returns new data together with whether that data should be appended to
214 205 /// the existing data file whose content is at `self.on_disk` (True),
215 206 /// instead of written to a new data file (False).
216 207 def write_v2(
217 208 &self,
218 209 can_append: bool,
219 210 ) -> PyResult<PyObject> {
220 211 let inner = self.inner(py).borrow();
221 212 let result = inner.pack_v2(can_append);
222 213 match result {
223 214 Ok((packed, tree_metadata, append)) => {
224 215 let packed = PyBytes::new(py, &packed);
225 216 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
226 217 let tuple = (packed, tree_metadata, append);
227 218 Ok(tuple.to_py_object(py).into_object())
228 219 },
229 220 Err(_) => Err(PyErr::new::<exc::OSError, _>(
230 221 py,
231 222 "Dirstate error".to_string(),
232 223 )),
233 224 }
234 225 }
235 226
236 227 def filefoldmapasdict(&self) -> PyResult<PyDict> {
237 228 let dict = PyDict::new(py);
238 229 for item in self.inner(py).borrow_mut().iter() {
239 230 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
240 231 if entry.state() != EntryState::Removed {
241 232 let key = normalize_case(path);
242 233 let value = path;
243 234 dict.set_item(
244 235 py,
245 236 PyBytes::new(py, key.as_bytes()).into_object(),
246 237 PyBytes::new(py, value.as_bytes()).into_object(),
247 238 )?;
248 239 }
249 240 }
250 241 Ok(dict)
251 242 }
252 243
253 244 def __len__(&self) -> PyResult<usize> {
254 245 Ok(self.inner(py).borrow().len())
255 246 }
256 247
257 248 def __contains__(&self, key: PyObject) -> PyResult<bool> {
258 249 let key = key.extract::<PyBytes>(py)?;
259 250 self.inner(py)
260 251 .borrow()
261 252 .contains_key(HgPath::new(key.data(py)))
262 253 .map_err(|e| v2_error(py, e))
263 254 }
264 255
265 256 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
266 257 let key = key.extract::<PyBytes>(py)?;
267 258 let key = HgPath::new(key.data(py));
268 259 match self
269 260 .inner(py)
270 261 .borrow()
271 262 .get(key)
272 263 .map_err(|e| v2_error(py, e))?
273 264 {
274 265 Some(entry) => {
275 266 Ok(DirstateItem::new_as_pyobject(py, entry)?)
276 267 },
277 268 None => Err(PyErr::new::<exc::KeyError, _>(
278 269 py,
279 270 String::from_utf8_lossy(key.as_bytes()),
280 271 )),
281 272 }
282 273 }
283 274
284 275 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
285 276 let leaked_ref = self.inner(py).leak_immutable();
286 277 DirstateMapKeysIterator::from_inner(
287 278 py,
288 279 unsafe { leaked_ref.map(py, |o| o.iter()) },
289 280 )
290 281 }
291 282
292 283 def items(&self) -> PyResult<DirstateMapItemsIterator> {
293 284 let leaked_ref = self.inner(py).leak_immutable();
294 285 DirstateMapItemsIterator::from_inner(
295 286 py,
296 287 unsafe { leaked_ref.map(py, |o| o.iter()) },
297 288 )
298 289 }
299 290
300 291 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
301 292 let leaked_ref = self.inner(py).leak_immutable();
302 293 DirstateMapKeysIterator::from_inner(
303 294 py,
304 295 unsafe { leaked_ref.map(py, |o| o.iter()) },
305 296 )
306 297 }
307 298
308 299 // TODO all copymap* methods, see docstring above
309 300 def copymapcopy(&self) -> PyResult<PyDict> {
310 301 let dict = PyDict::new(py);
311 302 for item in self.inner(py).borrow().copy_map_iter() {
312 303 let (key, value) = item.map_err(|e| v2_error(py, e))?;
313 304 dict.set_item(
314 305 py,
315 306 PyBytes::new(py, key.as_bytes()),
316 307 PyBytes::new(py, value.as_bytes()),
317 308 )?;
318 309 }
319 310 Ok(dict)
320 311 }
321 312
322 313 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
323 314 let key = key.extract::<PyBytes>(py)?;
324 315 match self
325 316 .inner(py)
326 317 .borrow()
327 318 .copy_map_get(HgPath::new(key.data(py)))
328 319 .map_err(|e| v2_error(py, e))?
329 320 {
330 321 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
331 322 None => Err(PyErr::new::<exc::KeyError, _>(
332 323 py,
333 324 String::from_utf8_lossy(key.data(py)),
334 325 )),
335 326 }
336 327 }
337 328 def copymap(&self) -> PyResult<CopyMap> {
338 329 CopyMap::from_inner(py, self.clone_ref(py))
339 330 }
340 331
341 332 def copymaplen(&self) -> PyResult<usize> {
342 333 Ok(self.inner(py).borrow().copy_map_len())
343 334 }
344 335 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
345 336 let key = key.extract::<PyBytes>(py)?;
346 337 self.inner(py)
347 338 .borrow()
348 339 .copy_map_contains_key(HgPath::new(key.data(py)))
349 340 .map_err(|e| v2_error(py, e))
350 341 }
351 342 def copymapget(
352 343 &self,
353 344 key: PyObject,
354 345 default: Option<PyObject>
355 346 ) -> PyResult<Option<PyObject>> {
356 347 let key = key.extract::<PyBytes>(py)?;
357 348 match self
358 349 .inner(py)
359 350 .borrow()
360 351 .copy_map_get(HgPath::new(key.data(py)))
361 352 .map_err(|e| v2_error(py, e))?
362 353 {
363 354 Some(copy) => Ok(Some(
364 355 PyBytes::new(py, copy.as_bytes()).into_object(),
365 356 )),
366 357 None => Ok(default),
367 358 }
368 359 }
369 360 def copymapsetitem(
370 361 &self,
371 362 key: PyObject,
372 363 value: PyObject
373 364 ) -> PyResult<PyObject> {
374 365 let key = key.extract::<PyBytes>(py)?;
375 366 let value = value.extract::<PyBytes>(py)?;
376 367 self.inner(py)
377 368 .borrow_mut()
378 369 .copy_map_insert(
379 370 HgPathBuf::from_bytes(key.data(py)),
380 371 HgPathBuf::from_bytes(value.data(py)),
381 372 )
382 373 .map_err(|e| v2_error(py, e))?;
383 374 Ok(py.None())
384 375 }
385 376 def copymappop(
386 377 &self,
387 378 key: PyObject,
388 379 default: Option<PyObject>
389 380 ) -> PyResult<Option<PyObject>> {
390 381 let key = key.extract::<PyBytes>(py)?;
391 382 match self
392 383 .inner(py)
393 384 .borrow_mut()
394 385 .copy_map_remove(HgPath::new(key.data(py)))
395 386 .map_err(|e| v2_error(py, e))?
396 387 {
397 388 Some(copy) => Ok(Some(
398 389 PyBytes::new(py, copy.as_bytes()).into_object(),
399 390 )),
400 391 None => Ok(default),
401 392 }
402 393 }
403 394
404 395 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
405 396 let leaked_ref = self.inner(py).leak_immutable();
406 397 CopyMapKeysIterator::from_inner(
407 398 py,
408 399 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
409 400 )
410 401 }
411 402
412 403 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
413 404 let leaked_ref = self.inner(py).leak_immutable();
414 405 CopyMapItemsIterator::from_inner(
415 406 py,
416 407 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
417 408 )
418 409 }
419 410
420 411 def tracked_dirs(&self) -> PyResult<PyList> {
421 412 let dirs = PyList::new(py, &[]);
422 413 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
423 414 .map_err(|e |dirstate_error(py, e))?
424 415 {
425 416 let path = path.map_err(|e| v2_error(py, e))?;
426 417 let path = PyBytes::new(py, path.as_bytes());
427 418 dirs.append(py, path.into_object())
428 419 }
429 420 Ok(dirs)
430 421 }
431 422
432 423 def debug_iter(&self, all: bool) -> PyResult<PyList> {
433 424 let dirs = PyList::new(py, &[]);
434 425 for item in self.inner(py).borrow().debug_iter(all) {
435 426 let (path, (state, mode, size, mtime)) =
436 427 item.map_err(|e| v2_error(py, e))?;
437 428 let path = PyBytes::new(py, path.as_bytes());
438 429 let item = (path, state, mode, size, mtime);
439 430 dirs.append(py, item.to_py_object(py).into_object())
440 431 }
441 432 Ok(dirs)
442 433 }
443 434 });
444 435
445 436 impl DirstateMap {
446 437 pub fn get_inner_mut<'a>(
447 438 &'a self,
448 439 py: Python<'a>,
449 440 ) -> RefMut<'a, OwningDirstateMap> {
450 441 self.inner(py).borrow_mut()
451 442 }
452 443 fn translate_key(
453 444 py: Python,
454 445 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
455 446 ) -> PyResult<Option<PyBytes>> {
456 447 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
457 448 Ok(Some(PyBytes::new(py, f.as_bytes())))
458 449 }
459 450 fn translate_key_value(
460 451 py: Python,
461 452 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
462 453 ) -> PyResult<Option<(PyBytes, PyObject)>> {
463 454 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
464 455 Ok(Some((
465 456 PyBytes::new(py, f.as_bytes()),
466 457 DirstateItem::new_as_pyobject(py, entry)?,
467 458 )))
468 459 }
469 460 }
470 461
471 462 py_shared_iterator!(
472 463 DirstateMapKeysIterator,
473 464 UnsafePyLeaked<StateMapIter<'static>>,
474 465 DirstateMap::translate_key,
475 466 Option<PyBytes>
476 467 );
477 468
478 469 py_shared_iterator!(
479 470 DirstateMapItemsIterator,
480 471 UnsafePyLeaked<StateMapIter<'static>>,
481 472 DirstateMap::translate_key_value,
482 473 Option<(PyBytes, PyObject)>
483 474 );
484 475
485 476 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
486 477 let bytes = obj.extract::<PyBytes>(py)?;
487 478 match bytes.data(py).try_into() {
488 479 Ok(s) => Ok(s),
489 480 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
490 481 }
491 482 }
492 483
493 484 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
494 485 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
495 486 }
496 487
497 488 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
498 489 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
499 490 }
@@ -1,303 +1,302 b''
1 1 // status.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 //! Bindings for the `hg::status` module provided by the
9 9 //! `hg-core` crate. From Python, this will be seen as
10 10 //! `rustext.dirstate.status`.
11 11
12 12 use crate::{dirstate::DirstateMap, exceptions::FallbackError};
13 13 use cpython::{
14 14 exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject,
15 15 PyResult, PyTuple, Python, PythonObject, ToPyObject,
16 16 };
17 17 use hg::dirstate::status::StatusPath;
18 18 use hg::{
19 19 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
20 20 parse_pattern_syntax,
21 21 utils::{
22 22 files::{get_bytes_from_path, get_path_from_bytes},
23 23 hg_path::{HgPath, HgPathBuf},
24 24 },
25 25 BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
26 26 StatusOptions,
27 27 };
28 28 use std::borrow::Borrow;
29 29
30 30 fn collect_status_path_list(py: Python, paths: &[StatusPath<'_>]) -> PyList {
31 31 collect_pybytes_list(py, paths.iter().map(|item| &*item.path))
32 32 }
33 33
34 34 /// This will be useless once trait impls for collection are added to `PyBytes`
35 35 /// upstream.
36 36 fn collect_pybytes_list(
37 37 py: Python,
38 38 iter: impl Iterator<Item = impl AsRef<HgPath>>,
39 39 ) -> PyList {
40 40 let list = PyList::new(py, &[]);
41 41
42 42 for path in iter {
43 43 list.append(
44 44 py,
45 45 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
46 46 )
47 47 }
48 48
49 49 list
50 50 }
51 51
52 52 fn collect_bad_matches(
53 53 py: Python,
54 54 collection: &[(impl AsRef<HgPath>, BadMatch)],
55 55 ) -> PyResult<PyList> {
56 56 let list = PyList::new(py, &[]);
57 57
58 58 let os = py.import("os")?;
59 59 let get_error_message = |code: i32| -> PyResult<_> {
60 60 os.call(
61 61 py,
62 62 "strerror",
63 63 PyTuple::new(py, &[code.to_py_object(py).into_object()]),
64 64 None,
65 65 )
66 66 };
67 67
68 68 for (path, bad_match) in collection.iter() {
69 69 let message = match bad_match {
70 70 BadMatch::OsError(code) => get_error_message(*code)?,
71 71 BadMatch::BadType(bad_type) => format!(
72 72 "unsupported file type (type is {})",
73 73 bad_type.to_string()
74 74 )
75 75 .to_py_object(py)
76 76 .into_object(),
77 77 };
78 78 list.append(
79 79 py,
80 80 (PyBytes::new(py, path.as_ref().as_bytes()), message)
81 81 .to_py_object(py)
82 82 .into_object(),
83 83 )
84 84 }
85 85
86 86 Ok(list)
87 87 }
88 88
89 89 fn handle_fallback(py: Python, err: StatusError) -> PyErr {
90 90 match err {
91 91 StatusError::Pattern(e) => {
92 92 let as_string = e.to_string();
93 93 log::trace!("Rust status fallback: `{}`", &as_string);
94 94
95 95 PyErr::new::<FallbackError, _>(py, &as_string)
96 96 }
97 97 e => PyErr::new::<ValueError, _>(py, e.to_string()),
98 98 }
99 99 }
100 100
101 101 pub fn status_wrapper(
102 102 py: Python,
103 103 dmap: DirstateMap,
104 104 matcher: PyObject,
105 105 root_dir: PyObject,
106 106 ignore_files: PyList,
107 107 check_exec: bool,
108 108 list_clean: bool,
109 109 list_ignored: bool,
110 110 list_unknown: bool,
111 111 collect_traversed_dirs: bool,
112 112 ) -> PyResult<PyTuple> {
113 113 let bytes = root_dir.extract::<PyBytes>(py)?;
114 114 let root_dir = get_path_from_bytes(bytes.data(py));
115 115
116 116 let dmap: DirstateMap = dmap.to_py_object(py);
117 117 let mut dmap = dmap.get_inner_mut(py);
118 118
119 119 let ignore_files: PyResult<Vec<_>> = ignore_files
120 120 .iter(py)
121 121 .map(|b| {
122 122 let file = b.extract::<PyBytes>(py)?;
123 123 Ok(get_path_from_bytes(file.data(py)).to_owned())
124 124 })
125 125 .collect();
126 126 let ignore_files = ignore_files?;
127 127 // The caller may call `copymap.items()` separately
128 128 let list_copies = false;
129 129
130 let after_status = |res: Result<(DirstateStatus<'_>, _), StatusError>| {
131 let (status_res, warnings) =
132 res.map_err(|e| handle_fallback(py, e))?;
133 build_response(py, status_res, warnings)
134 };
135
130 136 match matcher.get_type(py).name(py).borrow() {
131 137 "alwaysmatcher" => {
132 138 let matcher = AlwaysMatcher;
133 let (status_res, warnings) = dmap
134 .status(
135 &matcher,
136 root_dir.to_path_buf(),
137 ignore_files,
138 StatusOptions {
139 check_exec,
140 list_clean,
141 list_ignored,
142 list_unknown,
143 list_copies,
144 collect_traversed_dirs,
145 },
146 )
147 .map_err(|e| handle_fallback(py, e))?;
148 build_response(py, status_res, warnings)
139 dmap.with_status(
140 &matcher,
141 root_dir.to_path_buf(),
142 ignore_files,
143 StatusOptions {
144 check_exec,
145 list_clean,
146 list_ignored,
147 list_unknown,
148 list_copies,
149 collect_traversed_dirs,
150 },
151 after_status,
152 )
149 153 }
150 154 "exactmatcher" => {
151 155 let files = matcher.call_method(
152 156 py,
153 157 "files",
154 158 PyTuple::new(py, &[]),
155 159 None,
156 160 )?;
157 161 let files: PyList = files.cast_into(py)?;
158 162 let files: PyResult<Vec<HgPathBuf>> = files
159 163 .iter(py)
160 164 .map(|f| {
161 165 Ok(HgPathBuf::from_bytes(
162 166 f.extract::<PyBytes>(py)?.data(py),
163 167 ))
164 168 })
165 169 .collect();
166 170
167 171 let files = files?;
168 172 let matcher = FileMatcher::new(files.as_ref())
169 173 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
170 let (status_res, warnings) = dmap
171 .status(
172 &matcher,
173 root_dir.to_path_buf(),
174 ignore_files,
175 StatusOptions {
176 check_exec,
177 list_clean,
178 list_ignored,
179 list_unknown,
180 list_copies,
181 collect_traversed_dirs,
182 },
183 )
184 .map_err(|e| handle_fallback(py, e))?;
185 build_response(py, status_res, warnings)
174 dmap.with_status(
175 &matcher,
176 root_dir.to_path_buf(),
177 ignore_files,
178 StatusOptions {
179 check_exec,
180 list_clean,
181 list_ignored,
182 list_unknown,
183 list_copies,
184 collect_traversed_dirs,
185 },
186 after_status,
187 )
186 188 }
187 189 "includematcher" => {
188 190 // Get the patterns from Python even though most of them are
189 191 // redundant with those we will parse later on, as they include
190 192 // those passed from the command line.
191 193 let ignore_patterns: PyResult<Vec<_>> = matcher
192 194 .getattr(py, "_kindpats")?
193 195 .iter(py)?
194 196 .map(|k| {
195 197 let k = k?;
196 198 let syntax = parse_pattern_syntax(
197 199 &[
198 200 k.get_item(py, 0)?
199 201 .extract::<PyBytes>(py)?
200 202 .data(py),
201 203 &b":"[..],
202 204 ]
203 205 .concat(),
204 206 )
205 207 .map_err(|e| {
206 208 handle_fallback(py, StatusError::Pattern(e))
207 209 })?;
208 210 let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
209 211 let pattern = pattern.data(py);
210 212 let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
211 213 let source = get_path_from_bytes(source.data(py));
212 214 let new = IgnorePattern::new(syntax, pattern, source);
213 215 Ok(new)
214 216 })
215 217 .collect();
216 218
217 219 let ignore_patterns = ignore_patterns?;
218 220
219 221 let matcher = IncludeMatcher::new(ignore_patterns)
220 222 .map_err(|e| handle_fallback(py, e.into()))?;
221 223
222 let (status_res, warnings) = dmap
223 .status(
224 &matcher,
225 root_dir.to_path_buf(),
226 ignore_files,
227 StatusOptions {
228 check_exec,
229 list_clean,
230 list_ignored,
231 list_unknown,
232 list_copies,
233 collect_traversed_dirs,
234 },
235 )
236 .map_err(|e| handle_fallback(py, e))?;
237
238 build_response(py, status_res, warnings)
224 dmap.with_status(
225 &matcher,
226 root_dir.to_path_buf(),
227 ignore_files,
228 StatusOptions {
229 check_exec,
230 list_clean,
231 list_ignored,
232 list_unknown,
233 list_copies,
234 collect_traversed_dirs,
235 },
236 after_status,
237 )
239 238 }
240 239 e => Err(PyErr::new::<ValueError, _>(
241 240 py,
242 241 format!("Unsupported matcher {}", e),
243 242 )),
244 243 }
245 244 }
246 245
247 246 fn build_response(
248 247 py: Python,
249 248 status_res: DirstateStatus,
250 249 warnings: Vec<PatternFileWarning>,
251 250 ) -> PyResult<PyTuple> {
252 251 let modified = collect_status_path_list(py, &status_res.modified);
253 252 let added = collect_status_path_list(py, &status_res.added);
254 253 let removed = collect_status_path_list(py, &status_res.removed);
255 254 let deleted = collect_status_path_list(py, &status_res.deleted);
256 255 let clean = collect_status_path_list(py, &status_res.clean);
257 256 let ignored = collect_status_path_list(py, &status_res.ignored);
258 257 let unknown = collect_status_path_list(py, &status_res.unknown);
259 258 let unsure = collect_status_path_list(py, &status_res.unsure);
260 259 let bad = collect_bad_matches(py, &status_res.bad)?;
261 260 let traversed = collect_pybytes_list(py, status_res.traversed.iter());
262 261 let dirty = status_res.dirty.to_py_object(py);
263 262 let py_warnings = PyList::new(py, &[]);
264 263 for warning in warnings.iter() {
265 264 // We use duck-typing on the Python side for dispatch, good enough for
266 265 // now.
267 266 match warning {
268 267 PatternFileWarning::InvalidSyntax(file, syn) => {
269 268 py_warnings.append(
270 269 py,
271 270 (
272 271 PyBytes::new(py, &get_bytes_from_path(&file)),
273 272 PyBytes::new(py, syn),
274 273 )
275 274 .to_py_object(py)
276 275 .into_object(),
277 276 );
278 277 }
279 278 PatternFileWarning::NoSuchFile(file) => py_warnings.append(
280 279 py,
281 280 PyBytes::new(py, &get_bytes_from_path(&file)).into_object(),
282 281 ),
283 282 }
284 283 }
285 284
286 285 Ok(PyTuple::new(
287 286 py,
288 287 &[
289 288 unsure.into_object(),
290 289 modified.into_object(),
291 290 added.into_object(),
292 291 removed.into_object(),
293 292 deleted.into_object(),
294 293 clean.into_object(),
295 294 ignored.into_object(),
296 295 unknown.into_object(),
297 296 py_warnings.into_object(),
298 297 bad.into_object(),
299 298 traversed.into_object(),
300 299 dirty.into_object(),
301 300 ][..],
302 301 ))
303 302 }
@@ -1,531 +1,549 b''
1 1 // status.rs
2 2 //
3 3 // Copyright 2020, Georges Racinet <georges.racinets@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 use crate::error::CommandError;
9 9 use crate::ui::Ui;
10 10 use crate::utils::path_utils::RelativizePaths;
11 11 use clap::{Arg, SubCommand};
12 12 use format_bytes::format_bytes;
13 13 use hg;
14 14 use hg::config::Config;
15 15 use hg::dirstate::has_exec_bit;
16 16 use hg::dirstate::status::StatusPath;
17 17 use hg::dirstate::TruncatedTimestamp;
18 18 use hg::dirstate::RANGE_MASK_31BIT;
19 19 use hg::errors::{HgError, IoResultExt};
20 20 use hg::lock::LockError;
21 21 use hg::manifest::Manifest;
22 22 use hg::matchers::AlwaysMatcher;
23 23 use hg::repo::Repo;
24 24 use hg::utils::files::get_bytes_from_os_string;
25 25 use hg::utils::files::get_bytes_from_path;
26 26 use hg::utils::files::get_path_from_bytes;
27 27 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
28 use hg::DirstateStatus;
29 use hg::PatternFileWarning;
30 use hg::StatusError;
28 31 use hg::StatusOptions;
29 32 use log::info;
30 33 use std::io;
31 34 use std::path::PathBuf;
32 35
33 36 pub const HELP_TEXT: &str = "
34 37 Show changed files in the working directory
35 38
36 39 This is a pure Rust version of `hg status`.
37 40
38 41 Some options might be missing, check the list below.
39 42 ";
40 43
41 44 pub fn args() -> clap::App<'static, 'static> {
42 45 SubCommand::with_name("status")
43 46 .alias("st")
44 47 .about(HELP_TEXT)
45 48 .arg(
46 49 Arg::with_name("all")
47 50 .help("show status of all files")
48 51 .short("-A")
49 52 .long("--all"),
50 53 )
51 54 .arg(
52 55 Arg::with_name("modified")
53 56 .help("show only modified files")
54 57 .short("-m")
55 58 .long("--modified"),
56 59 )
57 60 .arg(
58 61 Arg::with_name("added")
59 62 .help("show only added files")
60 63 .short("-a")
61 64 .long("--added"),
62 65 )
63 66 .arg(
64 67 Arg::with_name("removed")
65 68 .help("show only removed files")
66 69 .short("-r")
67 70 .long("--removed"),
68 71 )
69 72 .arg(
70 73 Arg::with_name("clean")
71 74 .help("show only clean files")
72 75 .short("-c")
73 76 .long("--clean"),
74 77 )
75 78 .arg(
76 79 Arg::with_name("deleted")
77 80 .help("show only deleted files")
78 81 .short("-d")
79 82 .long("--deleted"),
80 83 )
81 84 .arg(
82 85 Arg::with_name("unknown")
83 86 .help("show only unknown (not tracked) files")
84 87 .short("-u")
85 88 .long("--unknown"),
86 89 )
87 90 .arg(
88 91 Arg::with_name("ignored")
89 92 .help("show only ignored files")
90 93 .short("-i")
91 94 .long("--ignored"),
92 95 )
93 96 .arg(
94 97 Arg::with_name("copies")
95 98 .help("show source of copied files (DEFAULT: ui.statuscopies)")
96 99 .short("-C")
97 100 .long("--copies"),
98 101 )
99 102 .arg(
100 103 Arg::with_name("no-status")
101 104 .help("hide status prefix")
102 105 .short("-n")
103 106 .long("--no-status"),
104 107 )
105 108 }
106 109
107 110 /// Pure data type allowing the caller to specify file states to display
108 111 #[derive(Copy, Clone, Debug)]
109 112 pub struct DisplayStates {
110 113 pub modified: bool,
111 114 pub added: bool,
112 115 pub removed: bool,
113 116 pub clean: bool,
114 117 pub deleted: bool,
115 118 pub unknown: bool,
116 119 pub ignored: bool,
117 120 }
118 121
119 122 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
120 123 modified: true,
121 124 added: true,
122 125 removed: true,
123 126 clean: false,
124 127 deleted: true,
125 128 unknown: true,
126 129 ignored: false,
127 130 };
128 131
129 132 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
130 133 modified: true,
131 134 added: true,
132 135 removed: true,
133 136 clean: true,
134 137 deleted: true,
135 138 unknown: true,
136 139 ignored: true,
137 140 };
138 141
139 142 impl DisplayStates {
140 143 pub fn is_empty(&self) -> bool {
141 144 !(self.modified
142 145 || self.added
143 146 || self.removed
144 147 || self.clean
145 148 || self.deleted
146 149 || self.unknown
147 150 || self.ignored)
148 151 }
149 152 }
150 153
151 154 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
152 155 // TODO: lift these limitations
153 156 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
154 157 return Err(CommandError::unsupported(
155 158 "ui.tweakdefaults is not yet supported with rhg status",
156 159 ));
157 160 }
158 161 if invocation.config.get_bool(b"ui", b"statuscopies")? {
159 162 return Err(CommandError::unsupported(
160 163 "ui.statuscopies is not yet supported with rhg status",
161 164 ));
162 165 }
163 166 if invocation
164 167 .config
165 168 .get(b"commands", b"status.terse")
166 169 .is_some()
167 170 {
168 171 return Err(CommandError::unsupported(
169 172 "status.terse is not yet supported with rhg status",
170 173 ));
171 174 }
172 175
173 176 let ui = invocation.ui;
174 177 let config = invocation.config;
175 178 let args = invocation.subcommand_args;
176 179
177 180 let verbose = !ui.plain(None)
178 181 && !args.is_present("print0")
179 182 && (config.get_bool(b"ui", b"verbose")?
180 183 || config.get_bool(b"commands", b"status.verbose")?);
181 184 if verbose {
182 185 return Err(CommandError::unsupported(
183 186 "verbose status is not supported yet",
184 187 ));
185 188 }
186 189
187 190 let all = args.is_present("all");
188 191 let display_states = if all {
189 192 // TODO when implementing `--quiet`: it excludes clean files
190 193 // from `--all`
191 194 ALL_DISPLAY_STATES
192 195 } else {
193 196 let requested = DisplayStates {
194 197 modified: args.is_present("modified"),
195 198 added: args.is_present("added"),
196 199 removed: args.is_present("removed"),
197 200 clean: args.is_present("clean"),
198 201 deleted: args.is_present("deleted"),
199 202 unknown: args.is_present("unknown"),
200 203 ignored: args.is_present("ignored"),
201 204 };
202 205 if requested.is_empty() {
203 206 DEFAULT_DISPLAY_STATES
204 207 } else {
205 208 requested
206 209 }
207 210 };
208 211 let no_status = args.is_present("no-status");
209 212 let list_copies = all
210 213 || args.is_present("copies")
211 214 || config.get_bool(b"ui", b"statuscopies")?;
212 215
213 216 let repo = invocation.repo?;
214 217
215 218 if repo.has_sparse() || repo.has_narrow() {
216 219 return Err(CommandError::unsupported(
217 220 "rhg status is not supported for sparse checkouts or narrow clones yet"
218 221 ));
219 222 }
220 223
221 224 let mut dmap = repo.dirstate_map_mut()?;
222 225
223 226 let options = StatusOptions {
224 227 // we're currently supporting file systems with exec flags only
225 228 // anyway
226 229 check_exec: true,
227 230 list_clean: display_states.clean,
228 231 list_unknown: display_states.unknown,
229 232 list_ignored: display_states.ignored,
230 233 list_copies,
231 234 collect_traversed_dirs: false,
232 235 };
233 let (mut ds_status, pattern_warnings) = dmap.status(
234 &AlwaysMatcher,
235 repo.working_directory_path().to_owned(),
236 ignore_files(repo, config),
237 options,
238 )?;
239 for warning in pattern_warnings {
240 match warning {
241 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
242 .write_stderr(&format_bytes!(
243 b"{}: ignoring invalid syntax '{}'\n",
244 get_bytes_from_path(path),
245 &*syntax
246 ))?,
247 hg::PatternFileWarning::NoSuchFile(path) => {
248 let path = if let Ok(relative) =
249 path.strip_prefix(repo.working_directory_path())
250 {
251 relative
252 } else {
253 &*path
254 };
255 ui.write_stderr(&format_bytes!(
256 b"skipping unreadable pattern file '{}': \
257 No such file or directory\n",
258 get_bytes_from_path(path),
259 ))?
236
237 type StatusResult<'a> =
238 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
239
240 let after_status = |res: StatusResult| -> Result<_, CommandError> {
241 let (mut ds_status, pattern_warnings) = res?;
242 for warning in pattern_warnings {
243 match warning {
244 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
245 .write_stderr(&format_bytes!(
246 b"{}: ignoring invalid syntax '{}'\n",
247 get_bytes_from_path(path),
248 &*syntax
249 ))?,
250 hg::PatternFileWarning::NoSuchFile(path) => {
251 let path = if let Ok(relative) =
252 path.strip_prefix(repo.working_directory_path())
253 {
254 relative
255 } else {
256 &*path
257 };
258 ui.write_stderr(&format_bytes!(
259 b"skipping unreadable pattern file '{}': \
260 No such file or directory\n",
261 get_bytes_from_path(path),
262 ))?
263 }
260 264 }
261 265 }
262 }
263 266
264 for (path, error) in ds_status.bad {
265 let error = match error {
266 hg::BadMatch::OsError(code) => {
267 std::io::Error::from_raw_os_error(code).to_string()
268 }
269 hg::BadMatch::BadType(ty) => {
270 format!("unsupported file type (type is {})", ty)
271 }
272 };
273 ui.write_stderr(&format_bytes!(
274 b"{}: {}\n",
275 path.as_bytes(),
276 error.as_bytes()
277 ))?
278 }
279 if !ds_status.unsure.is_empty() {
280 info!(
281 "Files to be rechecked by retrieval from filelog: {:?}",
282 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
283 );
284 }
285 let mut fixup = Vec::new();
286 if !ds_status.unsure.is_empty()
287 && (display_states.modified || display_states.clean)
288 {
289 let p1 = repo.dirstate_parents()?.p1;
290 let manifest = repo.manifest_for_node(p1).map_err(|e| {
291 CommandError::from((e, &*format!("{:x}", p1.short())))
292 })?;
293 for to_check in ds_status.unsure {
294 if unsure_is_modified(repo, &manifest, &to_check.path)? {
295 if display_states.modified {
296 ds_status.modified.push(to_check);
267 for (path, error) in ds_status.bad {
268 let error = match error {
269 hg::BadMatch::OsError(code) => {
270 std::io::Error::from_raw_os_error(code).to_string()
271 }
272 hg::BadMatch::BadType(ty) => {
273 format!("unsupported file type (type is {})", ty)
297 274 }
298 } else {
299 if display_states.clean {
300 ds_status.clean.push(to_check.clone());
275 };
276 ui.write_stderr(&format_bytes!(
277 b"{}: {}\n",
278 path.as_bytes(),
279 error.as_bytes()
280 ))?
281 }
282 if !ds_status.unsure.is_empty() {
283 info!(
284 "Files to be rechecked by retrieval from filelog: {:?}",
285 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
286 );
287 }
288 let mut fixup = Vec::new();
289 if !ds_status.unsure.is_empty()
290 && (display_states.modified || display_states.clean)
291 {
292 let p1 = repo.dirstate_parents()?.p1;
293 let manifest = repo.manifest_for_node(p1).map_err(|e| {
294 CommandError::from((e, &*format!("{:x}", p1.short())))
295 })?;
296 for to_check in ds_status.unsure {
297 if unsure_is_modified(repo, &manifest, &to_check.path)? {
298 if display_states.modified {
299 ds_status.modified.push(to_check);
300 }
301 } else {
302 if display_states.clean {
303 ds_status.clean.push(to_check.clone());
304 }
305 fixup.push(to_check.path.into_owned())
301 306 }
302 fixup.push(to_check.path.into_owned())
303 307 }
304 308 }
305 }
306 let relative_paths = (!ui.plain(None))
307 && config
308 .get_option(b"commands", b"status.relative")?
309 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
310 let output = DisplayStatusPaths {
311 ui,
312 no_status,
313 relativize: if relative_paths {
314 Some(RelativizePaths::new(repo)?)
315 } else {
316 None
317 },
309 let relative_paths = (!ui.plain(None))
310 && config
311 .get_option(b"commands", b"status.relative")?
312 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
313 let output = DisplayStatusPaths {
314 ui,
315 no_status,
316 relativize: if relative_paths {
317 Some(RelativizePaths::new(repo)?)
318 } else {
319 None
320 },
321 };
322 if display_states.modified {
323 output.display(b"M ", "status.modified", ds_status.modified)?;
324 }
325 if display_states.added {
326 output.display(b"A ", "status.added", ds_status.added)?;
327 }
328 if display_states.removed {
329 output.display(b"R ", "status.removed", ds_status.removed)?;
330 }
331 if display_states.deleted {
332 output.display(b"! ", "status.deleted", ds_status.deleted)?;
333 }
334 if display_states.unknown {
335 output.display(b"? ", "status.unknown", ds_status.unknown)?;
336 }
337 if display_states.ignored {
338 output.display(b"I ", "status.ignored", ds_status.ignored)?;
339 }
340 if display_states.clean {
341 output.display(b"C ", "status.clean", ds_status.clean)?;
342 }
343
344 let dirstate_write_needed = ds_status.dirty;
345 let filesystem_time_at_status_start =
346 ds_status.filesystem_time_at_status_start;
347
348 Ok((
349 fixup,
350 dirstate_write_needed,
351 filesystem_time_at_status_start,
352 ))
318 353 };
319 if display_states.modified {
320 output.display(b"M ", "status.modified", ds_status.modified)?;
321 }
322 if display_states.added {
323 output.display(b"A ", "status.added", ds_status.added)?;
324 }
325 if display_states.removed {
326 output.display(b"R ", "status.removed", ds_status.removed)?;
327 }
328 if display_states.deleted {
329 output.display(b"! ", "status.deleted", ds_status.deleted)?;
330 }
331 if display_states.unknown {
332 output.display(b"? ", "status.unknown", ds_status.unknown)?;
333 }
334 if display_states.ignored {
335 output.display(b"I ", "status.ignored", ds_status.ignored)?;
336 }
337 if display_states.clean {
338 output.display(b"C ", "status.clean", ds_status.clean)?;
339 }
340
341 let mut dirstate_write_needed = ds_status.dirty;
342 let filesystem_time_at_status_start =
343 ds_status.filesystem_time_at_status_start;
354 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
355 dmap.with_status(
356 &AlwaysMatcher,
357 repo.working_directory_path().to_owned(),
358 ignore_files(repo, config),
359 options,
360 after_status,
361 )?;
344 362
345 363 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
346 364 && !dirstate_write_needed
347 365 {
348 366 // Nothing to update
349 367 return Ok(());
350 368 }
351 369
352 370 // Update the dirstate on disk if we can
353 371 let with_lock_result =
354 372 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
355 373 if let Some(mtime_boundary) = filesystem_time_at_status_start {
356 374 for hg_path in fixup {
357 375 use std::os::unix::fs::MetadataExt;
358 376 let fs_path = hg_path_to_path_buf(&hg_path)
359 377 .expect("HgPath conversion");
360 378 // Specifically do not reuse `fs_metadata` from
361 379 // `unsure_is_clean` which was needed before reading
362 380 // contents. Here we access metadata again after reading
363 381 // content, in case it changed in the meantime.
364 382 let fs_metadata = repo
365 383 .working_directory_vfs()
366 384 .symlink_metadata(&fs_path)?;
367 385 if let Some(mtime) =
368 386 TruncatedTimestamp::for_reliable_mtime_of(
369 387 &fs_metadata,
370 388 &mtime_boundary,
371 389 )
372 390 .when_reading_file(&fs_path)?
373 391 {
374 392 let mode = fs_metadata.mode();
375 393 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
376 394 let mut entry = dmap
377 395 .get(&hg_path)?
378 396 .expect("ambiguous file not in dirstate");
379 397 entry.set_clean(mode, size, mtime);
380 398 dmap.add_file(&hg_path, entry)?;
381 399 dirstate_write_needed = true
382 400 }
383 401 }
384 402 }
385 403 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
386 404 if dirstate_write_needed {
387 405 repo.write_dirstate()?
388 406 }
389 407 Ok(())
390 408 });
391 409 match with_lock_result {
392 410 Ok(closure_result) => closure_result?,
393 411 Err(LockError::AlreadyHeld) => {
394 412 // Not updating the dirstate is not ideal but not critical:
395 413 // don’t keep our caller waiting until some other Mercurial
396 414 // process releases the lock.
397 415 }
398 416 Err(LockError::Other(HgError::IoError { error, .. }))
399 417 if error.kind() == io::ErrorKind::PermissionDenied =>
400 418 {
401 419 // `hg status` on a read-only repository is fine
402 420 }
403 421 Err(LockError::Other(error)) => {
404 422 // Report other I/O errors
405 423 Err(error)?
406 424 }
407 425 }
408 426 Ok(())
409 427 }
410 428
411 429 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
412 430 let mut ignore_files = Vec::new();
413 431 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
414 432 if repo_ignore.exists() {
415 433 ignore_files.push(repo_ignore)
416 434 }
417 435 for (key, value) in config.iter_section(b"ui") {
418 436 if key == b"ignore" || key.starts_with(b"ignore.") {
419 437 let path = get_path_from_bytes(value);
420 438 // TODO: expand "~/" and environment variable here, like Python
421 439 // does with `os.path.expanduser` and `os.path.expandvars`
422 440
423 441 let joined = repo.working_directory_path().join(path);
424 442 ignore_files.push(joined);
425 443 }
426 444 }
427 445 ignore_files
428 446 }
429 447
430 448 struct DisplayStatusPaths<'a> {
431 449 ui: &'a Ui,
432 450 no_status: bool,
433 451 relativize: Option<RelativizePaths>,
434 452 }
435 453
436 454 impl DisplayStatusPaths<'_> {
437 455 // Probably more elegant to use a Deref or Borrow trait rather than
438 456 // harcode HgPathBuf, but probably not really useful at this point
439 457 fn display(
440 458 &self,
441 459 status_prefix: &[u8],
442 460 label: &'static str,
443 461 mut paths: Vec<StatusPath<'_>>,
444 462 ) -> Result<(), CommandError> {
445 463 paths.sort_unstable();
446 464 // TODO: get the stdout lock once for the whole loop
447 465 // instead of in each write
448 466 for StatusPath { path, copy_source } in paths {
449 467 let relative;
450 468 let path = if let Some(relativize) = &self.relativize {
451 469 relative = relativize.relativize(&path);
452 470 &*relative
453 471 } else {
454 472 path.as_bytes()
455 473 };
456 474 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
457 475 // in order to stream to stdout instead of allocating an
458 476 // itermediate `Vec<u8>`.
459 477 if !self.no_status {
460 478 self.ui.write_stdout_labelled(status_prefix, label)?
461 479 }
462 480 self.ui
463 481 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
464 482 if let Some(source) = copy_source {
465 483 let label = "status.copied";
466 484 self.ui.write_stdout_labelled(
467 485 &format_bytes!(b" {}\n", source.as_bytes()),
468 486 label,
469 487 )?
470 488 }
471 489 }
472 490 Ok(())
473 491 }
474 492 }
475 493
476 494 /// Check if a file is modified by comparing actual repo store and file system.
477 495 ///
478 496 /// This meant to be used for those that the dirstate cannot resolve, due
479 497 /// to time resolution limits.
480 498 fn unsure_is_modified(
481 499 repo: &Repo,
482 500 manifest: &Manifest,
483 501 hg_path: &HgPath,
484 502 ) -> Result<bool, HgError> {
485 503 let vfs = repo.working_directory_vfs();
486 504 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
487 505 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
488 506 let is_symlink = fs_metadata.file_type().is_symlink();
489 507 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
490 508 // dirstate
491 509 let fs_flags = if is_symlink {
492 510 Some(b'l')
493 511 } else if has_exec_bit(&fs_metadata) {
494 512 Some(b'x')
495 513 } else {
496 514 None
497 515 };
498 516
499 517 let entry = manifest
500 518 .find_by_path(hg_path)?
501 519 .expect("ambgious file not in p1");
502 520 if entry.flags != fs_flags {
503 521 return Ok(true);
504 522 }
505 523 let filelog = repo.filelog(hg_path)?;
506 524 let fs_len = fs_metadata.len();
507 525 let filelog_entry =
508 526 filelog.entry_for_node(entry.node_id()?).map_err(|_| {
509 527 HgError::corrupted("filelog missing node from manifest")
510 528 })?;
511 529 if filelog_entry.file_data_len_not_equal_to(fs_len) {
512 530 // No need to read file contents:
513 531 // it cannot be equal if it has a different length.
514 532 return Ok(true);
515 533 }
516 534
517 535 let p1_filelog_data = filelog_entry.data()?;
518 536 let p1_contents = p1_filelog_data.file_data()?;
519 537 if p1_contents.len() as u64 != fs_len {
520 538 // No need to read file contents:
521 539 // it cannot be equal if it has a different length.
522 540 return Ok(true);
523 541 }
524 542
525 543 let fs_contents = if is_symlink {
526 544 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
527 545 } else {
528 546 vfs.read(fs_path)?
529 547 };
530 548 Ok(p1_contents != &*fs_contents)
531 549 }
General Comments 0
You need to be logged in to leave comments. Login now