diff --git a/Cargo.lock b/Cargo.lock index a3169e4..3fcdbcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,6 +117,7 @@ dependencies = [ "env_logger", "gix", "log", + "num_enum", "rustix", ] @@ -213,6 +214,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 = "digest" version = "0.10.7" @@ -240,6 +255,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -263,6 +287,12 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.11" @@ -318,6 +348,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -356,22 +392,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" 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", @@ -379,12 +421,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", @@ -404,6 +449,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", + "unicode-bom", +] + [[package]] name = "gix-bitmap" version = "0.2.14" @@ -502,8 +564,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" 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", +] + +[[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", ] @@ -543,6 +637,27 @@ dependencies = [ "walkdir", ] +[[package]] +name = "gix-filter" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +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", +] + [[package]] name = "gix-fs" version = "0.15.0" @@ -588,10 +703,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b5cb3c308b4144f2612ff64e32130e641279fcf1a84d8d40dad843b4f64904" dependencies = [ "gix-hash", - "hashbrown", + "hashbrown 0.14.5", "parking_lot", ] +[[package]] +name = "gix-ignore" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae358c3c96660b10abc7da63c06788dfded603e717edbd19e38c6477911b71c8" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-trace", + "unicode-bom", +] + [[package]] name = "gix-index" version = "0.40.0" @@ -611,7 +739,7 @@ dependencies = [ "gix-traverse", "gix-utils", "gix-validate", - "hashbrown", + "hashbrown 0.14.5", "itoa", "libc", "memmap2", @@ -704,6 +832,18 @@ dependencies = [ "thiserror", ] +[[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", + "gix-trace", + "thiserror", +] + [[package]] name = "gix-path" version = "0.10.17" @@ -718,6 +858,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "gix-pathspec" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce061c50e5f8f7c830cacb3da3e999ae935e283ce8522249f0ce2256d110979d" +dependencies = [ + "bitflags", + "bstr", + "gix-attributes", + "gix-config-value", + "gix-glob", + "gix-path", + "thiserror", +] + [[package]] name = "gix-protocol" version = "0.50.1" @@ -840,12 +995,51 @@ dependencies = [ "thiserror", ] +[[package]] +name = "gix-status" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +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", +] + +[[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", +] + [[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", @@ -912,6 +1106,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5351af2b172caf41a3728eb4455326d84e0d70fe26fc4de74ab0bd37df4191c5" dependencies = [ + "bstr", "fastrand", "unicode-normalization", ] @@ -926,6 +1121,25 @@ dependencies = [ "thiserror", ] +[[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 = "hash32" version = "0.3.1" @@ -945,6 +1159,15 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +dependencies = [ + "foldhash", +] + [[package]] name = "heapless" version = "0.8.0" @@ -1103,6 +1326,25 @@ 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.3", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.3", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1156,6 +1398,15 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "kstring" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" +dependencies = [ + "static_assertions", +] + [[package]] name = "libc" version = "0.2.172" @@ -1245,6 +1496,27 @@ dependencies = [ "adler2", ] +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1295,6 +1567,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -1454,6 +1735,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "2.0.101" @@ -1534,6 +1821,23 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml_datetime" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "typenum" version = "1.18.0" diff --git a/Cargo.toml b/Cargo.toml index bfc5910..96ad0f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,14 @@ edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -gix = {version = "0.72", default-features = false, features = ["max-performance-safe", "revision"] } +gix = {version = "0.72", default-features = false, features = ["max-performance-safe", "revision", "status"] } anyhow = "1.0" env_logger = "0.11" log = "0.4" rustix = { version = "1", features = ["process"] } +num_enum = "0.7.3" + +[profile.release] +lto = "thin" +opt-level = 3 +codegen-units = 1 diff --git a/src/main.rs b/src/main.rs index 716e264..c3f914a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,15 @@ use anyhow::{Context, Result, anyhow}; use gix::commit::describe::SelectRef::{self}; +use gix::progress; use gix::state::InProgress; +use gix::status::Submodule; use gix::{ Repository, ThreadSafeRepository, sec::{self, trust::DefaultForLevel}, }; use log::debug; +use num_enum::IntoPrimitive; +use std::borrow::Cow; use std::env; use std::path::Path; use std::process::Command; @@ -38,7 +42,11 @@ fn main() { let path = PathBuf::from("."); - let progress_status = match repo_progress(path) { + let Ok(repo) = get_repo(&path) else { + exit(1); + }; + + let progress_status = match repo_progress(&repo) { Ok(output) => output, Err(e) => { debug!("{e}"); @@ -46,24 +54,145 @@ fn main() { } }; - let status = print_and_get_status(&progress_status); + println!("{progress_status}"); + + let status = get_status(&repo).into(); exit(status) } -fn print_and_get_status(progress_status: &str) -> i32 { - println!("{progress_status}"); +#[derive(Debug, IntoPrimitive)] +#[repr(i32)] +enum Status { + Unchange = 5, + Change = 6, + Untracked = 7, + HasError = 8, + Disable = 9, +} +fn get_status(repo: &Repo) -> Status { if env::var("BASH_DISABLE_GIT_FILE_TRACKING").is_ok() { - return 9; + return Status::Disable; + } + + let repo = repo.repo.to_thread_local(); + + if repo.index_or_empty().is_ok_and(|repo| repo.is_sparse()) { + return get_status_sparse(); + } + + let Ok(status) = repo + .status(progress::Discard) + .inspect_err(|e| debug!("{e}")) + else { + return Status::HasError; + }; + + let status = status.index_worktree_submodules(Submodule::AsConfigured { check_dirty: true }); + let status = status.index_worktree_options_mut(|opts| { + // TODO: figure out good defaults for other platforms, maybe make it configurable. + opts.thread_limit = None; + + 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); + } + }); + + let status = status.tree_index_track_renames(gix::status::tree_index::TrackRenames::Given({ + let mut config = gix::diff::new_rewrites(&repo.config_snapshot(), true) + .unwrap_or_default() + .0 + .unwrap_or_default(); + + config.limit = 100; + config + })); + + // 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 Ok(status) = status.into_iter(None).inspect_err(|e| debug!("{e}")) else { + return Status::HasError; + }; + + let mut is_untracked = false; + + for change in status.filter_map(Result::ok) { + use gix::status; + match &change { + status::Item::TreeIndex(_) => { + return Status::Change; + } + 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(_), + .. + } => { + return Status::Change; + } + Item::Modification { + status: EntryStatus::Change(Change::Removed), + .. + } => { + return Status::Change; + } + Item::Modification { + status: + EntryStatus::IntentToAdd + | EntryStatus::Change( + Change::Modification { .. } | Change::SubmoduleModification(_), + ), + .. + } => { + return Status::Change; + } + Item::Modification { + status: EntryStatus::Change(Change::Type { .. }), + .. + } => { + return Status::Change; + } + Item::DirectoryContents { + entry: + gix::dir::Entry { + status: gix::dir::entry::Status::Untracked, + .. + }, + .. + } => { + is_untracked = true; + } + Item::Rewrite { .. } => { + unreachable!( + "this kind of rename tracking isn't enabled by default and specific to gitoxide" + ) + } + _ => {} + } + } + } } + if is_untracked { + return Status::Untracked; + } + + Status::Unchange +} + +fn get_status_sparse() -> Status { let cmd = Command::new("git") .arg("status") .arg("--porcelain") .output(); - let mut status = 0; + let mut status = Status::Unchange; if let Ok(cmd) = cmd { if cmd.status.success() { @@ -75,47 +204,47 @@ fn print_and_get_status(progress_status: &str) -> i32 { .map(|x| x.0); match out_iter.next() { - None => { - status = 5; - } + None => {} Some(x) if MODIFY_STATUS.contains(x) || MODIFY_STATUS.contains(&x[..1]) || MODIFY_STATUS.contains(&x[1..2]) => { - status = 6; + status = Status::Change; } Some("??") => { - status = 7; + status = Status::Untracked; } _ => {} } debug!("git status --porcelain output: {out}"); } else { - status = 8; + status = Status::HasError; } } status } -fn repo_progress(path: PathBuf) -> Result { - // custom open options - let repo = get_repo(&path)?; - +fn repo_progress(repo: &Repo) -> Result { let git_repo = repo.repo.to_thread_local(); - let display_name = repo.branch.or_else(|| get_tag(&git_repo)).or_else(|| { - Some(format!( - "(detached {})", - git_repo.head_id().ok()?.shorten_or_id() - )) - }); + let display_name = repo + .branch + .as_ref() + .map(Cow::Borrowed) + .or_else(|| get_tag(&git_repo).map(Cow::Owned)) + .or_else(|| { + Some(Cow::Owned(format!( + "(detached {})", + git_repo.head_id().ok()?.shorten_or_id() + ))) + }); let display_name = display_name.ok_or_else(|| anyhow!("Failed to get branch/hash"))?; - let s = if let Some(state) = repo.state { + let s = if let Some(state) = &repo.state { match state { InProgress::ApplyMailbox => format!("Mailbox progress {display_name}"), InProgress::ApplyMailboxRebase => { @@ -135,7 +264,7 @@ fn repo_progress(path: PathBuf) -> Result { InProgress::RevertSequence => format!("Revert Sequence progress {display_name}"), } } else { - display_name + display_name.to_string() }; Ok(s) @@ -145,10 +274,10 @@ fn get_repo(path: &Path) -> Result { let mut git_open_opts_map = sec::trust::Mapping::::default(); 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, };