max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
9 files changed,
+664 insertions,
-161 deletions
| @@ -134,7 +134,7 @@ | |||
| 134 | 134 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 135 | 135 | checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" | |
| 136 | 136 | dependencies = [ | |
| 137 | - | "windows-sys 0.61.2", | |
| 137 | + | "windows-sys 0.60.2", | |
| 138 | 138 | ] | |
| 139 | 139 | ||
| 140 | 140 | [[package]] | |
| @@ -145,7 +145,7 @@ | |||
| 145 | 145 | dependencies = [ | |
| 146 | 146 | "anstyle", | |
| 147 | 147 | "once_cell_polyfill", | |
| 148 | - | "windows-sys 0.61.2", | |
| 148 | + | "windows-sys 0.60.2", | |
| 149 | 149 | ] | |
| 150 | 150 | ||
| 151 | 151 | [[package]] | |
| @@ -1363,6 +1363,21 @@ | |||
| 1363 | 1363 | "virtue", | |
| 1364 | 1364 | ] | |
| 1365 | 1365 | ||
| 1366 | + | [[package]] | |
| 1367 | + | name = "bisync" | |
| 1368 | + | version = "0.3.0" | |
| 1369 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1370 | + | checksum = "5020822f6d6f23196ccaf55e228db36f9de1cf788052b37992e17cbc96ec41a7" | |
| 1371 | + | dependencies = [ | |
| 1372 | + | "bisync_macros", | |
| 1373 | + | ] | |
| 1374 | + | ||
| 1375 | + | [[package]] | |
| 1376 | + | name = "bisync_macros" | |
| 1377 | + | version = "0.2.3" | |
| 1378 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1379 | + | checksum = "d21f40d350a700f6aa107e45fb26448cf489d34794b2ba4522181dc9f1173af6" | |
| 1380 | + | ||
| 1366 | 1381 | [[package]] | |
| 1367 | 1382 | name = "bit-set" | |
| 1368 | 1383 | version = "0.8.0" | |
| @@ -1695,6 +1710,15 @@ | |||
| 1695 | 1710 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1696 | 1711 | checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" | |
| 1697 | 1712 | ||
| 1713 | + | [[package]] | |
| 1714 | + | name = "clru" | |
| 1715 | + | version = "0.6.3" | |
| 1716 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1717 | + | checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" | |
| 1718 | + | dependencies = [ | |
| 1719 | + | "hashbrown 0.16.1", | |
| 1720 | + | ] | |
| 1721 | + | ||
| 1698 | 1722 | [[package]] | |
| 1699 | 1723 | name = "cmake" | |
| 1700 | 1724 | version = "0.1.58" | |
| @@ -2113,6 +2137,15 @@ | |||
| 2113 | 2137 | "cfg-if", | |
| 2114 | 2138 | ] | |
| 2115 | 2139 | ||
| 2140 | + | [[package]] | |
| 2141 | + | name = "crossbeam-channel" | |
| 2142 | + | version = "0.5.16" | |
| 2143 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2144 | + | checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" | |
| 2145 | + | dependencies = [ | |
| 2146 | + | "crossbeam-utils", | |
| 2147 | + | ] | |
| 2148 | + | ||
| 2116 | 2149 | [[package]] | |
| 2117 | 2150 | name = "crossbeam-deque" | |
| 2118 | 2151 | version = "0.8.6" | |
| @@ -2781,7 +2814,7 @@ | |||
| 2781 | 2814 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" | |
| 2782 | 2815 | dependencies = [ | |
| 2783 | 2816 | "libc", | |
| 2784 | - | "windows-sys 0.61.2", | |
| 2817 | + | "windows-sys 0.52.0", | |
| 2785 | 2818 | ] | |
| 2786 | 2819 | ||
| 2787 | 2820 | [[package]] | |
| @@ -2834,6 +2867,16 @@ | |||
| 2834 | 2867 | "regex-syntax", | |
| 2835 | 2868 | ] | |
| 2836 | 2869 | ||
| 2870 | + | [[package]] | |
| 2871 | + | name = "faster-hex" | |
| 2872 | + | version = "0.10.0" | |
| 2873 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2874 | + | checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" | |
| 2875 | + | dependencies = [ | |
| 2876 | + | "heapless", | |
| 2877 | + | "serde", | |
| 2878 | + | ] | |
| 2879 | + | ||
| 2837 | 2880 | [[package]] | |
| 2838 | 2881 | name = "fastrand" | |
| 2839 | 2882 | version = "2.4.1" | |
| @@ -2971,7 +3014,7 @@ | |||
| 2971 | 3014 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2972 | 3015 | checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" | |
| 2973 | 3016 | dependencies = [ | |
| 2974 | - | "nonempty", | |
| 3017 | + | "nonempty 0.7.0", | |
| 2975 | 3018 | "thiserror 1.0.69", | |
| 2976 | 3019 | ] | |
| 2977 | 3020 | ||
| @@ -3213,6 +3256,766 @@ | |||
| 3213 | 3256 | "log", | |
| 3214 | 3257 | ] | |
| 3215 | 3258 | ||
| 3259 | + | [[package]] | |
| 3260 | + | name = "gix" | |
| 3261 | + | version = "0.86.0" | |
| 3262 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3263 | + | checksum = "bb3790fd8981cba7949f1ba924ef865d902df731627bc5998d14164063892fce" | |
| 3264 | + | dependencies = [ | |
| 3265 | + | "gix-actor", | |
| 3266 | + | "gix-attributes", | |
| 3267 | + | "gix-blame", | |
| 3268 | + | "gix-command", | |
| 3269 | + | "gix-commitgraph", | |
| 3270 | + | "gix-config", | |
| 3271 | + | "gix-date", | |
| 3272 | + | "gix-diff", | |
| 3273 | + | "gix-discover", | |
| 3274 | + | "gix-error", | |
| 3275 | + | "gix-features", | |
| 3276 | + | "gix-filter", | |
| 3277 | + | "gix-fs", | |
| 3278 | + | "gix-glob", | |
| 3279 | + | "gix-hash", | |
| 3280 | + | "gix-hashtable", | |
| 3281 | + | "gix-ignore", | |
| 3282 | + | "gix-index", | |
| 3283 | + | "gix-lock", | |
| 3284 | + | "gix-object", | |
| 3285 | + | "gix-odb", | |
| 3286 | + | "gix-pack", | |
| 3287 | + | "gix-path", | |
| 3288 | + | "gix-pathspec", | |
| 3289 | + | "gix-protocol", | |
| 3290 | + | "gix-ref", | |
| 3291 | + | "gix-refspec", | |
| 3292 | + | "gix-revision", | |
| 3293 | + | "gix-revwalk", | |
| 3294 | + | "gix-sec", | |
| 3295 | + | "gix-shallow", | |
| 3296 | + | "gix-submodule", | |
| 3297 | + | "gix-tempfile", | |
| 3298 | + | "gix-trace", | |
| 3299 | + | "gix-traverse", | |
| 3300 | + | "gix-url", | |
| 3301 | + | "gix-utils", | |
| 3302 | + | "gix-validate", | |
| 3303 | + | "gix-worktree", | |
| 3304 | + | "gix-worktree-stream", | |
| 3305 | + | "gix-zlib", | |
| 3306 | + | "nonempty 0.12.0", | |
| 3307 | + | "smallvec", | |
| 3308 | + | "thiserror 2.0.18", | |
| 3309 | + | ] | |
| 3310 | + | ||
| 3311 | + | [[package]] | |
| 3312 | + | name = "gix-actor" | |
| 3313 | + | version = "0.41.2" | |
| 3314 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3315 | + | checksum = "33f9308ad6fd35b2a865cbe4117ac61b2be59e4a9ef1621c7a9794f7c8e52c5b" | |
| 3316 | + | dependencies = [ | |
| 3317 | + | "bstr", | |
| 3318 | + | "gix-date", | |
| 3319 | + | "gix-error", | |
| 3320 | + | ] | |
| 3321 | + | ||
| 3322 | + | [[package]] | |
| 3323 | + | name = "gix-attributes" | |
| 3324 | + | version = "0.34.0" | |
| 3325 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3326 | + | checksum = "c31c593692ebdc1e38858d9a2b56f6a594c501e24a38971fe6685571f5a07be0" | |
| 3327 | + | dependencies = [ | |
| 3328 | + | "bstr", | |
| 3329 | + | "gix-features", | |
| 3330 | + | "gix-glob", | |
| 3331 | + | "gix-path", | |
| 3332 | + | "gix-quote", | |
| 3333 | + | "gix-trace", | |
| 3334 | + | "smallvec", | |
| 3335 | + | "thiserror 2.0.18", | |
| 3336 | + | "unicode-bom", | |
| 3337 | + | ] | |
| 3338 | + | ||
| 3339 | + | [[package]] | |
| 3340 | + | name = "gix-bitmap" | |
| 3341 | + | version = "0.3.3" | |
| 3342 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3343 | + | checksum = "7cd1d118d0f5d88b96e6f6e13b566475fef4797ead4a02c26fed36c1375066f7" | |
| 3344 | + | dependencies = [ | |
| 3345 | + | "gix-error", | |
| 3346 | + | ] | |
| 3347 | + | ||
| 3348 | + | [[package]] | |
| 3349 | + | name = "gix-blame" | |
| 3350 | + | version = "0.16.0" | |
| 3351 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3352 | + | checksum = "b06d20ff0e88ada2dd852b3852727b664ec9baefdf653d1d4e722e858c52cd17" | |
| 3353 | + | dependencies = [ | |
| 3354 | + | "gix-commitgraph", | |
| 3355 | + | "gix-date", | |
| 3356 | + | "gix-diff", | |
| 3357 | + | "gix-error", | |
| 3358 | + | "gix-hash", | |
| 3359 | + | "gix-object", | |
| 3360 | + | "gix-revwalk", | |
| 3361 | + | "gix-trace", | |
| 3362 | + | "gix-traverse", | |
| 3363 | + | "gix-worktree", | |
| 3364 | + | "smallvec", | |
| 3365 | + | "thiserror 2.0.18", | |
| 3366 | + | ] | |
| 3367 | + | ||
| 3368 | + | [[package]] | |
| 3369 | + | name = "gix-chunk" | |
| 3370 | + | version = "0.7.3" | |
| 3371 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3372 | + | checksum = "b2a871e5cab12ba568845714473505deefffb3c04eb47f4708ce344cd459c1cc" | |
| 3373 | + | dependencies = [ | |
| 3374 | + | "gix-error", | |
| 3375 | + | ] | |
| 3376 | + | ||
| 3377 | + | [[package]] | |
| 3378 | + | name = "gix-command" | |
| 3379 | + | version = "0.9.1" | |
| 3380 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3381 | + | checksum = "00706d4fef135ef4b01680d5218c6ee40cda8baf697b864296cbc887d19118f6" | |
| 3382 | + | dependencies = [ | |
| 3383 | + | "bstr", | |
| 3384 | + | "gix-path", | |
| 3385 | + | "gix-quote", | |
| 3386 | + | "gix-trace", | |
| 3387 | + | "shell-words", | |
| 3388 | + | ] | |
| 3389 | + | ||
| 3390 | + | [[package]] | |
| 3391 | + | name = "gix-commitgraph" | |
| 3392 | + | version = "0.38.0" | |
| 3393 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3394 | + | checksum = "a2cd7f054ae2727223fe46dd39c012f066b12f532962d336d29ee193261787da" | |
| 3395 | + | dependencies = [ | |
| 3396 | + | "bstr", | |
| 3397 | + | "gix-chunk", | |
| 3398 | + | "gix-error", | |
| 3399 | + | "gix-hash", | |
| 3400 | + | "memmap2", | |
| 3401 | + | "nonempty 0.12.0", | |
| 3402 | + | ] | |
| 3403 | + | ||
| 3404 | + | [[package]] | |
| 3405 | + | name = "gix-config" | |
| 3406 | + | version = "0.59.0" | |
| 3407 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3408 | + | checksum = "103d11bef95c467577ecfa8b7b86a22e65af3507b2c9bfa3809a4afbae7df301" | |
| 3409 | + | dependencies = [ | |
| 3410 | + | "bstr", | |
| 3411 | + | "gix-config-value", | |
| 3412 | + | "gix-features", | |
| 3413 | + | "gix-glob", | |
| 3414 | + | "gix-path", | |
| 3415 | + | "gix-ref", | |
| 3416 | + | "gix-sec", | |
| 3417 | + | "gix-utils", | |
| 3418 | + | "smallvec", | |
| 3419 | + | "thiserror 2.0.18", | |
| 3420 | + | "unicode-bom", | |
| 3421 | + | ] | |
| 3422 | + | ||
| 3423 | + | [[package]] | |
| 3424 | + | name = "gix-config-value" | |
| 3425 | + | version = "0.19.0" | |
| 3426 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3427 | + | checksum = "9f813e312a3f7f327187823cd4c754a3e4d948c98907d11e8f37554a2b6b8059" | |
| 3428 | + | dependencies = [ | |
| 3429 | + | "bitflags 2.13.0", | |
| 3430 | + | "bstr", | |
| 3431 | + | "gix-path", | |
| 3432 | + | "libc", | |
| 3433 | + | "thiserror 2.0.18", | |
| 3434 | + | ] | |
| 3435 | + | ||
| 3436 | + | [[package]] | |
| 3437 | + | name = "gix-date" | |
| 3438 | + | version = "0.15.6" | |
| 3439 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3440 | + | checksum = "7e47b9e8cdc688296609b706428de570f88b1e0eed7156dde7b4a89d26fa4567" | |
| 3441 | + | dependencies = [ | |
| 3442 | + | "bstr", | |
| 3443 | + | "gix-error", | |
| 3444 | + | "itoa", | |
| 3445 | + | "jiff", | |
| 3446 | + | ] | |
| 3447 | + | ||
| 3448 | + | [[package]] | |
| 3449 | + | name = "gix-diff" | |
| 3450 | + | version = "0.66.0" | |
| 3451 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3452 | + | checksum = "fee7d89a3c507491cdfc57a1d1e0e300214720b4f7709ebc253e422f99822bfc" | |
| 3453 | + | dependencies = [ | |
| 3454 | + | "bstr", | |
| 3455 | + | "gix-command", | |
| 3456 | + | "gix-filter", | |
| 3457 | + | "gix-fs", | |
| 3458 | + | "gix-hash", | |
| 3459 | + | "gix-imara-diff", | |
| 3460 | + | "gix-object", | |
| 3461 | + | "gix-path", | |
| 3462 | + | "gix-tempfile", | |
| 3463 | + | "gix-trace", | |
| 3464 | + | "gix-traverse", | |
| 3465 | + | "gix-worktree", | |
| 3466 | + | "thiserror 2.0.18", | |
| 3467 | + | ] | |
| 3468 | + | ||
| 3469 | + | [[package]] | |
| 3470 | + | name = "gix-discover" | |
| 3471 | + | version = "0.54.0" | |
| 3472 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3473 | + | checksum = "b9f517766fa1101dfe2606c1a19a8ffa699099030995a9194445446dfe261bdf" | |
| 3474 | + | dependencies = [ | |
| 3475 | + | "bstr", | |
| 3476 | + | "dunce", | |
| 3477 | + | "gix-fs", | |
| 3478 | + | "gix-path", | |
| 3479 | + | "gix-ref", | |
| 3480 | + | "gix-sec", | |
| 3481 | + | "thiserror 2.0.18", | |
| 3482 | + | ] | |
| 3483 | + | ||
| 3484 | + | [[package]] | |
| 3485 | + | name = "gix-error" | |
| 3486 | + | version = "0.2.5" | |
| 3487 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3488 | + | checksum = "4a9292309fd944e71b2a3c96d3c03a6feb8852db646febdde7cbb9f79cb5f329" | |
| 3489 | + | dependencies = [ | |
| 3490 | + | "bstr", | |
| 3491 | + | ] | |
| 3492 | + | ||
| 3493 | + | [[package]] | |
| 3494 | + | name = "gix-features" | |
| 3495 | + | version = "0.49.0" | |
| 3496 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3497 | + | checksum = "20aa09e83a48dc02c5f5f08578aa79d3ab1bab4618b8c362f88684645a02bdcc" | |
| 3498 | + | dependencies = [ | |
| 3499 | + | "bytes", | |
| 3500 | + | "crc32fast", | |
| 3501 | + | "crossbeam-channel", | |
| 3502 | + | "gix-path", | |
| 3503 | + | "gix-trace", | |
| 3504 | + | "gix-utils", | |
| 3505 | + | "libc", | |
| 3506 | + | "once_cell", | |
| 3507 | + | "parking_lot", | |
| 3508 | + | "prodash", | |
| 3509 | + | "walkdir", | |
| 3510 | + | ] | |
| 3511 | + | ||
| 3512 | + | [[package]] | |
| 3513 | + | name = "gix-filter" | |
| 3514 | + | version = "0.33.0" | |
| 3515 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3516 | + | checksum = "5e7b5dbf524d97e839f642930c76d7f011c0791e7d11d8148989ac5af7c76aa8" | |
| 3517 | + | dependencies = [ | |
| 3518 | + | "bstr", | |
| 3519 | + | "encoding_rs", | |
| 3520 | + | "gix-attributes", | |
| 3521 | + | "gix-command", | |
| 3522 | + | "gix-hash", | |
| 3523 | + | "gix-object", | |
| 3524 | + | "gix-packetline", | |
| 3525 | + | "gix-path", | |
| 3526 | + | "gix-quote", | |
| 3527 | + | "gix-trace", | |
| 3528 | + | "gix-utils", | |
| 3529 | + | "smallvec", | |
| 3530 | + | "thiserror 2.0.18", | |
| 3531 | + | ] | |
| 3532 | + | ||
| 3533 | + | [[package]] | |
| 3534 | + | name = "gix-fs" | |
| 3535 | + | version = "0.22.0" | |
| 3536 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3537 | + | checksum = "865cf13fcaf5455220546cb9607c416bd1be9a6caafd143655a362fdeab64e80" | |
| 3538 | + | dependencies = [ | |
| 3539 | + | "bstr", | |
| 3540 | + | "gix-features", | |
| 3541 | + | "gix-path", | |
| 3542 | + | "gix-utils", | |
| 3543 | + | "thiserror 2.0.18", | |
| 3544 | + | ] | |
| 3545 | + | ||
| 3546 | + | [[package]] | |
| 3547 | + | name = "gix-glob" | |
| 3548 | + | version = "0.27.0" | |
| 3549 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3550 | + | checksum = "421e92a711554fa5827d1b0599d3389acdd0f6729e97a8c5a57d79af1e50bf36" | |
| 3551 | + | dependencies = [ | |
| 3552 | + | "bitflags 2.13.0", | |
| 3553 | + | "bstr", | |
| 3554 | + | "gix-features", | |
| 3555 | + | "gix-path", | |
| 3556 | + | ] | |
| 3557 | + | ||
| 3558 | + | [[package]] | |
| 3559 | + | name = "gix-hash" | |
| 3560 | + | version = "0.26.0" | |
| 3561 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3562 | + | checksum = "13adaa73415fd6c902310923f68d0b98e8cecf14b33ea58c02cc387cee56f54e" | |
| 3563 | + | dependencies = [ | |
| 3564 | + | "faster-hex", | |
| 3565 | + | "gix-features", | |
| 3566 | + | "sha1-checked", | |
| 3567 | + | "thiserror 2.0.18", | |
| 3568 | + | ] | |
| 3569 | + | ||
| 3570 | + | [[package]] | |
| 3571 | + | name = "gix-hashtable" | |
| 3572 | + | version = "0.16.0" | |
| 3573 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3574 | + | checksum = "78fccd6fea3bcf0b39c076bae60ae49b08daaf538b950202101a981f9d3c01d3" | |
| 3575 | + | dependencies = [ | |
| 3576 | + | "gix-hash", | |
| 3577 | + | "hashbrown 0.17.1", | |
| 3578 | + | "parking_lot", | |
| 3579 | + | ] | |
| 3580 | + | ||
| 3581 | + | [[package]] | |
| 3582 | + | name = "gix-ignore" | |
| 3583 | + | version = "0.22.0" | |
| 3584 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3585 | + | checksum = "12cff8e8aa125e39377456073e63df3334d9e5741372ddcc226198015076dda2" | |
| 3586 | + | dependencies = [ | |
| 3587 | + | "bstr", | |
| 3588 | + | "gix-glob", | |
| 3589 | + | "gix-path", | |
| 3590 | + | "gix-trace", | |
| 3591 | + | "unicode-bom", | |
| 3592 | + | ] | |
| 3593 | + | ||
| 3594 | + | [[package]] | |
| 3595 | + | name = "gix-imara-diff" | |
| 3596 | + | version = "0.2.4" | |
| 3597 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3598 | + | checksum = "1a791e6620676a875f362f3156ed213e73ca099a09bf992c18812abe65cc37b1" | |
| 3599 | + | dependencies = [ | |
| 3600 | + | "bstr", | |
| 3601 | + | "hashbrown 0.15.5", | |
| 3602 | + | ] | |
| 3603 | + | ||
| 3604 | + | [[package]] | |
| 3605 | + | name = "gix-index" | |
| 3606 | + | version = "0.54.0" | |
| 3607 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3608 | + | checksum = "5009c4e7e9f9b4cfaaab1153e49133eb04d79c015b5702d6c3d2ab94271a89c6" | |
| 3609 | + | dependencies = [ | |
| 3610 | + | "bitflags 2.13.0", | |
| 3611 | + | "bstr", | |
| 3612 | + | "filetime", | |
| 3613 | + | "fnv", | |
| 3614 | + | "gix-bitmap", | |
| 3615 | + | "gix-features", | |
| 3616 | + | "gix-fs", | |
| 3617 | + | "gix-hash", | |
| 3618 | + | "gix-lock", | |
| 3619 | + | "gix-object", | |
| 3620 | + | "gix-traverse", | |
| 3621 | + | "gix-utils", | |
| 3622 | + | "gix-validate", | |
| 3623 | + | "hashbrown 0.17.1", | |
| 3624 | + | "itoa", | |
| 3625 | + | "libc", | |
| 3626 | + | "memmap2", | |
| 3627 | + | "rustix 1.1.4", | |
| 3628 | + | "smallvec", | |
| 3629 | + | "thiserror 2.0.18", | |
| 3630 | + | ] | |
| 3631 | + | ||
| 3632 | + | [[package]] | |
| 3633 | + | name = "gix-lock" | |
| 3634 | + | version = "24.0.0" | |
| 3635 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3636 | + | checksum = "d4c69157820343bf1c6e4b88b9808e920900de02e18aaf5862b30ada43814848" | |
| 3637 | + | dependencies = [ | |
| 3638 | + | "gix-tempfile", | |
| 3639 | + | "gix-utils", | |
| 3640 | + | "thiserror 2.0.18", | |
| 3641 | + | ] | |
| 3642 | + | ||
| 3643 | + | [[package]] | |
| 3644 | + | name = "gix-object" | |
| 3645 | + | version = "0.63.0" | |
| 3646 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3647 | + | checksum = "0e48c235e7f886eb819fc878af75be889333dd3c38bee02ed7af48ae2cf596c4" | |
| 3648 | + | dependencies = [ | |
| 3649 | + | "bstr", | |
| 3650 | + | "gix-actor", | |
| 3651 | + | "gix-date", | |
| 3652 | + | "gix-features", | |
| 3653 | + | "gix-hash", | |
| 3654 | + | "gix-hashtable", | |
| 3655 | + | "gix-utils", | |
| 3656 | + | "gix-validate", |
Lines truncated
| @@ -127,8 +127,11 @@ | |||
| 127 | 127 | # Shared theme palette + the bundled theme set (Tier 0 creator theming). | |
| 128 | 128 | makeover = "2.4.1" | |
| 129 | 129 | ||
| 130 | - | # Git source browser | |
| 130 | + | # Git source browser. Migrating off git2 (libgit2, GPL-2.0 with linking | |
| 131 | + | # exception) onto gix, which is MIT/Apache-2.0. Both are linked while the port | |
| 132 | + | # is in flight; git2 goes away when the last read path moves over. | |
| 131 | 133 | git2 = { version = "0.21", features = ["vendored-libgit2"] } | |
| 134 | + | gix = { version = "0.86", default-features = false, features = ["blame", "revision", "sha1", "max-performance-safe"] } | |
| 132 | 135 | syntect = { version = "5", default-features = false, features = ["default-syntaxes", "default-themes", "html", "regex-fancy"] } | |
| 133 | 136 | regex = "1" | |
| 134 | 137 | semver = "1" |
| @@ -330,7 +330,13 @@ | |||
| 330 | 330 | // line, so blaming a large file is expensive; `read_file` returns early for | |
| 331 | 331 | // binary and over-cap blobs without loading them, so checking it first avoids | |
| 332 | 332 | // the walk entirely on a file we won't render (ultra-fuzz Run 6 R6-Sec-L3). | |
| 333 | - | let file = super::read_file(repo, commit_oid, path)?; | |
| 333 | + | // | |
| 334 | + | // The gate reads through gitoxide because `read_file` has already been | |
| 335 | + | // ported; the blame walk below is still libgit2. Both handles open the same | |
| 336 | + | // path, and the second open goes away when this function ports. | |
| 337 | + | let gix_repo = super::open_gix_repo_at(repo.path())?; | |
| 338 | + | let gix_oid = gix::ObjectId::from_bytes_or_panic(commit_oid.as_bytes()); | |
| 339 | + | let file = super::read_file(&gix_repo, gix_oid, path)?; | |
| 334 | 340 | if file.is_binary || file.size > crate::constants::GIT_MAX_FILE_SIZE_BYTES as u64 { | |
| 335 | 341 | return Err(GitError::PathNotFound); | |
| 336 | 342 | } |
| @@ -272,6 +272,28 @@ | |||
| 272 | 272 | Repository::open_bare(repo_path).map_err(|_| GitError::RepoNotFound) | |
| 273 | 273 | } | |
| 274 | 274 | ||
| 275 | + | /// Open a bare repository with gitoxide at an already-resolved, validated path. | |
| 276 | + | /// | |
| 277 | + | /// Sibling of [`open_repo_at`] while the libgit2 port is in flight. Unlike | |
| 278 | + | /// `git2::Repository`, `gix::Repository` is `Send`, so once every read path has | |
| 279 | + | /// moved over, the reopen-per-call dance in `routes::git::ResolvedRepo` can go | |
| 280 | + | /// away with it. | |
| 281 | + | /// Opened with `isolated()` options, so no ambient git configuration (the | |
| 282 | + | /// service account's `~/.gitconfig`, `/etc/gitconfig`, `GIT_*` environment | |
| 283 | + | /// variables) can change how a visitor's repository renders. | |
| 284 | + | pub(crate) fn open_gix_repo_at(repo_path: &Path) -> Result<gix::Repository, GitError> { | |
| 285 | + | gix::open_opts(repo_path, gix::open::Options::isolated()).map_err(|_| GitError::RepoNotFound) | |
| 286 | + | } | |
| 287 | + | ||
| 288 | + | /// Convert a gitoxide object id to a libgit2 one. | |
| 289 | + | /// | |
| 290 | + | /// Transitional: the ported modules hand back `gix::ObjectId` while | |
| 291 | + | /// `git/history.rs` still takes `git2::Oid`. Delete this along with the last | |
| 292 | + | /// git2 read path. | |
| 293 | + | pub(crate) fn to_git2_oid(id: gix::ObjectId) -> git2::Oid { | |
| 294 | + | git2::Oid::from_bytes(id.as_bytes()).expect("gix and libgit2 agree on hash length") | |
| 295 | + | } | |
| 296 | + | ||
| 275 | 297 | /// Get basic repository info. | |
| 276 | 298 | pub fn repo_info(repo: &Repository, name: &str) -> RepoInfo { | |
| 277 | 299 | let description = std::fs::read_to_string(repo.path().join("description")) | |
| @@ -502,6 +524,13 @@ | |||
| 502 | 524 | (tmp, bare_path) | |
| 503 | 525 | } | |
| 504 | 526 | ||
| 527 | + | /// Open the same test repo through gitoxide. The fixtures are still built | |
| 528 | + | /// with git2 (write paths are not part of this migration), while the ported | |
| 529 | + | /// read paths take a `gix::Repository`. | |
| 530 | + | fn open_gix(bare_path: &std::path::Path) -> gix::Repository { | |
| 531 | + | open_gix_repo_at(bare_path).unwrap() | |
| 532 | + | } | |
| 533 | + | ||
| 505 | 534 | #[test] | |
| 506 | 535 | fn open_repo_valid() { | |
| 507 | 536 | let (tmp, _) = make_test_repo(); | |
| @@ -554,8 +583,8 @@ | |||
| 554 | 583 | ||
| 555 | 584 | #[test] | |
| 556 | 585 | fn resolve_ref_and_list_tree() { | |
| 557 | - | let (tmp, _) = make_test_repo(); | |
| 558 | - | let repo = open_repo(tmp.path(), "owner", "testrepo").unwrap(); | |
| 586 | + | let (_tmp, bare_path) = make_test_repo(); | |
| 587 | + | let repo = open_gix(&bare_path); | |
| 559 | 588 | let oid = resolve_ref(&repo, "main").unwrap(); | |
| 560 | 589 | ||
| 561 | 590 | let items = list_tree(&repo, oid, "").unwrap(); | |
| @@ -568,8 +597,8 @@ | |||
| 568 | 597 | ||
| 569 | 598 | #[test] | |
| 570 | 599 | fn list_tree_subdir() { | |
| 571 | - | let (tmp, _) = make_test_repo(); | |
| 572 | - | let repo = open_repo(tmp.path(), "owner", "testrepo").unwrap(); | |
| 600 | + | let (_tmp, bare_path) = make_test_repo(); | |
| 601 | + | let repo = open_gix(&bare_path); | |
| 573 | 602 | let oid = resolve_ref(&repo, "main").unwrap(); | |
| 574 | 603 | ||
| 575 | 604 | let items = list_tree(&repo, oid, "src").unwrap(); | |
| @@ -580,8 +609,8 @@ | |||
| 580 | 609 | ||
| 581 | 610 | #[test] | |
| 582 | 611 | fn read_file_content() { | |
| 583 | - | let (tmp, _) = make_test_repo(); | |
| 584 | - | let repo = open_repo(tmp.path(), "owner", "testrepo").unwrap(); | |
| 612 | + | let (_tmp, bare_path) = make_test_repo(); | |
| 613 | + | let repo = open_gix(&bare_path); | |
| 585 | 614 | let oid = resolve_ref(&repo, "main").unwrap(); | |
| 586 | 615 | ||
| 587 | 616 | let file = read_file(&repo, oid, "src/main.rs").unwrap(); | |
| @@ -591,19 +620,19 @@ | |||
| 591 | 620 | ||
| 592 | 621 | #[test] | |
| 593 | 622 | fn read_file_nonexistent() { | |
| 594 | - | let (tmp, _) = make_test_repo(); | |
| 595 | - | let repo = open_repo(tmp.path(), "owner", "testrepo").unwrap(); | |
| 623 | + | let (_tmp, bare_path) = make_test_repo(); | |
| 624 | + | let repo = open_gix(&bare_path); | |
| 596 | 625 | let oid = resolve_ref(&repo, "main").unwrap(); | |
| 597 | 626 | assert!(read_file(&repo, oid, "nope.txt").is_err()); | |
| 598 | 627 | } | |
| 599 | 628 | ||
| 600 | 629 | #[test] | |
| 601 | 630 | fn commit_log_returns_commits() { | |
| 602 | - | let (tmp, _) = make_test_repo(); | |
| 631 | + | let (tmp, bare_path) = make_test_repo(); | |
| 603 | 632 | let repo = open_repo(tmp.path(), "owner", "testrepo").unwrap(); | |
| 604 | - | let oid = resolve_ref(&repo, "main").unwrap(); | |
| 633 | + | let oid = resolve_ref(&open_gix(&bare_path), "main").unwrap(); | |
| 605 | 634 | ||
| 606 | - | let commits = commit_log(&repo, oid, 10, 0).unwrap(); | |
| 635 | + | let commits = commit_log(&repo, to_git2_oid(oid), 10, 0).unwrap(); | |
| 607 | 636 | assert_eq!(commits.len(), 1); | |
| 608 | 637 | assert_eq!(commits[0].summary, "Initial commit"); | |
| 609 | 638 | assert_eq!(commits[0].author_name, "Test"); | |
| @@ -611,8 +640,8 @@ | |||
| 611 | 640 | ||
| 612 | 641 | #[test] | |
| 613 | 642 | fn find_readme_returns_rendered_markdown() { | |
| 614 | - | let (tmp, _) = make_test_repo(); | |
| 615 | - | let repo = open_repo(tmp.path(), "owner", "testrepo").unwrap(); | |
| 643 | + | let (_tmp, bare_path) = make_test_repo(); | |
| 644 | + | let repo = open_gix(&bare_path); | |
| 616 | 645 | let oid = resolve_ref(&repo, "main").unwrap(); | |
| 617 | 646 | ||
| 618 | 647 | let readme = find_readme(&repo, oid); | |
| @@ -624,8 +653,8 @@ | |||
| 624 | 653 | ||
| 625 | 654 | #[test] | |
| 626 | 655 | fn list_refs_includes_branch() { | |
| 627 | - | let (tmp, _) = make_test_repo(); | |
| 628 | - | let repo = open_repo(tmp.path(), "owner", "testrepo").unwrap(); | |
| 656 | + | let (_tmp, bare_path) = make_test_repo(); | |
| 657 | + | let repo = open_gix(&bare_path); | |
| 629 | 658 | ||
| 630 | 659 | let refs = list_refs(&repo); | |
| 631 | 660 | assert!(refs.iter().any(|r| r.name == "main" && r.is_branch)); | |
| @@ -718,9 +747,9 @@ | |||
| 718 | 747 | ||
| 719 | 748 | #[test] | |
| 720 | 749 | fn file_commit_log_filters_by_path() { | |
| 721 | - | let (tmp, _) = make_two_commit_repo(); | |
| 750 | + | let (tmp, bare_path) = make_two_commit_repo(); | |
| 722 | 751 | let repo = open_repo(tmp.path(), "owner", "testrepo").unwrap(); | |
| 723 | - | let oid = resolve_ref(&repo, "main").unwrap(); | |
| 752 | + | let oid = to_git2_oid(resolve_ref(&open_gix(&bare_path), "main").unwrap()); | |
| 724 | 753 | ||
| 725 | 754 | // src/main.rs was changed in both commits | |
| 726 | 755 | let main_rs_log = file_commit_log(&repo, oid, "src/main.rs", 10, 0, 1000).unwrap(); | |
| @@ -751,9 +780,9 @@ | |||
| 751 | 780 | ||
| 752 | 781 | #[test] | |
| 753 | 782 | fn file_commit_log_pagination() { | |
| 754 | - | let (tmp, _) = make_two_commit_repo(); | |
| 783 | + | let (tmp, bare_path) = make_two_commit_repo(); | |
| 755 | 784 | let repo = open_repo(tmp.path(), "owner", "testrepo").unwrap(); | |
| 756 | - | let oid = resolve_ref(&repo, "main").unwrap(); | |
| 785 | + | let oid = to_git2_oid(resolve_ref(&open_gix(&bare_path), "main").unwrap()); | |
| 757 | 786 | ||
| 758 | 787 | // Get first commit only (limit=1) | |
| 759 | 788 | let page1 = file_commit_log(&repo, oid, "src/main.rs", 1, 0, 1000).unwrap(); |
| @@ -1,8 +1,6 @@ | |||
| 1 | 1 | //! Object reading, tree listing, file content, and README detection. | |
| 2 | 2 | ||
| 3 | - | use std::path::Path; | |
| 4 | - | ||
| 5 | - | use git2::{ObjectType, Oid, Repository}; | |
| 3 | + | use gix::{ObjectId, Repository, objs::tree::EntryKind}; | |
| 6 | 4 | ||
| 7 | 5 | use super::{FileContent, GitError, TreeItem, TreeItemKind}; | |
| 8 | 6 | use crate::constants; | |
| @@ -10,38 +8,38 @@ | |||
| 10 | 8 | /// List entries in a tree at the given path (empty string for root). | |
| 11 | 9 | pub fn list_tree( | |
| 12 | 10 | repo: &Repository, | |
| 13 | - | commit_oid: Oid, | |
| 11 | + | commit_oid: ObjectId, | |
| 14 | 12 | path: &str, | |
| 15 | 13 | ) -> Result<Vec<TreeItem>, GitError> { | |
| 16 | 14 | let commit = repo | |
| 17 | 15 | .find_commit(commit_oid) | |
| 18 | 16 | .map_err(|_| GitError::RefNotFound)?; | |
| 19 | - | let tree = commit.tree().map_err(|_| GitError::TreeNotFound)?; | |
| 17 | + | let mut tree = commit.tree().map_err(|_| GitError::TreeNotFound)?; | |
| 20 | 18 | ||
| 21 | 19 | let target_tree = if path.is_empty() { | |
| 22 | 20 | tree | |
| 23 | 21 | } else { | |
| 24 | - | let entry = tree | |
| 25 | - | .get_path(Path::new(path)) | |
| 26 | - | .map_err(|_| GitError::PathNotFound)?; | |
| 27 | - | let obj = entry.to_object(repo).map_err(|_| GitError::PathNotFound)?; | |
| 28 | - | obj.peel_to_tree().map_err(|_| GitError::PathNotFound)? | |
| 22 | + | tree.peel_to_entry_by_path(path) | |
| 23 | + | .map_err(|_| GitError::PathNotFound)? | |
| 24 | + | .ok_or(GitError::PathNotFound)? | |
| 25 | + | .object() | |
| 26 | + | .map_err(|_| GitError::PathNotFound)? | |
| 27 | + | .peel_to_tree() | |
| 28 | + | .map_err(|_| GitError::PathNotFound)? | |
| 29 | 29 | }; | |
| 30 | 30 | ||
| 31 | 31 | let mut items: Vec<TreeItem> = target_tree | |
| 32 | 32 | .iter() | |
| 33 | 33 | .filter_map(|entry| { | |
| 34 | - | let name = entry.name().ok()?.to_string(); | |
| 35 | - | let kind = match entry.kind()? { | |
| 36 | - | ObjectType::Tree => TreeItemKind::Dir, | |
| 37 | - | ObjectType::Blob => TreeItemKind::File, | |
| 34 | + | let entry = entry.ok()?; | |
| 35 | + | let name = entry.filename().to_string(); | |
| 36 | + | let kind = match entry.mode().kind() { | |
| 37 | + | EntryKind::Tree => TreeItemKind::Dir, | |
| 38 | + | EntryKind::Blob | EntryKind::BlobExecutable => TreeItemKind::File, | |
| 38 | 39 | _ => return None, | |
| 39 | 40 | }; | |
| 40 | 41 | let size = if kind == TreeItemKind::File { | |
| 41 | - | entry | |
| 42 | - | .to_object(repo) | |
| 43 | - | .ok() | |
| 44 | - | .and_then(|o| o.as_blob().map(|b| b.size() as u64)) | |
| 42 | + | repo.find_header(entry.oid()).ok().map(|h| h.size()) | |
| 45 | 43 | } else { | |
| 46 | 44 | None | |
| 47 | 45 | }; | |
| @@ -60,20 +58,14 @@ | |||
| 60 | 58 | } | |
| 61 | 59 | ||
| 62 | 60 | /// Read a file blob at the given path. | |
| 63 | - | pub fn read_file(repo: &Repository, commit_oid: Oid, path: &str) -> Result<FileContent, GitError> { | |
| 64 | - | let commit = repo | |
| 65 | - | .find_commit(commit_oid) | |
| 66 | - | .map_err(|_| GitError::RefNotFound)?; | |
| 67 | - | let tree = commit.tree().map_err(|_| GitError::TreeNotFound)?; | |
| 61 | + | pub fn read_file( | |
| 62 | + | repo: &Repository, | |
| 63 | + | commit_oid: ObjectId, | |
| 64 | + | path: &str, | |
| 65 | + | ) -> Result<FileContent, GitError> { | |
| 66 | + | let content = read_blob_at(repo, commit_oid, path)?; | |
| 68 | 67 | ||
| 69 | - | let entry = tree | |
| 70 | - | .get_path(Path::new(path)) | |
| 71 | - | .map_err(|_| GitError::PathNotFound)?; | |
| 72 | - | let obj = entry.to_object(repo).map_err(|_| GitError::PathNotFound)?; | |
| 73 | - | let blob = obj.as_blob().ok_or(GitError::PathNotFound)?; | |
| 74 | - | ||
| 75 | - | let size = blob.size() as u64; | |
| 76 | - | let content = blob.content(); | |
| 68 | + | let size = content.len() as u64; | |
| 77 | 69 | ||
| 78 | 70 | // Binary detection: check first 8KB for null bytes | |
| 79 | 71 | let check_len = content.len().min(8192); | |
| @@ -95,7 +87,7 @@ | |||
| 95 | 87 | }); | |
| 96 | 88 | } | |
| 97 | 89 | ||
| 98 | - | let text = String::from_utf8_lossy(content).into_owned(); | |
| 90 | + | let text = String::from_utf8_lossy(&content).into_owned(); | |
| 99 | 91 | Ok(FileContent { | |
| 100 | 92 | content: text, | |
| 101 | 93 | size, | |
| @@ -103,11 +95,29 @@ | |||
| 103 | 95 | }) | |
| 104 | 96 | } | |
| 105 | 97 | ||
| 106 | - | /// Look for a README file at the tree root. | |
| 107 | - | pub fn find_readme(repo: &Repository, commit_oid: Oid) -> Option<String> { | |
| 108 | - | let commit = repo.find_commit(commit_oid).ok()?; | |
| 109 | - | let tree = commit.tree().ok()?; | |
| 98 | + | /// Read the raw bytes of a blob at `path` in the tree of `commit_oid`. | |
| 99 | + | fn read_blob_at(repo: &Repository, commit_oid: ObjectId, path: &str) -> Result<Vec<u8>, GitError> { | |
| 100 | + | let commit = repo | |
| 101 | + | .find_commit(commit_oid) | |
| 102 | + | .map_err(|_| GitError::RefNotFound)?; | |
| 103 | + | let mut tree = commit.tree().map_err(|_| GitError::TreeNotFound)?; | |
| 110 | 104 | ||
| 105 | + | let object = tree | |
| 106 | + | .peel_to_entry_by_path(path) | |
| 107 | + | .map_err(|_| GitError::PathNotFound)? | |
| 108 | + | .ok_or(GitError::PathNotFound)? | |
| 109 | + | .object() | |
| 110 | + | .map_err(|_| GitError::PathNotFound)?; | |
| 111 | + | ||
| 112 | + | if object.kind != gix::object::Kind::Blob { | |
| 113 | + | return Err(GitError::PathNotFound); | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | Ok(object.detach().data) | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | /// Look for a README file at the tree root. | |
| 120 | + | pub fn find_readme(repo: &Repository, commit_oid: ObjectId) -> Option<String> { | |
| 111 | 121 | let readme_names = [ | |
| 112 | 122 | "README.md", | |
| 113 | 123 | "readme.md", | |
| @@ -117,29 +127,27 @@ | |||
| 117 | 127 | ]; | |
| 118 | 128 | ||
| 119 | 129 | for name in &readme_names { | |
| 120 | - | if let Ok(entry) = tree.get_path(Path::new(name)) | |
| 121 | - | && let Ok(obj) = entry.to_object(repo) | |
| 122 | - | && let Some(blob) = obj.as_blob() | |
| 123 | - | { | |
| 124 | - | // Skip oversized READMEs to avoid unbounded memory allocation | |
| 125 | - | if blob.size() > constants::GIT_MAX_FILE_SIZE_BYTES { | |
| 126 | - | return Some("<p><em>README too large to display.</em></p>".to_string()); | |
| 127 | - | } | |
| 128 | - | let content = blob.content(); | |
| 129 | - | if !content[..content.len().min(8192)].contains(&0) { | |
| 130 | - | let text = String::from_utf8_lossy(content); | |
| 131 | - | if std::path::Path::new(name) | |
| 132 | - | .extension() | |
| 133 | - | .is_some_and(|e| e == "md") | |
| 134 | - | { | |
| 135 | - | return Some(docengine::render_permissive(&text)); | |
| 136 | - | } | |
| 137 | - | let escaped = text | |
| 138 | - | .replace('&', "&") | |
| 139 | - | .replace('<', "<") | |
| 140 | - | .replace('>', ">"); | |
| 141 | - | return Some(format!("<pre>{escaped}</pre>")); | |
| 130 | + | let Ok(content) = read_blob_at(repo, commit_oid, name) else { | |
| 131 | + | continue; | |
| 132 | + | }; | |
| 133 | + | ||
| 134 | + | // Skip oversized READMEs to avoid unbounded memory allocation | |
| 135 | + | if content.len() > constants::GIT_MAX_FILE_SIZE_BYTES { | |
| 136 | + | return Some("<p><em>README too large to display.</em></p>".to_string()); | |
| 137 | + | } | |
| 138 | + | if !content[..content.len().min(8192)].contains(&0) { | |
| 139 | + | let text = String::from_utf8_lossy(&content); | |
| 140 | + | if std::path::Path::new(name) | |
| 141 | + | .extension() | |
| 142 | + | .is_some_and(|e| e == "md") | |
| 143 | + | { | |
| 144 | + | return Some(docengine::render_permissive(&text)); | |
| 142 | 145 | } | |
| 146 | + | let escaped = text | |
| 147 | + | .replace('&', "&") | |
| 148 | + | .replace('<', "<") | |
| 149 | + | .replace('>', ">"); | |
| 150 | + | return Some(format!("<pre>{escaped}</pre>")); | |
| 143 | 151 | } | |
| 144 | 152 | } | |
| 145 | 153 |
| @@ -1,31 +1,30 @@ | |||
| 1 | 1 | //! Reference management, branch/tag listing and ref resolution. | |
| 2 | 2 | ||
| 3 | + | use gix::{ObjectId, Repository, bstr::ByteSlice}; | |
| 4 | + | ||
| 3 | 5 | use super::{GitError, RefInfo}; | |
| 4 | - | use git2::{ObjectType, Oid, Repository}; | |
| 5 | 6 | ||
| 6 | 7 | /// Resolve a ref name to a commit OID. Tries branch, tag, raw OID, then revparse. | |
| 7 | - | pub fn resolve_ref(repo: &Repository, refname: &str) -> Result<Oid, GitError> { | |
| 8 | + | pub fn resolve_ref(repo: &Repository, refname: &str) -> Result<ObjectId, GitError> { | |
| 8 | 9 | // Try as branch | |
| 9 | - | if let Ok(branch) = repo.find_branch(refname, git2::BranchType::Local) | |
| 10 | - | && let Some(target) = branch.get().target() | |
| 10 | + | if let Ok(mut reference) = repo.find_reference(&format!("refs/heads/{refname}")) | |
| 11 | + | && let Ok(id) = reference.peel_to_id() | |
| 11 | 12 | { | |
| 12 | - | return Ok(target); | |
| 13 | + | return Ok(id.detach()); | |
| 13 | 14 | } | |
| 14 | 15 | ||
| 15 | - | // Try as tag | |
| 16 | - | if let Ok(reference) = repo.find_reference(&format!("refs/tags/{refname}")) { | |
| 17 | - | let obj = reference | |
| 18 | - | .peel(ObjectType::Commit) | |
| 19 | - | .map_err(|_| GitError::RefNotFound)?; | |
| 20 | - | return Ok(obj.id()); | |
| 16 | + | // Try as tag. Peeling follows an annotated tag through to the commit it | |
| 17 | + | // points at, which is what `peel(ObjectType::Commit)` did under libgit2. | |
| 18 | + | if let Ok(mut reference) = repo.find_reference(&format!("refs/tags/{refname}")) { | |
| 19 | + | let id = reference.peel_to_id().map_err(|_| GitError::RefNotFound)?; | |
| 20 | + | return Ok(id.detach()); | |
| 21 | 21 | } | |
| 22 | 22 | ||
| 23 | 23 | // Try as raw OID | |
| 24 | - | if let Ok(oid) = Oid::from_str(refname) { | |
| 25 | - | // Verify it resolves to a commit | |
| 26 | - | if repo.find_commit(oid).is_ok() { | |
| 27 | - | return Ok(oid); | |
| 28 | - | } | |
| 24 | + | if let Ok(oid) = ObjectId::from_hex(refname.as_bytes()) | |
| 25 | + | && repo.find_commit(oid).is_ok() | |
| 26 | + | { | |
| 27 | + | return Ok(oid); | |
| 29 | 28 | } | |
| 30 | 29 | ||
| 31 | 30 | // Reject arbitrary revparse expressions (e.g. HEAD~99999, @{upstream}). | |
| @@ -33,10 +32,11 @@ | |||
| 33 | 32 | if refname | |
| 34 | 33 | .chars() | |
| 35 | 34 | .all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '_' | '/')) | |
| 36 | - | && let Ok(obj) = repo.revparse_single(refname) | |
| 37 | - | && let Ok(commit) = obj.peel(ObjectType::Commit) | |
| 35 | + | && let Ok(id) = repo.rev_parse_single(refname) | |
| 36 | + | && let Ok(object) = id.object() | |
| 37 | + | && let Ok(commit) = object.peel_to_commit() | |
| 38 | 38 | { | |
| 39 | - | return Ok(commit.id()); | |
| 39 | + | return Ok(commit.id); | |
| 40 | 40 | } | |
| 41 | 41 | ||
| 42 | 42 | Err(GitError::RefNotFound) | |
| @@ -46,21 +46,23 @@ | |||
| 46 | 46 | pub fn list_refs(repo: &Repository) -> Vec<RefInfo> { | |
| 47 | 47 | let mut refs = Vec::new(); | |
| 48 | 48 | ||
| 49 | - | if let Ok(branches) = repo.branches(Some(git2::BranchType::Local)) { | |
| 49 | + | let Ok(platform) = repo.references() else { | |
| 50 | + | return refs; | |
| 51 | + | }; | |
| 52 | + | ||
| 53 | + | if let Ok(branches) = platform.prefixed("refs/heads/") { | |
| 50 | 54 | for branch in branches.flatten() { | |
| 51 | - | if let Some(name) = branch.0.name().ok().flatten() { | |
| 52 | - | refs.push(RefInfo { | |
| 53 | - | name: name.to_string(), | |
| 54 | - | is_branch: true, | |
| 55 | - | }); | |
| 56 | - | } | |
| 55 | + | refs.push(RefInfo { | |
| 56 | + | name: branch.name().shorten().to_str_lossy().into_owned(), | |
| 57 | + | is_branch: true, | |
| 58 | + | }); | |
| 57 | 59 | } | |
| 58 | 60 | } | |
| 59 | 61 | ||
| 60 | - | if let Ok(tags) = repo.tag_names(None) { | |
| 61 | - | for tag in tags.iter().flatten().flatten() { | |
| 62 | + | if let Ok(tags) = platform.prefixed("refs/tags/") { | |
| 63 | + | for tag in tags.flatten() { | |
| 62 | 64 | refs.push(RefInfo { | |
| 63 | - | name: tag.to_string(), | |
| 65 | + | name: tag.name().shorten().to_str_lossy().into_owned(), | |
| 64 | 66 | is_branch: false, | |
| 65 | 67 | }); | |
| 66 | 68 | } |
| @@ -48,12 +48,12 @@ | |||
| 48 | 48 | .await?; | |
| 49 | 49 | let repo_name_c = repo_name.clone(); | |
| 50 | 50 | let (info, refs, tree_items, readme_html) = resolved | |
| 51 | - | .with_repo(move |repo| { | |
| 51 | + | .with_repo(move |repo, gix_repo| { | |
| 52 | 52 | let info = git::repo_info(repo, &repo_name_c); | |
| 53 | - | let refs = git::list_refs(repo); | |
| 54 | - | let commit_oid = git::resolve_ref(repo, &info.default_branch)?; | |
| 55 | - | let tree_items = git::list_tree(repo, commit_oid, "")?; | |
| 56 | - | let readme_html = git::find_readme(repo, commit_oid); | |
| 53 | + | let refs = git::list_refs(gix_repo); | |
| 54 | + | let commit_oid = git::resolve_ref(gix_repo, &info.default_branch)?; | |
| 55 | + | let tree_items = git::list_tree(gix_repo, commit_oid, "")?; | |
| 56 | + | let readme_html = git::find_readme(gix_repo, commit_oid); | |
| 57 | 57 | Ok((info, refs, tree_items, readme_html)) | |
| 58 | 58 | }) | |
| 59 | 59 | .await?; | |
| @@ -111,11 +111,11 @@ | |||
| 111 | 111 | .await?; | |
| 112 | 112 | let git_ref_c = git_ref.clone(); | |
| 113 | 113 | let (refs, tree_items, readme_html) = resolved | |
| 114 | - | .with_repo(move |repo| { | |
| 115 | - | let refs = git::list_refs(repo); | |
| 116 | - | let commit_oid = git::resolve_ref(repo, &git_ref_c)?; | |
| 117 | - | let tree_items = git::list_tree(repo, commit_oid, "")?; | |
| 118 | - | let readme_html = git::find_readme(repo, commit_oid); | |
| 114 | + | .with_repo(move |_repo, gix_repo| { | |
| 115 | + | let refs = git::list_refs(gix_repo); | |
| 116 | + | let commit_oid = git::resolve_ref(gix_repo, &git_ref_c)?; | |
| 117 | + | let tree_items = git::list_tree(gix_repo, commit_oid, "")?; | |
| 118 | + | let readme_html = git::find_readme(gix_repo, commit_oid); | |
| 119 | 119 | Ok((refs, tree_items, readme_html)) | |
| 120 | 120 | }) | |
| 121 | 121 | .await?; | |
| @@ -186,13 +186,13 @@ | |||
| 186 | 186 | let git_ref_c = git_ref.clone(); | |
| 187 | 187 | let path_c = path.clone(); | |
| 188 | 188 | let dir_or_file = resolved | |
| 189 | - | .with_repo(move |repo| { | |
| 190 | - | let commit_oid = git::resolve_ref(repo, &git_ref_c)?; | |
| 191 | - | match git::list_tree(repo, commit_oid, &path_c) { | |
| 192 | - | Ok(tree_items) => Ok(DirOrFile::Dir(tree_items, git::list_refs(repo))), | |
| 189 | + | .with_repo(move |_repo, gix_repo| { | |
| 190 | + | let commit_oid = git::resolve_ref(gix_repo, &git_ref_c)?; | |
| 191 | + | match git::list_tree(gix_repo, commit_oid, &path_c) { | |
| 192 | + | Ok(tree_items) => Ok(DirOrFile::Dir(tree_items, git::list_refs(gix_repo))), | |
| 193 | 193 | Err(_) => { | |
| 194 | - | let file_content = git::read_file(repo, commit_oid, &path_c)?; | |
| 195 | - | Ok(DirOrFile::File(file_content, git::list_refs(repo))) | |
| 194 | + | let file_content = git::read_file(gix_repo, commit_oid, &path_c)?; | |
| 195 | + | Ok(DirOrFile::File(file_content, git::list_refs(gix_repo))) | |
| 196 | 196 | } | |
| 197 | 197 | } | |
| 198 | 198 | }) | |
| @@ -300,10 +300,10 @@ | |||
| 300 | 300 | ||
| 301 | 301 | let git_ref_c = git_ref.clone(); | |
| 302 | 302 | let (refs, commits) = resolved | |
| 303 | - | .with_repo(move |repo| { | |
| 304 | - | let refs = git::list_refs(repo); | |
| 305 | - | let commit_oid = git::resolve_ref(repo, &git_ref_c)?; | |
| 306 | - | let commits = git::commit_log(repo, commit_oid, limit + 1, offset)?; | |
| 303 | + | .with_repo(move |repo, gix_repo| { | |
| 304 | + | let refs = git::list_refs(gix_repo); | |
| 305 | + | let commit_oid = git::resolve_ref(gix_repo, &git_ref_c)?; | |
| 306 | + | let commits = git::commit_log(repo, git::to_git2_oid(commit_oid), limit + 1, offset)?; | |
| 307 | 307 | Ok((refs, commits)) | |
| 308 | 308 | }) | |
| 309 | 309 | .await?; | |
| @@ -354,7 +354,7 @@ | |||
| 354 | 354 | let oid = git2::Oid::from_str(&oid_str).map_err(|_| AppError::NotFound)?; | |
| 355 | 355 | let repo_name_c = repo_name.clone(); | |
| 356 | 356 | let (detail, diff_files, refs, info) = resolved | |
| 357 | - | .with_repo(move |repo| { | |
| 357 | + | .with_repo(move |repo, gix_repo| { | |
| 358 | 358 | repo.find_commit(oid).map_err(|_| AppError::NotFound)?; | |
| 359 | 359 | let detail = git::commit_detail(repo, oid)?; | |
| 360 | 360 | let diff_files = git::commit_diff( | |
| @@ -363,7 +363,7 @@ | |||
| 363 | 363 | constants::GIT_DIFF_MAX_FILES, | |
| 364 | 364 | constants::GIT_DIFF_MAX_LINES, | |
| 365 | 365 | )?; | |
| 366 | - | let refs = git::list_refs(repo); | |
| 366 | + | let refs = git::list_refs(gix_repo); | |
| 367 | 367 | let info = git::repo_info(repo, &repo_name_c); | |
| 368 | 368 | Ok((detail, diff_files, refs, info)) | |
| 369 | 369 | }) | |
| @@ -417,10 +417,10 @@ | |||
| 417 | 417 | let git_ref_c = git_ref.clone(); | |
| 418 | 418 | let path_c = path.clone(); | |
| 419 | 419 | let (blame_lines, refs) = resolved | |
| 420 | - | .with_repo(move |repo| { | |
| 421 | - | let commit_oid = git::resolve_ref(repo, &git_ref_c)?; | |
| 422 | - | let blame_lines = git::blame_file(repo, commit_oid, &path_c)?; | |
| 423 | - | let refs = git::list_refs(repo); | |
| 420 | + | .with_repo(move |repo, gix_repo| { | |
| 421 | + | let commit_oid = git::resolve_ref(gix_repo, &git_ref_c)?; | |
| 422 | + | let blame_lines = git::blame_file(repo, git::to_git2_oid(commit_oid), &path_c)?; | |
| 423 | + | let refs = git::list_refs(gix_repo); | |
| 424 | 424 | Ok((blame_lines, refs)) | |
| 425 | 425 | }) | |
| 426 | 426 | .await?; | |
| @@ -544,12 +544,12 @@ | |||
| 544 | 544 | let git_ref_c = git_ref.clone(); | |
| 545 | 545 | let path_c = path.clone(); | |
| 546 | 546 | let (refs, commits) = resolved | |
| 547 | - | .with_repo(move |repo| { | |
| 548 | - | let refs = git::list_refs(repo); | |
| 549 | - | let commit_oid = git::resolve_ref(repo, &git_ref_c)?; | |
| 547 | + | .with_repo(move |repo, gix_repo| { | |
| 548 | + | let refs = git::list_refs(gix_repo); | |
| 549 | + | let commit_oid = git::resolve_ref(gix_repo, &git_ref_c)?; | |
| 550 | 550 | let commits = git::file_commit_log( | |
| 551 | 551 | repo, | |
| 552 | - | commit_oid, | |
| 552 | + | git::to_git2_oid(commit_oid), | |
| 553 | 553 | &path_c, | |
| 554 | 554 | limit + 1, | |
| 555 | 555 | offset, |
| @@ -140,8 +140,8 @@ | |||
| 140 | 140 | /// | |
| 141 | 141 | /// We store the *path* rather than an open `git2::Repository` because the repo | |
| 142 | 142 | /// handle is `!Send` and would pin the synchronous libgit2 work to the async | |
| 143 | - | /// runtime. Handlers run their libgit2 reads through [`ResolvedRepo::with_repo`], | |
| 144 | - | /// which reopens the repo on the blocking pool. | |
| 143 | + | /// runtime. Handlers run their reads through [`ResolvedRepo::with_repo`], which | |
| 144 | + | /// reopens the repo on the blocking pool. | |
| 145 | 145 | pub(crate) struct ResolvedRepo { | |
| 146 | 146 | pub(crate) db_repo: DbGitRepo, | |
| 147 | 147 | pub(crate) repo_path: PathBuf, | |
| @@ -149,19 +149,25 @@ | |||
| 149 | 149 | } | |
| 150 | 150 | ||
| 151 | 151 | impl ResolvedRepo { | |
| 152 | - | /// Run a synchronous libgit2 closure on the blocking pool against a freshly | |
| 153 | - | /// opened handle. `git2::Repository` is `!Send`, so the handle is reopened | |
| 154 | - | /// inside the closure rather than moved across the await. Keeps libgit2's | |
| 155 | - | /// disk + zlib work off the Tokio worker threads (Run #14 Perf LOW). | |
| 152 | + | /// Run a synchronous git closure on the blocking pool against freshly opened | |
| 153 | + | /// handles. `git2::Repository` is `!Send`, so the handle is reopened inside | |
| 154 | + | /// the closure rather than moved across the await. Keeps the disk + zlib | |
| 155 | + | /// work off the Tokio worker threads (Run #14 Perf LOW). | |
| 156 | + | /// | |
| 157 | + | /// Both handles are passed while the port from libgit2 to gitoxide is in | |
| 158 | + | /// flight: ported reads take the `gix` handle, the rest still take the | |
| 159 | + | /// `git2` one. The `git2` parameter goes away with the last git2 read path, | |
| 160 | + | /// and the reopen itself can go with it, since `gix::Repository` is `Send`. | |
| 156 | 161 | pub(crate) async fn with_repo<T, F>(&self, f: F) -> Result<T> | |
| 157 | 162 | where | |
| 158 | 163 | T: Send + 'static, | |
| 159 | - | F: FnOnce(&git2::Repository) -> Result<T> + Send + 'static, | |
| 164 | + | F: FnOnce(&git2::Repository, &gix::Repository) -> Result<T> + Send + 'static, | |
| 160 | 165 | { | |
| 161 | 166 | let path = self.repo_path.clone(); | |
| 162 | 167 | tokio::task::spawn_blocking(move || { | |
| 163 | 168 | let repo = git::open_repo_at(&path)?; | |
| 164 | - | f(&repo) | |
| 169 | + | let gix_repo = git::open_gix_repo_at(&path)?; | |
| 170 | + | f(&repo, &gix_repo) | |
| 165 | 171 | }) | |
| 166 | 172 | .await | |
| 167 | 173 | .map_err(|e| AppError::Internal(anyhow::anyhow!("git worker task failed: {e}")))? |
| @@ -42,20 +42,27 @@ | |||
| 42 | 42 | let git_ref_c = git_ref.clone(); | |
| 43 | 43 | let path_c = path.clone(); | |
| 44 | 44 | let content: Vec<u8> = resolved | |
| 45 | - | .with_repo(move |repo| { | |
| 46 | - | let commit_oid = git::resolve_ref(repo, &git_ref_c)?; | |
| 47 | - | let commit = repo | |
| 45 | + | .with_repo(move |_repo, gix_repo| { | |
| 46 | + | let commit_oid = git::resolve_ref(gix_repo, &git_ref_c)?; | |
| 47 | + | let commit = gix_repo | |
| 48 | 48 | .find_commit(commit_oid) | |
| 49 | 49 | .map_err(|_| AppError::NotFound)?; | |
| 50 | - | let tree = commit.tree().map_err(|_| AppError::NotFound)?; | |
| 50 | + | let mut tree = commit.tree().map_err(|_| AppError::NotFound)?; | |
| 51 | 51 | let entry = tree | |
| 52 | - | .get_path(std::path::Path::new(&path_c)) | |
| 53 | - | .map_err(|_| AppError::NotFound)?; | |
| 54 | - | let obj = entry.to_object(repo).map_err(|_| AppError::NotFound)?; | |
| 55 | - | let blob = obj.as_blob().ok_or(AppError::NotFound)?; | |
| 52 | + | .peel_to_entry_by_path(path_c.as_str()) | |
| 53 | + | .map_err(|_| AppError::NotFound)? | |
| 54 | + | .ok_or(AppError::NotFound)?; | |
| 55 | + | if !entry.mode().is_blob() { | |
| 56 | + | return Err(AppError::NotFound); | |
| 57 | + | } | |
| 56 | 58 | ||
| 57 | - | // Prevent unbounded memory allocation from large blobs. | |
| 58 | - | let size = blob.size(); | |
| 59 | + | // Prevent unbounded memory allocation from large blobs. The header | |
| 60 | + | // carries the size without decoding the object, so an over-cap blob | |
| 61 | + | // is rejected before it is read. | |
| 62 | + | let size = gix_repo | |
| 63 | + | .find_header(entry.oid()) | |
| 64 | + | .map_err(|_| AppError::NotFound)? | |
| 65 | + | .size() as usize; | |
| 59 | 66 | if size > constants::GIT_RAW_MAX_BYTES { | |
| 60 | 67 | return Err(AppError::BadRequest(format!( | |
| 61 | 68 | "File too large for raw download ({} bytes, max {})", | |
| @@ -63,7 +70,8 @@ | |||
| 63 | 70 | constants::GIT_RAW_MAX_BYTES, | |
| 64 | 71 | ))); | |
| 65 | 72 | } | |
| 66 | - | Ok(blob.content().to_vec()) | |
| 73 | + | let object = entry.object().map_err(|_| AppError::NotFound)?; | |
| 74 | + | Ok(object.detach().data) | |
| 67 | 75 | }) | |
| 68 | 76 | .await?; | |
| 69 | 77 |