perf: use gitoxide for git_status and git_metrics modules (#6476)

This commit is contained in:
Sebastian Thiel
2025-04-26 15:43:41 +02:00
committed by GitHub
parent 8e4f8096f1
commit daf8d93d27
13 changed files with 1226 additions and 202 deletions
+6 -1
View File
@@ -664,7 +664,8 @@
"style": "red bold",
"typechanged": "",
"untracked": "?",
"up_to_date": ""
"up_to_date": "",
"use_git_executable": false
},
"allOf": [
{
@@ -3649,6 +3650,10 @@
"default": false,
"type": "boolean"
},
"use_git_executable": {
"default": false,
"type": "boolean"
},
"windows_starship": {
"type": [
"string",
Generated
+317 -78
View File
@@ -360,6 +360,12 @@ version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytesize"
version = "1.3.3"
@@ -607,6 +613,20 @@ dependencies = [
"typenum",
]
[[package]]
name = "dashmap"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "deelevate"
version = "0.2.0"
@@ -846,10 +866,11 @@ dependencies = [
[[package]]
name = "faster-hex"
version = "0.9.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73"
dependencies = [
"heapless",
"serde",
]
@@ -980,27 +1001,33 @@ dependencies = [
[[package]]
name = "gix"
version = "0.71.0"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a61e71ec6817fc3c9f12f812682cfe51ee6ea0d2e27e02fc3849c35524617435"
checksum = "01237e8d3d78581f71642be8b0c2ae8c0b2b5c251c9c5d9ebbea3c1ea280dce8"
dependencies = [
"gix-actor",
"gix-attributes",
"gix-command",
"gix-commitgraph",
"gix-config",
"gix-date",
"gix-diff",
"gix-dir",
"gix-discover",
"gix-features",
"gix-filter",
"gix-fs",
"gix-glob",
"gix-hash",
"gix-hashtable",
"gix-ignore",
"gix-index",
"gix-lock",
"gix-object",
"gix-odb",
"gix-pack",
"gix-path",
"gix-pathspec",
"gix-protocol",
"gix-ref",
"gix-refspec",
@@ -1008,12 +1035,15 @@ dependencies = [
"gix-revwalk",
"gix-sec",
"gix-shallow",
"gix-status",
"gix-submodule",
"gix-tempfile",
"gix-trace",
"gix-traverse",
"gix-url",
"gix-utils",
"gix-validate",
"gix-worktree",
"once_cell",
"smallvec",
"thiserror 2.0.12",
@@ -1021,9 +1051,9 @@ dependencies = [
[[package]]
name = "gix-actor"
version = "0.34.0"
version = "0.35.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f438c87d4028aca4b82f82ba8d8ab1569823cfb3e5bc5fa8456a71678b2a20e7"
checksum = "6b300e6e4f31f3f6bd2de5e2b0caab192ced00dc0fcd0f7cc56e28c575c8e1ff"
dependencies = [
"bstr",
"gix-date",
@@ -1033,6 +1063,23 @@ dependencies = [
"winnow",
]
[[package]]
name = "gix-attributes"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e26b3ac280ddb25bb6980d34f4a82ee326f78bf2c6d4ea45eef2d940048b8e"
dependencies = [
"bstr",
"gix-glob",
"gix-path",
"gix-quote",
"gix-trace",
"kstring",
"smallvec",
"thiserror 2.0.12",
"unicode-bom",
]
[[package]]
name = "gix-bitmap"
version = "0.2.14"
@@ -1053,9 +1100,9 @@ dependencies = [
[[package]]
name = "gix-command"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0378995847773a697f8e157fe2963ecf3462fe64be05b7b3da000b3b472def8"
checksum = "d2f47f3fb4ba33644061e8e0e1030ef2a937d42dc969553118c320a205a9fb28"
dependencies = [
"bstr",
"gix-path",
@@ -1066,9 +1113,9 @@ dependencies = [
[[package]]
name = "gix-commitgraph"
version = "0.27.0"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "043cbe49b7a7505150db975f3cb7c15833335ac1e26781f615454d9d640a28fe"
checksum = "e05050fd6caa6c731fe3bd7f9485b3b520be062d3d139cb2626e052d6c127951"
dependencies = [
"bstr",
"gix-chunk",
@@ -1079,9 +1126,9 @@ dependencies = [
[[package]]
name = "gix-config"
version = "0.44.0"
version = "0.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c6f830bf746604940261b49abf7f655d2c19cadc9f4142ae9379e3a316e8cfa"
checksum = "48f3c8f357ae049bfb77493c2ec9010f58cfc924ae485e1116c3718fc0f0d881"
dependencies = [
"bstr",
"gix-config-value",
@@ -1100,9 +1147,9 @@ dependencies = [
[[package]]
name = "gix-config-value"
version = "0.14.12"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dc2c844c4cf141884678cabef736fd91dd73068b9146e6f004ba1a0457944b6"
checksum = "439d62e241dae2dffd55bfeeabe551275cf9d9f084c5ebc6b48bad49d03285b7"
dependencies = [
"bitflags 2.9.0",
"bstr",
@@ -1113,33 +1160,66 @@ dependencies = [
[[package]]
name = "gix-date"
version = "0.9.4"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daa30058ec7d3511fbc229e4f9e696a35abd07ec5b82e635eff864a2726217e4"
checksum = "3a98593f1f1e14b9fa15c5b921b2c465e904d698b9463e21bb377be8376c3c1a"
dependencies = [
"bstr",
"itoa",
"jiff",
"smallvec",
"thiserror 2.0.12",
]
[[package]]
name = "gix-diff"
version = "0.51.0"
version = "0.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c975dad2afc85e4e233f444d1efbe436c3cdcf3a07173984509c436d00a3f8"
checksum = "5e9b43e95fe352da82a969f0c84ff860c2de3e724d93f6681fedbcd6c917f252"
dependencies = [
"bstr",
"gix-attributes",
"gix-command",
"gix-filter",
"gix-fs",
"gix-hash",
"gix-index",
"gix-object",
"gix-path",
"gix-pathspec",
"gix-tempfile",
"gix-trace",
"gix-traverse",
"gix-worktree",
"imara-diff",
"thiserror 2.0.12",
]
[[package]]
name = "gix-dir"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01e6e2dc5b8917142d0ffe272209d1671e45b771e433f90186bc71c016792e87"
dependencies = [
"bstr",
"gix-discover",
"gix-fs",
"gix-ignore",
"gix-index",
"gix-object",
"gix-path",
"gix-pathspec",
"gix-trace",
"gix-utils",
"gix-worktree",
"thiserror 2.0.12",
]
[[package]]
name = "gix-discover"
version = "0.39.0"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fb8a4349b854506a3915de18d3341e5f1daa6b489c8affc9ca0d69efe86781"
checksum = "dccfe3e25b4ea46083916c56db3ba9d1e6ef6dce54da485f0463f9fc0fe1837c"
dependencies = [
"bstr",
"dunce",
@@ -1153,9 +1233,9 @@ dependencies = [
[[package]]
name = "gix-features"
version = "0.41.1"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016d6050219458d14520fe22bdfdeb9cb71631dec9bc2724767c983f60109634"
checksum = "56f4399af6ec4fd9db84dd4cf9656c5c785ab492ab40a7c27ea92b4241923fed"
dependencies = [
"crc32fast",
"crossbeam-channel",
@@ -1172,10 +1252,31 @@ dependencies = [
]
[[package]]
name = "gix-fs"
version = "0.14.0"
name = "gix-filter"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "951e886120dc5fa8cac053e5e5c89443f12368ca36811b2e43d1539081f9c111"
checksum = "f90c21f0d61778f518bbb7c431b00247bf4534b2153c3e85bcf383876c55ca6c"
dependencies = [
"bstr",
"encoding_rs",
"gix-attributes",
"gix-command",
"gix-hash",
"gix-object",
"gix-packetline-blocking",
"gix-path",
"gix-quote",
"gix-trace",
"gix-utils",
"smallvec",
"thiserror 2.0.12",
]
[[package]]
name = "gix-fs"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a0637149b4ef24d3ea55f81f77231401c8463fae6da27331c987957eb597c7"
dependencies = [
"bstr",
"fastrand",
@@ -1187,9 +1288,9 @@ dependencies = [
[[package]]
name = "gix-glob"
version = "0.19.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20972499c03473e773a2099e5fd0c695b9b72465837797a51a43391a1635a030"
checksum = "2926b03666e83b8d01c10cf06e5733521aacbd2d97179a4c9b1fdddabb9e937d"
dependencies = [
"bitflags 2.9.0",
"bstr",
@@ -1199,9 +1300,9 @@ dependencies = [
[[package]]
name = "gix-hash"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "834e79722063958b03342edaa1e17595cd2939bb2b3306b3225d0815566dcb49"
checksum = "8d4900562c662852a6b42e2ef03442eccebf24f047d8eab4f23bc12ef0d785d8"
dependencies = [
"faster-hex",
"gix-features",
@@ -1211,9 +1312,9 @@ dependencies = [
[[package]]
name = "gix-hashtable"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f06066d8702a9186dc1fdc1ed751ff2d7e924ceca21cb5d51b8f990c9c2e014a"
checksum = "b5b5cb3c308b4144f2612ff64e32130e641279fcf1a84d8d40dad843b4f64904"
dependencies = [
"gix-hash",
"hashbrown 0.14.5",
@@ -1221,10 +1322,23 @@ dependencies = [
]
[[package]]
name = "gix-index"
version = "0.39.0"
name = "gix-ignore"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "855bece2d4153453aa5d0a80d51deea1ce8cd6a3b4cf213da85ac344ccb908a7"
checksum = "ae358c3c96660b10abc7da63c06788dfded603e717edbd19e38c6477911b71c8"
dependencies = [
"bstr",
"gix-glob",
"gix-path",
"gix-trace",
"unicode-bom",
]
[[package]]
name = "gix-index"
version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d505aea7d7c4267a3153cb90c712a89970b4dd02a2cb3205be322891f530b5"
dependencies = [
"bitflags 2.9.0",
"bstr",
@@ -1243,16 +1357,16 @@ dependencies = [
"itoa",
"libc",
"memmap2",
"rustix 0.38.44",
"rustix 1.0.5",
"smallvec",
"thiserror 2.0.12",
]
[[package]]
name = "gix-lock"
version = "17.0.0"
version = "17.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df47b8f11c34520db5541bc5fc9fbc8e4b0bdfcec3736af89ccb1a5728a0126f"
checksum = "570f8b034659f256366dc90f1a24924902f20acccd6a15be96d44d1269e7a796"
dependencies = [
"gix-tempfile",
"gix-utils",
@@ -1261,9 +1375,9 @@ dependencies = [
[[package]]
name = "gix-object"
version = "0.48.0"
version = "0.49.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4943fcdae6ffc135920c9ea71e0362ed539182924ab7a85dd9dac8d89b0dd69a"
checksum = "d957ca3640c555d48bb27f8278c67169fa1380ed94f6452c5590742524c40fbb"
dependencies = [
"bstr",
"gix-actor",
@@ -1282,9 +1396,9 @@ dependencies = [
[[package]]
name = "gix-odb"
version = "0.68.0"
version = "0.69.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50306d40dcc982eb6b7593103f066ea6289c7b094cb9db14f3cd2be0b9f5e610"
checksum = "868f703905fdbcfc1bd750942f82419903ecb7039f5288adb5206d6de405e0c9"
dependencies = [
"arc-swap",
"gix-date",
@@ -1303,9 +1417,9 @@ dependencies = [
[[package]]
name = "gix-pack"
version = "0.58.0"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b65fffb09393c26624ca408d32cfe8776fb94cd0a5cdf984905e1d2f39779cb"
checksum = "9d49c55d69c8449f2a0a5a77eb9cbacfebb6b0e2f1215f0fc23a4cb60528a450"
dependencies = [
"clru",
"gix-chunk",
@@ -1322,9 +1436,21 @@ dependencies = [
[[package]]
name = "gix-packetline"
version = "0.18.4"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123844a70cf4d5352441dc06bab0da8aef61be94ec239cb631e0ba01dc6d3a04"
checksum = "8ddc034bc67c848e4ef7596ab5528cd8fd439d310858dbe1ce8b324f25deb91c"
dependencies = [
"bstr",
"faster-hex",
"gix-trace",
"thiserror 2.0.12",
]
[[package]]
name = "gix-packetline-blocking"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c44880f028ba46d6cf37a66d27a300310c6b51b8ed0e44918f93df061168e2f3"
dependencies = [
"bstr",
"faster-hex",
@@ -1334,22 +1460,38 @@ dependencies = [
[[package]]
name = "gix-path"
version = "0.10.15"
version = "0.10.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f910668e2f6b2a55ff35a1f04df88a1a049f7b868507f4cbeeaa220eaba7be87"
checksum = "c091d2e887e02c3462f52252c5ea61150270c0f2657b642e8d0d6df56c16e642"
dependencies = [
"bstr",
"gix-trace",
"gix-validate",
"home",
"once_cell",
"thiserror 2.0.12",
]
[[package]]
name = "gix-protocol"
version = "0.49.0"
name = "gix-pathspec"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5678ddae1d62880bc30e2200be1b9387af3372e0e88e21f81b4e7f8367355b5a"
checksum = "ce061c50e5f8f7c830cacb3da3e999ae935e283ce8522249f0ce2256d110979d"
dependencies = [
"bitflags 2.9.0",
"bstr",
"gix-attributes",
"gix-config-value",
"gix-glob",
"gix-path",
"thiserror 2.0.12",
]
[[package]]
name = "gix-protocol"
version = "0.50.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5c17d78bb0414f8d60b5f952196dc2e47ec320dca885de9128ecdb4a0e38401"
dependencies = [
"bstr",
"gix-date",
@@ -1366,9 +1508,9 @@ dependencies = [
[[package]]
name = "gix-quote"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b005c550bf84de3b24aa5e540a23e6146a1c01c7d30470e35d75a12f827f969"
checksum = "4a375a75b4d663e8bafe3bf4940a18a23755644c13582fa326e99f8f987d83fd"
dependencies = [
"bstr",
"gix-utils",
@@ -1377,9 +1519,9 @@ dependencies = [
[[package]]
name = "gix-ref"
version = "0.51.0"
version = "0.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2e1f7eb6b7ce82d2d19961f74bd637bab3ea79b1bc7bfb23dbefc67b0415d8b"
checksum = "d1b7985657029684d759f656b09abc3e2c73085596d5cdb494428823970a7762"
dependencies = [
"gix-actor",
"gix-features",
@@ -1398,9 +1540,9 @@ dependencies = [
[[package]]
name = "gix-refspec"
version = "0.29.0"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d8587b21e2264a6e8938d940c5c99662779c13a10741a5737b15fc85c252ffc"
checksum = "445ed14e3db78e8e79980085e3723df94e1c8163b3ae5bc8ed6a8fe6cf983b42"
dependencies = [
"bstr",
"gix-hash",
@@ -1412,9 +1554,9 @@ dependencies = [
[[package]]
name = "gix-revision"
version = "0.33.0"
version = "0.34.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "342caa4e158df3020cadf62f656307c3948fe4eacfdf67171d7212811860c3e9"
checksum = "78d0b8e5cbd1c329e25383e088cb8f17439414021a643b30afa5146b71e3c65d"
dependencies = [
"bitflags 2.9.0",
"bstr",
@@ -1430,9 +1572,9 @@ dependencies = [
[[package]]
name = "gix-revwalk"
version = "0.19.0"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dc7c3d7e5cdc1ab8d35130106e4af0a4f9f9eca0c81f4312b690780e92bde0d"
checksum = "1bc756b73225bf005ddeb871d1ca7b3c33e2417d0d53e56effa5a36765b52b28"
dependencies = [
"gix-commitgraph",
"gix-date",
@@ -1445,21 +1587,21 @@ dependencies = [
[[package]]
name = "gix-sec"
version = "0.10.12"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47aeb0f13de9ef2f3033f5ff218de30f44db827ac9f1286f9ef050aacddd5888"
checksum = "d0dabbc78c759ecc006b970339394951b2c8e1e38a37b072c105b80b84c308fd"
dependencies = [
"bitflags 2.9.0",
"gix-path",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "gix-shallow"
version = "0.3.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc0598aacfe1d52575a21c9492fee086edbb21e228ec36c819c42ab923f434c3"
checksum = "6b9a6f6e34d6ede08f522d89e5c7990b4f60524b8ae6ebf8e850963828119ad4"
dependencies = [
"bstr",
"gix-hash",
@@ -1468,11 +1610,50 @@ dependencies = [
]
[[package]]
name = "gix-tempfile"
version = "17.0.0"
name = "gix-status"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6de439bbb9a5d3550c9c7fab0e16d2d637d120fcbe0dfbc538772a187f099b"
checksum = "072099c2415cfa5397df7d47eacbcb6016d2cd17e0d674c74965e6ad1b17289f"
dependencies = [
"bstr",
"filetime",
"gix-diff",
"gix-dir",
"gix-features",
"gix-filter",
"gix-fs",
"gix-hash",
"gix-index",
"gix-object",
"gix-path",
"gix-pathspec",
"gix-worktree",
"portable-atomic",
"thiserror 2.0.12",
]
[[package]]
name = "gix-submodule"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f51472f05a450cc61bc91ed2f62fb06e31e2bbb31c420bc4be8793f26c8b0c1"
dependencies = [
"bstr",
"gix-config",
"gix-path",
"gix-pathspec",
"gix-refspec",
"gix-url",
"thiserror 2.0.12",
]
[[package]]
name = "gix-tempfile"
version = "17.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c750e8c008453a2dba67a2b0d928b7716e05da31173a3f5e351d5457ad4470aa"
dependencies = [
"dashmap",
"gix-fs",
"libc",
"once_cell",
@@ -1488,9 +1669,9 @@ checksum = "7c396a2036920c69695f760a65e7f2677267ccf483f25046977d87e4cb2665f7"
[[package]]
name = "gix-transport"
version = "0.46.0"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3f68c2870bfca8278389d2484a7f2215b67d0b0cc5277d3c72ad72acf41787e"
checksum = "edfe22ba26d4b65c17879f12b9882eafe65d3c8611c933b272fce2c10f546f59"
dependencies = [
"bstr",
"gix-command",
@@ -1504,9 +1685,9 @@ dependencies = [
[[package]]
name = "gix-traverse"
version = "0.45.0"
version = "0.46.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36c0b049f8bdb61b20016694102f7b507f2e1727e83e9c5e6dad4f7d84ff7384"
checksum = "39094185f6d9a4d81101130fbbf7f598a06441d774ae3b3ae7930a613bbe1157"
dependencies = [
"bitflags 2.9.0",
"gix-commitgraph",
@@ -1521,9 +1702,9 @@ dependencies = [
[[package]]
name = "gix-url"
version = "0.30.0"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48dfe23f93f1ddb84977d80bb0dd7aa09d1bf5d5afc0c9b6820cccacc25ae860"
checksum = "42a1ad0b04a5718b5cb233e6888e52a9b627846296161d81dcc5eb9203ec84b8"
dependencies = [
"bstr",
"gix-features",
@@ -1535,24 +1716,44 @@ dependencies = [
[[package]]
name = "gix-utils"
version = "0.2.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "189f8724cf903e7fd57cfe0b7bc209db255cacdcb22c781a022f52c3a774f8d0"
checksum = "5351af2b172caf41a3728eb4455326d84e0d70fe26fc4de74ab0bd37df4191c5"
dependencies = [
"bstr",
"fastrand",
"unicode-normalization",
]
[[package]]
name = "gix-validate"
version = "0.9.4"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34b5f1253109da6c79ed7cf6e1e38437080bb6d704c76af14c93e2f255234084"
checksum = "77b9e00cacde5b51388d28ed746c493b18a6add1f19b5e01d686b3b9ece66d4d"
dependencies = [
"bstr",
"thiserror 2.0.12",
]
[[package]]
name = "gix-worktree"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54f1916f8d928268300c977d773dd70a8746b646873b77add0a34876a8c847e9"
dependencies = [
"bstr",
"gix-attributes",
"gix-features",
"gix-fs",
"gix-glob",
"gix-hash",
"gix-ignore",
"gix-index",
"gix-object",
"gix-path",
"gix-validate",
]
[[package]]
name = "guess_host_triple"
version = "0.1.4"
@@ -1565,6 +1766,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -1599,6 +1809,16 @@ dependencies = [
"hashbrown 0.15.2",
]
[[package]]
name = "heapless"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"hash32",
"stable_deref_trait",
]
[[package]]
name = "heck"
version = "0.5.0"
@@ -1789,6 +2009,15 @@ dependencies = [
"icu_properties",
]
[[package]]
name = "imara-diff"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2"
dependencies = [
"hashbrown 0.15.2",
]
[[package]]
name = "indexmap"
version = "1.9.3"
@@ -1917,6 +2146,15 @@ dependencies = [
"serde_json",
]
[[package]]
name = "kstring"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1"
dependencies = [
"static_assertions",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -3087,6 +3325,7 @@ dependencies = [
"nu-ansi-term",
"open",
"os_info",
"parking_lot",
"path-slash",
"pest",
"pest_derive",
+8 -1
View File
@@ -42,7 +42,7 @@ clap_complete_nushell = "4.5.5"
dirs = "6.0.0"
dunce = "1.0.5"
# default feature restriction addresses https://github.com/starship/starship/issues/4251
gix = { version = "0.71.0", default-features = false, features = ["max-performance-safe", "revision", "zlib-rs"] }
gix = { version = "0.72.1", default-features = false, features = ["max-performance-safe", "revision", "zlib-rs", "status"] }
indexmap = { version = "2.9.0", features = ["serde"] }
jsonc-parser = { version = "0.26.2", features = ["serde"] }
log = { version = "0.4.27", features = ["std"] }
@@ -53,6 +53,8 @@ nu-ansi-term = "0.50.1"
open = "5.3.2"
# update os module config and tests when upgrading os_info
os_info = "3.10.0"
# for efficient shared state between `git_status` and `git_metrics`, allowing parallel printing. This is for poison-free locks.
parking_lot = "0.12.3"
path-slash = "0.2.1"
pest = "2.8.0"
pest_derive = "2.8.0"
@@ -124,6 +126,11 @@ codegen-units = 1
lto = true
strip = true
[profile.bench]
codegen-units = 16
lto = "thin"
strip = false
[[bin]]
name = "starship"
path = "src/main.rs"
+20 -19
View File
@@ -1960,25 +1960,26 @@ You can disable the module or use the `windows_starship` option to use a Windows
### Options
| Option | Default | Description |
| ------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `format` | `'([\[$all_status$ahead_behind\]]($style) )'` | The default format for `git_status` |
| `conflicted` | `'='` | This branch has merge conflicts. |
| `ahead` | `'⇡'` | The format of `ahead` |
| `behind` | `'⇣'` | The format of `behind` |
| `diverged` | `'⇕'` | The format of `diverged` |
| `up_to_date` | `''` | The format of `up_to_date` |
| `untracked` | `'?'` | The format of `untracked` |
| `stashed` | `'$'` | The format of `stashed` |
| `modified` | `'!'` | The format of `modified` |
| `staged` | `'+'` | The format of `staged` |
| `renamed` | `'»'` | The format of `renamed` |
| `deleted` | `'✘'` | The format of `deleted` |
| `typechanged` | `""` | The format of `typechanged` |
| `style` | `'bold red'` | The style for the module. |
| `ignore_submodules` | `false` | Ignore changes to submodules. |
| `disabled` | `false` | Disables the `git_status` module. |
| `windows_starship` | | Use this (Linux) path to a Windows Starship executable to render `git_status` when on Windows paths in WSL. |
| Option | Default | Description |
| -------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `format` | `'([\[$all_status$ahead_behind\]]($style) )'` | The default format for `git_status` |
| `conflicted` | `'='` | This branch has merge conflicts. |
| `ahead` | `'⇡'` | The format of `ahead` |
| `behind` | `'⇣'` | The format of `behind` |
| `diverged` | `'⇕'` | The format of `diverged` |
| `up_to_date` | `''` | The format of `up_to_date` |
| `untracked` | `'?'` | The format of `untracked` |
| `stashed` | `'$'` | The format of `stashed` |
| `modified` | `'!'` | The format of `modified` |
| `staged` | `'+'` | The format of `staged` |
| `renamed` | `'»'` | The format of `renamed` |
| `deleted` | `'✘'` | The format of `deleted` |
| `typechanged` | `""` | The format of `typechanged` |
| `style` | `'bold red'` | The style for the module. |
| `ignore_submodules` | `false` | Ignore changes to submodules. |
| `disabled` | `false` | Disables the `git_status` module. |
| `windows_starship` | | Use this (Linux) path to a Windows Starship executable to render `git_status` when on Windows paths in WSL. |
| `use_git_executable` | `false` | Do not use `gitoxide` for computing the status, but use the `git` executable instead. |
### Variables
+2
View File
@@ -24,6 +24,7 @@ pub struct GitStatusConfig<'a> {
pub typechanged: &'a str,
pub ignore_submodules: bool,
pub disabled: bool,
pub use_git_executable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub windows_starship: Option<&'a str>,
}
@@ -47,6 +48,7 @@ impl Default for GitStatusConfig<'_> {
typechanged: "",
ignore_submodules: false,
disabled: false,
use_git_executable: false,
windows_starship: None,
}
}
+8 -7
View File
@@ -294,12 +294,13 @@ impl<'a> Context<'a> {
let mut git_open_opts_map =
git_sec::trust::Mapping::<gix::open::Options>::default();
// don't use the global git configs
// Load all the configuration as it affects aspects of the
// `git_status` and `git_metrics` modules.
let config = gix::open::permissions::Config {
git_binary: false,
system: false,
git: false,
user: false,
git_binary: true,
system: true,
git: true,
user: true,
env: true,
includes: true,
};
@@ -652,7 +653,7 @@ pub struct Repo {
/// Contains `true` if the value of `core.fsmonitor` is set to `true`.
/// If not `true`, `fsmonitor` is explicitly disabled in git commands.
fs_monitor_value_is_true: bool,
pub(crate) fs_monitor_value_is_true: bool,
// Kind of repository, work tree or bare
pub kind: Kind,
@@ -671,7 +672,7 @@ impl Repo {
pub fn exec_git<T: AsRef<OsStr> + Debug>(
&self,
context: &Context,
git_args: &[T],
git_args: impl IntoIterator<Item = T>,
) -> Option<CommandOutput> {
let mut command = create_command("git").ok()?;
+17
View File
@@ -3,6 +3,8 @@
#[macro_use]
extern crate shadow_rs;
use std::thread::available_parallelism;
shadow!(shadow);
// Lib is present to allow for benchmarking
@@ -24,3 +26,18 @@ mod utils;
#[cfg(test)]
mod test;
/// Return the number of threads starship should use, if configured.
pub fn num_configured_starship_threads() -> Option<usize> {
std::env::var("STARSHIP_NUM_THREADS")
.ok()
.and_then(|s| s.parse().ok())
}
/// Return the maximum number of threads for the global thread-pool.
pub fn num_rayon_threads() -> usize {
num_configured_starship_threads()
// Default to the number of logical cores,
// but restrict the number of threads to 8
.unwrap_or_else(|| available_parallelism().map(usize::from).unwrap_or(1).min(8))
}
+1 -10
View File
@@ -3,7 +3,6 @@
use clap::crate_authors;
use std::io;
use std::path::PathBuf;
use std::thread::available_parallelism;
use std::time::SystemTime;
use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
@@ -281,16 +280,8 @@ fn main() {
/// Initialize global `rayon` thread pool
fn init_global_threadpool() {
// Allow overriding the number of threads
let num_threads = std::env::var("STARSHIP_NUM_THREADS")
.ok()
.and_then(|s| s.parse().ok())
// Default to the number of logical cores,
// but restrict the number of threads to 8
.unwrap_or_else(|| available_parallelism().map(usize::from).unwrap_or(1).min(8));
rayon::ThreadPoolBuilder::new()
.num_threads(num_threads)
.num_threads(num_rayon_threads())
.build_global()
.expect("Failed to initialize worker thread pool");
}
+456 -26
View File
@@ -1,12 +1,16 @@
use gix::bstr::{BStr, ByteSlice};
use gix::diff::blob::ResourceKind;
use gix::diff::blob::pipeline::WorktreeRoots;
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
use regex::Regex;
use super::Context;
use crate::configs::git_status::GitStatusConfig;
use crate::{
config::ModuleConfig, configs::git_metrics::GitMetricsConfig, formatter::StringFormatter,
formatter::string_formatter::StringFormatterError, module::Module,
};
use super::Context;
/// Creates a module with the current added/deleted lines in the git repository at the
/// current directory
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
@@ -20,14 +24,227 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
};
let repo = context.get_repo().ok()?;
let mut git_args = vec!["diff", "--shortstat"];
if config.ignore_submodules {
git_args.push("--ignore-submodules");
let gix_repo = repo.open();
if gix_repo.is_bare() {
return None;
}
// TODO: remove this special case once `gitoxide` can handle sparse indices for tree-index comparisons.
let stats = if gix_repo.index_or_empty().ok()?.is_sparse() || repo.fs_monitor_value_is_true {
let mut git_args = vec!["diff", "--shortstat"];
if config.ignore_submodules {
git_args.push("--ignore-submodules");
}
let diff = repo.exec_git(context, &git_args)?.stdout;
let diff = repo.exec_git(context, &git_args)?.stdout;
let stats = GitDiff::parse(&diff);
GitDiff::parse(&diff)
} else {
#[derive(Default)]
struct Diff {
added: usize,
deleted: usize,
}
impl Diff {
fn add(&mut self, c: Option<gix::diff::blob::sink::Counter<()>>) {
let Some(c) = c else { return };
self.added += c.insertions as usize;
self.deleted += c.removals as usize;
}
}
let status_module = context.new_module("git_status");
let status_config = GitStatusConfig::try_load(status_module.config);
let status = super::git_status::get_static_repo_status(context, repo, &status_config)?;
let gix_repo = gix_repo.with_object_memory();
gix_repo.write_blob([]).ok()?; /* create empty blob */
let tree_index_cache = prevent_external_diff(
gix_repo
.diff_resource_cache(gix::diff::blob::pipeline::Mode::ToGit, Default::default())
.ok()?,
);
let index_worktree_cache = prevent_external_diff(
gix_repo
.diff_resource_cache(
gix::diff::blob::pipeline::Mode::ToGit,
WorktreeRoots {
old_root: None,
new_root: gix_repo.workdir().map(ToOwned::to_owned),
},
)
.ok()?,
);
let diff = status
.changes
.par_iter()
.map_init(
{
let repo = gix_repo.into_sync();
move || {
let repo = repo.to_thread_local();
(repo, tree_index_cache.clone(), index_worktree_cache.clone())
}
},
|(repo, tree_index_cache, index_worktree_cache), change| {
use gix::status;
let mut diff = Diff::default();
match change {
status::Item::TreeIndex(change) => {
use gix::diff::index::Change;
match change {
Change::Addition {
entry_mode,
location,
id,
..
} => {
diff.added += count_lines(
location,
id.as_ref().into(),
*entry_mode,
tree_index_cache,
repo,
);
}
Change::Deletion {
entry_mode,
location,
id,
..
} => {
diff.deleted += count_lines(
location,
id.as_ref().into(),
*entry_mode,
tree_index_cache,
repo,
);
}
Change::Modification {
location,
previous_entry_mode,
previous_id,
entry_mode,
id,
..
} => {
let location = location.as_ref();
diff.add(diff_two_opt(
location,
previous_id.as_ref().to_owned(),
*previous_entry_mode,
location,
id.as_ref().to_owned(),
*entry_mode,
tree_index_cache,
repo,
));
}
Change::Rewrite {
source_location,
source_entry_mode,
source_id,
location,
entry_mode,
id,
copy,
..
} => {
if *copy {
diff.added += count_lines(
location,
id.as_ref().into(),
*entry_mode,
tree_index_cache,
repo,
);
} else {
diff.add(diff_two_opt(
source_location.as_ref(),
source_id.as_ref().to_owned(),
*source_entry_mode,
location,
id.as_ref().to_owned(),
*entry_mode,
tree_index_cache,
repo,
));
}
}
}
}
status::Item::IndexWorktree(change) => {
use gix::status::index_worktree::Item;
use gix::status::plumbing::index_as_worktree::{Change, EntryStatus};
match change {
Item::Modification {
rela_path,
entry,
status: EntryStatus::Change(Change::Removed),
..
} => {
diff.deleted += count_lines(
rela_path.as_bstr(),
entry.id,
entry.mode,
tree_index_cache,
repo,
);
}
Item::Modification {
rela_path,
entry,
status:
EntryStatus::Change(Change::Modification {
content_change: Some(_),
..
}),
..
} => {
let location = rela_path.as_bstr();
diff.add(diff_two_opt(
location,
entry.id,
entry.mode,
location,
repo.object_hash().null(),
entry.mode,
index_worktree_cache,
repo,
));
}
Item::Modification {
rela_path,
entry,
status: EntryStatus::IntentToAdd,
..
} => {
diff.added += count_lines(
rela_path.as_bstr(),
repo.object_hash().null(),
entry.mode,
index_worktree_cache,
repo,
);
}
Item::Rewrite { .. } => {
unreachable!("not activated")
}
_ => {}
}
}
};
diff
},
)
.reduce(Diff::default, |a, b| Diff {
added: a.added + b.added,
deleted: a.deleted + b.deleted,
});
GitDiff {
added: diff.added.to_string(),
deleted: diff.deleted.to_string(),
}
};
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
@@ -37,8 +254,8 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
_ => None,
})
.map(|variable| match variable {
"added" => GitDiff::get_variable(config.only_nonzero_diffs, stats.added),
"deleted" => GitDiff::get_variable(config.only_nonzero_diffs, stats.deleted),
"added" => GitDiff::get_variable(config.only_nonzero_diffs, &stats.added),
"deleted" => GitDiff::get_variable(config.only_nonzero_diffs, &stats.deleted),
_ => None,
})
.parse(None, Some(context))
@@ -55,16 +272,95 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
Some(module)
}
/// Represents the parsed output from a git diff.
struct GitDiff<'a> {
added: &'a str,
deleted: &'a str,
fn prevent_external_diff(mut cache: gix::diff::blob::Platform) -> gix::diff::blob::Platform {
cache.options.skip_internal_diff_if_external_is_configured = false;
cache
}
impl<'a> GitDiff<'a> {
#[allow(clippy::too_many_arguments)]
fn diff_two_opt(
lhs_location: &BStr,
lhs_id: gix::ObjectId,
lhs_kind: gix::index::entry::Mode,
rhs_location: &BStr,
rhs_id: gix::ObjectId,
rhs_kind: gix::index::entry::Mode,
cache: &mut gix::diff::blob::Platform,
find: &impl gix::objs::FindObjectOrHeader,
) -> Option<gix::diff::blob::sink::Counter<()>> {
cache
.set_resource(
lhs_id,
lhs_kind.to_tree_entry_mode()?.kind(),
lhs_location,
ResourceKind::OldOrSource,
find,
)
.ok()?;
cache
.set_resource(
rhs_id,
rhs_kind.to_tree_entry_mode()?.kind(),
rhs_location,
ResourceKind::NewOrDestination,
find,
)
.ok()?;
count_diff_lines(cache.prepare_diff().ok()?)
}
fn count_lines(
location: &BStr,
id: gix::ObjectId,
kind: gix::index::entry::Mode,
cache: &mut gix::diff::blob::Platform,
find: &impl gix::objs::FindObjectOrHeader,
) -> usize {
diff_two_opt(
location,
id.kind().null(),
kind,
location,
id,
kind,
cache,
find,
)
.map_or(0, |diff| diff.insertions as usize)
}
fn count_diff_lines(
prep: gix::diff::blob::platform::prepare_diff::Outcome<'_>,
) -> Option<gix::diff::blob::sink::Counter<()>> {
use gix::diff::blob::platform::prepare_diff::Operation;
match prep.operation {
Operation::InternalDiff { algorithm } => {
let tokens = prep.interned_input();
let counter = gix::diff::blob::diff(
algorithm,
&tokens,
gix::diff::blob::sink::Counter::default(),
);
Some(counter)
}
Operation::ExternalCommand { .. } => {
unreachable!("we disabled that")
}
Operation::SourceOrDestinationIsBinary => None,
}
}
/// Represents the parsed output from a git diff.
#[derive(Default)]
struct GitDiff {
added: String,
deleted: String,
}
impl GitDiff {
/// Returns the first capture group given a regular expression and a string.
/// If it fails to get the capture group it will return "0".
fn get_matched_str(diff: &'a str, re: &Regex) -> &'a str {
fn get_matched_str<'a>(diff: &'a str, re: &Regex) -> &'a str {
match re.captures(diff) {
Some(caps) => caps.get(1).unwrap().as_str(),
_ => "0",
@@ -72,13 +368,13 @@ impl<'a> GitDiff<'a> {
}
/// Parses the result of 'git diff --shortstat' as a `GitDiff` struct.
pub fn parse(diff: &'a str) -> Self {
pub fn parse(diff: &str) -> Self {
let added_re = Regex::new(r"(\d+) \w+\(\+\)").unwrap();
let deleted_re = Regex::new(r"(\d+) \w+\(\-\)").unwrap();
Self {
added: GitDiff::get_matched_str(diff, &added_re),
deleted: GitDiff::get_matched_str(diff, &deleted_re),
added: GitDiff::get_matched_str(diff, &added_re).to_owned(),
deleted: GitDiff::get_matched_str(diff, &deleted_re).to_owned(),
}
}
@@ -105,10 +401,10 @@ mod tests {
use std::path::Path;
use std::process::Stdio;
use crate::modules::git_status::tests::make_sparse;
use crate::test::{FixtureProvider, ModuleRenderer, fixture_repo};
use nu_ansi_term::Color;
use crate::test::ModuleRenderer;
#[test]
fn shows_nothing_on_empty_dir() -> io::Result<()> {
let repo_dir = tempfile::tempdir()?;
@@ -140,6 +436,78 @@ mod tests {
repo_dir.close()
}
#[test]
fn shows_staged_addition() -> io::Result<()> {
let repo_dir = create_repo_with_commit()?;
let path = repo_dir.path();
std::fs::write(path.join("new-file"), "new line")?;
run_git_cmd(["add", "new-file"], Some(path), true)?;
let actual = render_metrics(path);
let expected = Some(format!("{} ", Color::Green.bold().paint("+1"),));
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn shows_staged_rename_modification() -> io::Result<()> {
let repo_dir = create_repo_with_commit()?;
let path = repo_dir.path();
let the_file = path.join("the_file");
let mut the_file = OpenOptions::new().append(true).open(the_file)?;
writeln!(the_file, "Added line")?;
the_file.sync_all()?;
run_git_cmd(["add", "the_file"], Some(path), true)?;
run_git_cmd(["mv", "the_file", "that_file"], Some(path), true)?;
let actual = render_metrics(path);
let expected = Some(format!("{} ", Color::Green.bold().paint("+1"),));
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn shows_staged_addition_intended() -> io::Result<()> {
let repo_dir = create_repo_with_commit()?;
let path = repo_dir.path();
std::fs::write(path.join("new-file"), "new line")?;
run_git_cmd(["add", "-N", "new-file"], Some(path), true)?;
let actual = render_metrics(path);
let expected = Some(format!("{} ", Color::Green.bold().paint("+1"),));
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn shows_staged_modification() -> io::Result<()> {
let repo_dir = create_repo_with_commit()?;
let path = repo_dir.path();
std::fs::write(path.join("the_file"), "modify all")?;
run_git_cmd(["add", "the_file"], Some(path), true)?;
let actual = render_metrics(path);
let expected = Some(format!(
"{} {} ",
Color::Green.bold().paint("+1"),
Color::Red.bold().paint("-3")
));
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn shows_deleted_lines() -> io::Result<()> {
let repo_dir = create_repo_with_commit()?;
@@ -156,6 +524,36 @@ mod tests {
repo_dir.close()
}
#[test]
fn shows_deleted_lines_of_entire_file() -> io::Result<()> {
let repo_dir = create_repo_with_commit()?;
let path = repo_dir.path();
std::fs::remove_file(path.join("the_file"))?;
let actual = render_metrics(path);
let expected = Some(format!("{} ", Color::Red.bold().paint("-3")));
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn shows_staged_deletion() -> io::Result<()> {
let repo_dir = create_repo_with_commit()?;
let path = repo_dir.path();
run_git_cmd(["rm", "the_file"], Some(path), true)?;
let actual = render_metrics(path);
let expected = Some(format!("{} ", Color::Red.bold().paint("-3")));
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn shows_all_changes() -> io::Result<()> {
let repo_dir = create_repo_with_commit()?;
@@ -188,6 +586,32 @@ mod tests {
repo_dir.close()
}
#[test]
fn shows_nothing_on_untracked() -> io::Result<()> {
let repo_dir = create_repo_with_commit()?;
let path = repo_dir.path();
std::fs::write(path.join("untracked"), "a line")?;
let actual = render_metrics(path);
let expected = None;
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn shows_nothing_if_no_changes_sparse() -> io::Result<()> {
let repo_dir = create_repo_with_commit()?;
let path = repo_dir.path();
make_sparse(path)?;
let actual = render_metrics(path);
let expected = None;
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn shows_all_if_only_nonzero_diffs_is_false() -> io::Result<()> {
let repo_dir = create_repo_with_commit()?;
@@ -202,21 +626,27 @@ mod tests {
.config(toml::toml! {
[git_metrics]
disabled = false
only_nonzero_diffs = false
only_nonzero_diffs = true
})
.path(path)
.collect();
let expected = Some(format!(
"{} {} ",
Color::Green.bold().paint("+1"),
Color::Red.bold().paint("-0")
));
let expected = Some(format!("{} ", Color::Green.bold().paint("+1"),));
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn doesnt_generate_git_metrics_for_bare_repo() -> io::Result<()> {
let repo_dir = fixture_repo(FixtureProvider::GitBare)?;
let actual = render_metrics(repo_dir.path());
assert_eq!(None, actual);
repo_dir.close()
}
#[test]
fn shows_all_changes_with_ignored_submodules() -> io::Result<()> {
let repo_dir = create_repo_with_commit()?;
+382 -53
View File
@@ -1,13 +1,14 @@
use regex::Regex;
use std::sync::OnceLock;
use super::{Context, Module, ModuleConfig};
use crate::configs::git_status::GitStatusConfig;
use crate::context;
use crate::formatter::StringFormatter;
use crate::segment::Segment;
use std::sync::Arc;
use crate::{context, num_configured_starship_threads, num_rayon_threads};
use gix::bstr::ByteVec;
use gix::status::Submodule;
use regex::Regex;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, OnceLock};
const ALL_STATUS_FORMAT: &str =
"$conflicted$stashed$deleted$renamed$modified$typechanged$staged$untracked";
@@ -47,7 +48,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return Some(module);
}
let info = Arc::new(GitStatusInfo::load(context, repo, config.clone()));
let info = GitStatusInfo::load(context, repo, config.clone());
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
@@ -60,7 +61,6 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
_ => None,
})
.map_variables_to_segments(|variable: &str| {
let info = Arc::clone(&info);
let segments = match variable {
"stashed" => info.get_stashed().and_then(|count| {
format_count(config.stashed, "git_status.stashed", context, count)
@@ -135,7 +135,7 @@ struct GitStatusInfo<'a> {
context: &'a Context<'a>,
repo: &'a context::Repo,
config: GitStatusConfig<'a>,
repo_status: OnceLock<Option<RepoStatus>>,
repo_status: OnceLock<Option<Arc<RepoStatus>>>,
stashed_count: OnceLock<Option<usize>>,
}
@@ -158,16 +158,19 @@ impl<'a> GitStatusInfo<'a> {
self.get_repo_status().map(|data| (data.ahead, data.behind))
}
pub fn get_repo_status(&self) -> &Option<RepoStatus> {
self.repo_status.get_or_init(|| {
match get_repo_status(self.context, self.repo, &self.config) {
Some(repo_status) => Some(repo_status),
None => {
log::debug!("get_repo_status: git status execution failed");
None
}
}
})
pub fn get_repo_status(&self) -> Option<&RepoStatus> {
self.repo_status
.get_or_init(
|| match get_static_repo_status(self.context, self.repo, &self.config) {
Some(repo_status) => Some(repo_status),
None => {
log::debug!("get_repo_status: git status execution failed");
None
}
},
)
.as_ref()
.map(|repo_status| repo_status.as_ref())
}
pub fn get_stashed(&self) -> &Option<usize> {
@@ -210,6 +213,29 @@ impl<'a> GitStatusInfo<'a> {
}
}
/// Return a globally shared version the repository status so it can be reused.
/// It's shared so those who received a copy can keep it, even if the next call uses a different
/// path so the cache is trashed.
///
/// The trashing is only expected when tests run though, as otherwise one path is used with a variety of modules.
pub(crate) fn get_static_repo_status(
context: &Context,
repo: &context::Repo,
config: &GitStatusConfig,
) -> Option<Arc<RepoStatus>> {
static REPO_STATUS: parking_lot::Mutex<Option<(Arc<RepoStatus>, PathBuf)>> =
parking_lot::Mutex::new(None);
let mut status = REPO_STATUS.lock();
let needs_update = status
.as_ref()
.is_none_or(|(_status, status_path)| status_path != &context.current_dir);
if needs_update {
*status = get_repo_status(context, repo, config)
.map(|status| (Arc::new(status), context.current_dir.clone()));
}
status.as_ref().map(|(status, _)| Arc::clone(status))
}
/// Gets the number of files in various git states (staged, modified, deleted, etc...)
fn get_repo_status(
context: &Context,
@@ -219,40 +245,226 @@ fn get_repo_status(
log::debug!("New repo status created");
let mut repo_status = RepoStatus::default();
let mut args = vec!["status", "--porcelain=2"];
// for performance reasons, only pass flags if necessary...
let has_ahead_behind = !config.ahead.is_empty() || !config.behind.is_empty();
let has_up_to_date_diverged = !config.up_to_date.is_empty() || !config.diverged.is_empty();
if has_ahead_behind || has_up_to_date_diverged {
args.push("--branch");
}
// ... and add flags that omit information the user doesn't want
let gix_repo = repo.open();
// TODO: remove this special case once `gitoxide` can handle sparse indices for tree-index comparisons.
let has_untracked = !config.untracked.is_empty();
if !has_untracked {
args.push("--untracked-files=no");
}
if config.ignore_submodules {
args.push("--ignore-submodules=dirty");
} else if !has_untracked {
args.push("--ignore-submodules=untracked");
}
let git_config = gix_repo.config_snapshot();
if config.use_git_executable
|| gix_repo.index_or_empty().ok()?.is_sparse()
|| repo.fs_monitor_value_is_true
{
let mut args = vec!["status", "--porcelain=2"];
let status_output = repo.exec_git(context, &args)?;
let statuses = status_output.stdout.lines();
statuses.for_each(|status| {
if status.starts_with("# branch.ab ") {
repo_status.set_ahead_behind(status);
} else if !status.starts_with('#') {
repo_status.add(status);
// for performance reasons, only pass flags if necessary...
let has_ahead_behind = !config.ahead.is_empty() || !config.behind.is_empty();
let has_up_to_date_diverged = !config.up_to_date.is_empty() || !config.diverged.is_empty();
if has_ahead_behind || has_up_to_date_diverged {
args.push("--branch");
}
});
// ... and add flags that omit information the user doesn't want
if !has_untracked {
args.push("--untracked-files=no");
}
if config.ignore_submodules {
args.push("--ignore-submodules=dirty");
} else if !has_untracked {
args.push("--ignore-submodules=untracked");
}
let status_output = repo.exec_git(context, &args)?;
let statuses = status_output.stdout.lines();
statuses.for_each(|status| {
if status.starts_with("# branch.ab ") {
repo_status.set_ahead_behind(status);
} else if !status.starts_with('#') {
repo_status.add(status);
}
});
} else {
let is_interrupted = Arc::new(AtomicBool::new(false));
std::thread::Builder::new()
.name("starship timer".into())
.stack_size(256 * 1024)
.spawn({
let is_interrupted = is_interrupted.clone();
let abort_after =
std::time::Duration::from_millis(context.root_config.command_timeout);
move || {
std::thread::sleep(abort_after);
is_interrupted.store(true, std::sync::atomic::Ordering::SeqCst);
}
})
.expect("should be able to spawn timer thread");
// We don't show details in submodules.
let check_dirty = true;
let status = gix_repo
.status(gix::features::progress::Discard)
.ok()?
.index_worktree_submodules(if config.ignore_submodules {
Submodule::Given {
ignore: gix::submodule::config::Ignore::Dirty,
check_dirty,
}
} else if !has_untracked {
Submodule::Given {
ignore: gix::submodule::config::Ignore::Untracked,
check_dirty,
}
} else {
Submodule::AsConfigured { check_dirty }
})
.index_worktree_options_mut(|opts| {
opts.thread_limit = if cfg!(target_os = "macos") {
Some(num_configured_starship_threads().unwrap_or(
// TODO: figure out good defaults for other platforms, maybe make it configurable.
// Git uses everything (if repo-size permits), but that's not the best choice for MacOS.
3,
))
} else {
Some(num_rayon_threads())
};
if config.untracked.is_empty() {
opts.dirwalk_options.take();
} else if let Some(opts) = opts.dirwalk_options.as_mut() {
opts.set_emit_untracked(gix::dir::walk::EmissionMode::Matching)
.set_emit_ignored(None)
.set_emit_pruned(false)
.set_emit_empty_directories(false);
}
})
.tree_index_track_renames(if config.renamed.is_empty() {
gix::status::tree_index::TrackRenames::Disabled
} else {
gix::status::tree_index::TrackRenames::Given(sanitize_rename_tracking(
// Get configured diff-rename configuration, or use default settings.
gix::diff::new_rewrites(&git_config, true)
.unwrap_or_default()
.0
.unwrap_or_default(),
))
})
.should_interrupt_owned(is_interrupted.clone());
// This will start the status machinery, collecting status items in the background.
// Thus, we can do some work in this thread without blocking, before starting to count status items.
let status = status.into_iter(None).ok()?;
// for performance reasons, only pass flags if necessary...
let has_ahead_behind = !config.ahead.is_empty() || !config.behind.is_empty();
let has_up_to_date_or_diverged =
!config.up_to_date.is_empty() || !config.diverged.is_empty();
if has_ahead_behind || has_up_to_date_or_diverged {
if let Some(branch_name) = gix_repo.head_name().ok().flatten().and_then(|ref_name| {
Vec::from(gix::bstr::BString::from(ref_name))
.into_string()
.ok()
}) {
let output = repo.exec_git(
context,
["for-each-ref", "--format", "%(upstream:track)"]
.into_iter()
.map(ToOwned::to_owned)
.chain(Some(branch_name)),
)?;
if let Some(line) = output.stdout.lines().next() {
repo_status.set_ahead_behind_for_each_ref(line);
}
}
}
for change in status.filter_map(Result::ok) {
use gix::status;
match &change {
status::Item::TreeIndex(change) => {
use gix::diff::index::Change;
match change {
Change::Addition { .. } => {
repo_status.staged += 1;
}
Change::Deletion { .. } => {
repo_status.deleted += 1;
}
Change::Modification { .. } => {
repo_status.staged += 1;
}
Change::Rewrite { .. } => {
repo_status.renamed += 1;
}
}
}
status::Item::IndexWorktree(change) => {
use gix::status::index_worktree::Item;
use gix::status::plumbing::index_as_worktree::{Change, EntryStatus};
match change {
Item::Modification {
status: EntryStatus::Conflict(_),
..
} => {
repo_status.conflicted += 1;
}
Item::Modification {
status: EntryStatus::Change(Change::Removed),
..
} => {
repo_status.deleted += 1;
}
Item::Modification {
status:
EntryStatus::IntentToAdd
| EntryStatus::Change(
Change::Modification { .. } | Change::SubmoduleModification(_),
),
..
} => {
repo_status.modified += 1;
}
Item::Modification {
status: EntryStatus::Change(Change::Type { .. }),
..
} => {
repo_status.typechanged += 1;
}
Item::DirectoryContents {
entry:
gix::dir::Entry {
status: gix::dir::entry::Status::Untracked,
..
},
..
} => {
repo_status.untracked += 1;
}
Item::Rewrite { .. } => {
unreachable!(
"this kind of rename tracking isn't enabled by default and specific to gitoxide"
)
}
_ => {}
}
}
}
// Keep it for potential reuse by `git_metrics`
repo_status.changes.push(change);
}
if is_interrupted.load(std::sync::atomic::Ordering::Relaxed) {
repo_status = RepoStatus {
ahead: repo_status.ahead,
behind: repo_status.behind,
..Default::default()
};
}
}
Some(repo_status)
}
fn sanitize_rename_tracking(mut config: gix::diff::Rewrites) -> gix::diff::Rewrites {
config.limit = 100;
config
}
fn get_stashed_count(repo: &context::Repo) -> Option<usize> {
let repo = repo.open();
let reference = match repo.try_find_reference("refs/stash") {
@@ -279,10 +491,11 @@ fn get_stashed_count(repo: &context::Repo) -> Option<usize> {
}
}
#[derive(Default, Debug, Copy, Clone)]
struct RepoStatus {
#[derive(Default, Debug, Clone)]
pub(crate) struct RepoStatus {
ahead: Option<usize>,
behind: Option<usize>,
pub(crate) changes: Vec<gix::status::Item>,
conflicted: usize,
deleted: usize,
renamed: usize,
@@ -355,6 +568,27 @@ impl RepoStatus {
self.behind = caps.get(2).unwrap().as_str().parse::<usize>().ok();
}
}
fn set_ahead_behind_for_each_ref(&mut self, mut s: &str) {
s = s.trim_matches(|c| c == '[' || c == ']');
for pair in s.split(',') {
let mut tokens = pair.trim().splitn(2, ' ');
if let (Some(name), Some(number)) = (tokens.next(), tokens.next()) {
let storage = match name {
"ahead" => &mut self.ahead,
"behind" => &mut self.behind,
_ => return,
};
*storage = number.parse().ok();
}
}
for field in [&mut self.ahead, &mut self.behind] {
if field.is_none() {
*field = Some(0);
}
}
}
}
fn format_text<F>(
@@ -514,16 +748,15 @@ fn git_status_wsl(_context: &Context, _conf: &GitStatusConfig) -> Option<String>
}
#[cfg(test)]
mod tests {
pub(crate) mod tests {
use crate::test::{FixtureProvider, ModuleRenderer, fixture_repo};
use crate::utils::create_command;
use nu_ansi_term::{AnsiStrings, Color};
use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::{self, prelude::*};
use std::path::Path;
use crate::test::{FixtureProvider, ModuleRenderer, fixture_repo};
use crate::utils::create_command;
#[allow(clippy::unnecessary_wraps)]
fn format_output(symbols: &str) -> Option<String> {
Some(format!(
@@ -880,6 +1113,21 @@ mod tests {
repo_dir.close()
}
#[test]
fn shows_typechanged_in_index() -> io::Result<()> {
let repo_dir = fixture_repo(FixtureProvider::Git)?;
create_typechanged_in_index(repo_dir.path())?;
let actual = ModuleRenderer::new("git_status")
.path(repo_dir.path())
.collect();
let expected = format_output("+");
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn shows_modified() -> io::Result<()> {
let repo_dir = fixture_repo(FixtureProvider::Git)?;
@@ -914,6 +1162,27 @@ mod tests {
repo_dir.close()
}
#[test]
fn shows_modified_with_count_sparse() -> io::Result<()> {
let repo_dir = fixture_repo(FixtureProvider::Git)?;
make_sparse(repo_dir.path())?;
create_modified(repo_dir.path())?;
let actual = ModuleRenderer::new("git_status")
.config(toml::toml! {
[git_status]
modified = "!$count"
ahead = ""
})
.path(repo_dir.path())
.collect();
let expected = format_output("!1");
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn shows_added() -> io::Result<()> {
let repo_dir = fixture_repo(FixtureProvider::Git)?;
@@ -1075,6 +1344,21 @@ mod tests {
repo_dir.close()
}
#[test]
fn shows_deleted_file_in_index() -> io::Result<()> {
let repo_dir = fixture_repo(FixtureProvider::Git)?;
create_deleted_in_index(repo_dir.path())?;
let actual = ModuleRenderer::new("git_status")
.path(repo_dir.path())
.collect();
let expected = format_output("");
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn shows_deleted_file_with_count() -> io::Result<()> {
let repo_dir = fixture_repo(FixtureProvider::Git)?;
@@ -1139,7 +1423,6 @@ mod tests {
// but as untracked instead. The following test checks if manually deleted and manually renamed
// files are tracked by git_status module in the same way 'git status' does.
#[test]
#[ignore]
fn ignore_manually_renamed() -> io::Result<()> {
let repo_dir = fixture_repo(FixtureProvider::Git)?;
File::create(repo_dir.path().join("a"))?.sync_all()?;
@@ -1239,6 +1522,16 @@ mod tests {
Ok(())
}
fn create_typechanged_in_index(repo_dir: &Path) -> io::Result<()> {
create_typechanged(repo_dir)?;
create_command("git")?
.args(["add", "readme.md"])
.current_dir(repo_dir)
.output()?;
Ok(())
}
fn create_staged_typechange(repo_dir: &Path) -> io::Result<()> {
create_typechanged(repo_dir)?;
@@ -1320,6 +1613,33 @@ mod tests {
Ok(())
}
pub(crate) fn make_sparse(repo_dir: &Path) -> io::Result<()> {
let sparse_dirname = "sparse-dir";
let dir = repo_dir.join(sparse_dirname);
std::fs::create_dir(&dir)?;
File::create(dir.join("still-visible"))?.sync_all()?;
let subdir = dir.join("not-checked-out");
std::fs::create_dir(&subdir)?;
File::create(subdir.join("invisible"))?.sync_all()?;
create_command("git")?
.args(["add", "sparse-dir"])
.current_dir(repo_dir)
.output()?;
create_command("git")?
.args(["commit", "-m", "add new directory", "--no-gpg-sign"])
.current_dir(repo_dir)
.output()?;
create_command("git")?
.args(["sparse-checkout", "set", sparse_dirname, "--sparse-index"])
.current_dir(repo_dir)
.output()?;
Ok(())
}
fn create_staged(repo_dir: &Path) -> io::Result<()> {
File::create(repo_dir.join("license"))?.sync_all()?;
@@ -1384,6 +1704,15 @@ mod tests {
Ok(())
}
fn create_deleted_in_index(repo_dir: &Path) -> io::Result<()> {
create_command("git")?
.args(["rm", "readme.md"])
.current_dir(repo_dir)
.output()?;
Ok(())
}
fn create_staged_and_ignored(repo_dir: &Path) -> io::Result<()> {
let mut file = File::create(repo_dir.join(".gitignore"))?;
writeln!(&mut file, "ignored.txt")?;
+1 -1
View File
@@ -32,7 +32,7 @@ mod git_branch;
mod git_commit;
mod git_metrics;
mod git_state;
mod git_status;
pub(crate) mod git_status;
mod gleam;
mod golang;
mod gradle;
+7 -6
View File
@@ -246,12 +246,13 @@ pub fn fixture_repo(provider: FixtureProvider) -> io::Result<TempDir> {
}
FixtureProvider::GitBare => {
let path = tempfile::tempdir()?;
gix::ThreadSafeRepository::init(
&path,
gix::create::Kind::Bare,
gix::create::Options::default(),
)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
create_command("git")?
.current_dir(path.path())
.args(["clone", "-b", "master", "--bare"])
.arg(GIT_FIXTURE.as_os_str())
.arg(path.path())
.output()?;
Ok(path)
}
FixtureProvider::Hg => {
+1
View File
@@ -9,5 +9,6 @@ afe = "afe"
typ = "typ"
extentions = "extentions" # TODO: should be extensions
worl = "worl" # typo on purpose
rela = "rela"
[files]
extend-exclude = ["CHANGELOG.md", "docs/*"]