use std::{ io::{BufReader, stdin}, path::PathBuf, sync::{ Arc, atomic::{AtomicBool, Ordering}, },}; use anyhow::{Context, Result, anyhow};use clap::{CommandFactory, Parser};use gitoxide_core as core;use gitoxide_core::{pack::verify, repository::PathsOrPatterns};use gix::bstr::{BString, io::BufReadExt}; use crate::{ plumbing::{ options::{ Args, Subcommands, attributes, branch, commit, commitgraph, config, credential, exclude, free, fsck, index, mailmap, merge, odb, revision, tag, tree, }, show_progress, }, shared::pretty::prepare_and_run,}; #[cfg(feature = "gitoxide-core-async-client")]pub mod async_util { use crate::shared::ProgressRange; #[cfg(not(feature = "prodash-render-line"))] compile_error!("BUG: Need at least a line renderer in async mode"); pub fn prepare( verbose: bool, trace: bool, name: &str, range: impl Into<Option<ProgressRange>>, ) -> ( Option<prodash::render::line::JoinHandle>, gix_features::progress::DoOrDiscard<prodash::tree::Item>, ) { use crate::shared::{self, STANDARD_RANGE}; shared::init_env_logger(); if verbose { let progress = shared::progress_tree(trace); let sub_progress = progress.add_child(name); let ui_handle = shared::setup_line_renderer_range(&progress, range.into().unwrap_or(STANDARD_RANGE)); (Some(ui_handle), Some(sub_progress).into()) } else { (None, None.into()) } }} pub fn main() -> Result<()> { let args: Args = Args::parse_from(gix::env::args_os()); let thread_limit = args.threads; let verbose = args.verbose; let format = args.format; let cmd = args.cmd; #[cfg_attr(not(feature = "tracing"), allow(unused_mut))] #[cfg_attr(feature = "tracing", allow(unused_assignments))] let mut trace = false; #[cfg(feature = "tracing")] { trace = args.trace; } let object_hash = args.object_hash; let config = args.config; let repository = args.repository; let repository_path = repository.clone(); enum Mode { Strict, StrictWithGitInstallConfig, Lenient, LenientWithGitInstallConfig, } let repository = { let config = config.clone(); move |mut mode: Mode| -> Result<gix::Repository> { let mut mapping: gix::sec::trust::Mapping<gix::open::Options> = Default::default(); if !config.is_empty() { mode = match mode { Mode::Lenient => Mode::Strict, Mode::LenientWithGitInstallConfig => Mode::StrictWithGitInstallConfig, _ => mode, }; } let strict_toggle = matches!(mode, Mode::Strict | Mode::StrictWithGitInstallConfig) || args.strict; mapping.full = mapping.full.strict_config(strict_toggle); mapping.reduced = mapping.reduced.strict_config(strict_toggle); let git_installation = matches!( mode, Mode::StrictWithGitInstallConfig | Mode::LenientWithGitInstallConfig ); let to_match_settings = |mut opts: gix::open::Options| { opts.permissions.config.git_binary = git_installation; opts.permissions.attributes.git_binary = git_installation; if config.is_empty() { opts } else { opts.cli_overrides(config.clone()) } }; mapping.full.modify(to_match_settings); mapping.reduced.modify(to_match_settings); let mut repo = gix::ThreadSafeRepository::discover_with_environment_overrides_opts( repository, Default::default(), mapping, ) .map(gix::Repository::from)?; if !config.is_empty() { repo.config_snapshot_mut() .append_config(config.iter(), gix::config::Source::Cli) .context("Unable to parse command-line configuration")?; } { let mut config_mut = repo.config_snapshot_mut(); // Enable precious file parsing unless the user made a choice. if config_mut .boolean(gix::config::tree::Gitoxide::PARSE_PRECIOUS)? .is_none() { config_mut.set_raw_value(gix::config::tree::Gitoxide::PARSE_PRECIOUS, "true")?; } } Ok(repo) } }; let progress; let progress_keep_open; #[cfg(feature = "prodash-render-tui")] { progress = args.progress; progress_keep_open = args.progress_keep_open; } #[cfg(not(feature = "prodash-render-tui"))] { progress = false; progress_keep_open = false; } let auto_verbose = !progress && !args.no_verbose; let should_interrupt = Arc::new(AtomicBool::new(false)); #[expect(unsafe_code)] unsafe { // SAFETY: The closure doesn't use mutexes or memory allocation, so it should be safe to call from a signal handler. gix::interrupt::init_handler(1, { let should_interrupt = Arc::clone(&should_interrupt); move || should_interrupt.store(true, Ordering::SeqCst) })?; } match cmd { Subcommands::Env => prepare_and_run( "env", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| core::env(out, format), ), Subcommands::Merge(merge::Platform { cmd }) => match cmd { merge::SubCommands::File { resolve_with, ours, base, theirs, } => prepare_and_run( "merge-file", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| { core::repository::merge::file( repository(Mode::Lenient)?, out, format, resolve_with.map(Into::into), base, ours, theirs, ) }, ), merge::SubCommands::Tree { opts: merge::SharedOptions { in_memory, file_favor, tree_favor, debug, }, message, update_head, ours, base, theirs, } => prepare_and_run( "merge-tree", trace, verbose, progress, progress_keep_open, None, move |_progress, out, err| { core::repository::merge::tree( repository(Mode::Lenient)?, out, err, base, ours, theirs, core::repository::merge::tree::Options { format, file_favor: file_favor.map(Into::into), in_memory, tree_favor: tree_favor.map(Into::into), debug, message, update_head, }, ) }, ), merge::SubCommands::Commit { opts: merge::SharedOptions { in_memory, file_favor, tree_favor, debug, }, ours, theirs, } => prepare_and_run( "merge-commit", trace, verbose, progress, progress_keep_open, None, move |_progress, out, err| { core::repository::merge::commit( repository(Mode::Lenient)?, out, err, ours, theirs, core::repository::merge::tree::Options { format, file_favor: file_favor.map(Into::into), tree_favor: tree_favor.map(Into::into), in_memory, debug, message: None, update_head: false, }, ) }, ), }, Subcommands::MergeBase(crate::plumbing::options::merge_base::Command { first, others }) => prepare_and_run( "merge-base", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| { core::repository::merge_base(repository(Mode::Lenient)?, first, others, out, format) }, ), Subcommands::Diff(crate::plumbing::options::diff::Platform { cmd }) => match cmd { crate::plumbing::options::diff::SubCommands::Tree { old_treeish, new_treeish, } => prepare_and_run( "diff-tree", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| { core::repository::diff::tree(repository(Mode::Lenient)?, out, old_treeish, new_treeish) }, ), crate::plumbing::options::diff::SubCommands::File { old_revspec, new_revspec, } => prepare_and_run( "diff-file", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| { core::repository::diff::file(repository(Mode::Lenient)?, out, old_revspec, new_revspec) }, ), }, Subcommands::Log(crate::plumbing::options::log::Platform { pathspec }) => prepare_and_run( "log", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| core::repository::log::log(repository(Mode::Lenient)?, out, pathspec), ), Subcommands::Worktree(crate::plumbing::options::worktree::Platform { cmd }) => match cmd { crate::plumbing::options::worktree::SubCommands::List => prepare_and_run( "worktree-list", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| core::repository::worktree::list(repository(Mode::Lenient)?, out, format), ), }, Subcommands::IsClean | Subcommands::IsChanged => { let mode = if matches!(cmd, Subcommands::IsClean) { core::repository::dirty::Mode::IsClean } else { core::repository::dirty::Mode::IsDirty }; prepare_and_run( "clean", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| { core::repository::dirty::check(repository(Mode::Lenient)?, mode, out, format) }, ) } #[cfg(feature = "gitoxide-core-tools-clean")] Subcommands::Clean(crate::plumbing::options::clean::Command { debug, dry_run: _, execute, ignored, precious, directories, pathspec, repositories, pathspec_matches_result, skip_hidden_repositories, find_untracked_repositories, }) => prepare_and_run( "clean", trace, verbose, progress, progress_keep_open, None, move |_progress, out, err| { core::repository::clean( repository(Mode::Lenient)?, out, err, pathspec, core::repository::clean::Options { debug, format, execute, ignored, precious, directories, repositories, pathspec_matches_result, skip_hidden_repositories: skip_hidden_repositories.map(Into::into), find_untracked_repositories: find_untracked_repositories.into(), }, ) }, ), Subcommands::Status(crate::plumbing::options::status::Platform { ignored, untracked, format: status_format, statistics, submodules, no_write, pathspec, index_worktree_renames, }) => prepare_and_run( "status", trace, auto_verbose, progress, progress_keep_open, None, move |progress, out, err| { use crate::plumbing::options::status::Submodules; core::repository::status::show( repository(Mode::Lenient)?, pathspec, out, err, progress, core::repository::status::Options { format: match status_format.unwrap_or_default() { crate::plumbing::options::status::Format::Simplified => { core::repository::status::Format::Simplified } crate::plumbing::options::status::Format::PorcelainV2 => { core::repository::status::Format::PorcelainV2 } }, ignored: ignored.map(|ignored| match ignored.unwrap_or_default() { crate::plumbing::options::status::Ignored::Matching => { core::repository::status::Ignored::Matching } crate::plumbing::options::status::Ignored::Collapsed => { core::repository::status::Ignored::Collapsed } }), untracked: untracked.map(|mode| match mode.unwrap_or_default() { crate::plumbing::options::status::Untracked::No => gix::status::UntrackedFiles::None, crate::plumbing::options::status::Untracked::Normal => { gix::status::UntrackedFiles::Collapsed } crate::plumbing::options::status::Untracked::All => gix::status::UntrackedFiles::Files, }), output_format: format, statistics, thread_limit: thread_limit.or(cfg!(target_os = "macos").then_some(3)), // TODO: make this a configurable when in `gix`, this seems to be optimal on MacOS, linux scales though! MacOS also scales if reading a lot of files for refresh index allow_write: !no_write, index_worktree_renames: index_worktree_renames.map(|percentage| percentage.unwrap_or(0.5)), submodules: submodules.map(|submodules| match submodules { Submodules::All => core::repository::status::Submodules::All, Submodules::RefChange => core::repository::status::Submodules::RefChange, Submodules::Modifications => core::repository::status::Submodules::Modifications, Submodules::None => core::repository::status::Submodules::None, }), }, ) }, ), Subcommands::Dirwalk(crate::plumbing::options::dirwalk::Platform { statistics, untracked, pathspec, }) => prepare_and_run( "dirwalk", trace, auto_verbose, progress, progress_keep_open, None, move |_progress, out, err| { core::repository::dirwalk::walk( repository(Mode::Lenient)?, pathspec, out, err, core::repository::dirwalk::Options { output_format: format, statistics, untracked: match untracked { crate::plumbing::options::dirwalk::Untracked::Collapsed => { core::repository::dirwalk::Untracked::Collapsed } crate::plumbing::options::dirwalk::Untracked::Matching => { core::repository::dirwalk::Untracked::Matching } }, }, ) }, ), Subcommands::Submodule(platform) => match platform .cmds .unwrap_or(crate::plumbing::options::submodule::Subcommands::List { dirty_suffix: None }) { crate::plumbing::options::submodule::Subcommands::List { dirty_suffix } => prepare_and_run( "submodule-list", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| { core::repository::submodule::list( repository(Mode::Lenient)?, out,Lines 1–500 of 1829. Load more
use std::{ io::{BufReader, stdin}, path::PathBuf, sync::{ Arc, atomic::{AtomicBool, Ordering}, }, }; use anyhow::{Context, Result, anyhow}; use clap::{CommandFactory, Parser}; use gitoxide_core as core; use gitoxide_core::{pack::verify, repository::PathsOrPatterns}; use gix::bstr::{BString, io::BufReadExt}; use crate::{ plumbing::{ options::{ Args, Subcommands, attributes, branch, commit, commitgraph, config, credential, exclude, free, fsck, index, mailmap, merge, odb, revision, tag, tree, }, show_progress, }, shared::pretty::prepare_and_run, }; #[cfg(feature = "gitoxide-core-async-client")] pub mod async_util { use crate::shared::ProgressRange; #[cfg(not(feature = "prodash-render-line"))] compile_error!("BUG: Need at least a line renderer in async mode"); pub fn prepare( verbose: bool, trace: bool, name: &str, range: impl Into<Option<ProgressRange>>, ) -> ( Option<prodash::render::line::JoinHandle>, gix_features::progress::DoOrDiscard<prodash::tree::Item>, ) { use crate::shared::{self, STANDARD_RANGE}; shared::init_env_logger(); if verbose { let progress = shared::progress_tree(trace); let sub_progress = progress.add_child(name); let ui_handle = shared::setup_line_renderer_range(&progress, range.into().unwrap_or(STANDARD_RANGE)); (Some(ui_handle), Some(sub_progress).into()) } else { (None, None.into()) } } } pub fn main() -> Result<()> { let args: Args = Args::parse_from(gix::env::args_os()); let thread_limit = args.threads; let verbose = args.verbose; let format = args.format; let cmd = args.cmd; #[cfg_attr(not(feature = "tracing"), allow(unused_mut))] #[cfg_attr(feature = "tracing", allow(unused_assignments))] let mut trace = false; #[cfg(feature = "tracing")] { trace = args.trace; } let object_hash = args.object_hash; let config = args.config; let repository = args.repository; let repository_path = repository.clone(); enum Mode { Strict, StrictWithGitInstallConfig, Lenient, LenientWithGitInstallConfig, } let repository = { let config = config.clone(); move |mut mode: Mode| -> Result<gix::Repository> { let mut mapping: gix::sec::trust::Mapping<gix::open::Options> = Default::default(); if !config.is_empty() { mode = match mode { Mode::Lenient => Mode::Strict, Mode::LenientWithGitInstallConfig => Mode::StrictWithGitInstallConfig, _ => mode, }; } let strict_toggle = matches!(mode, Mode::Strict | Mode::StrictWithGitInstallConfig) || args.strict; mapping.full = mapping.full.strict_config(strict_toggle); mapping.reduced = mapping.reduced.strict_config(strict_toggle); let git_installation = matches!( mode, Mode::StrictWithGitInstallConfig | Mode::LenientWithGitInstallConfig ); let to_match_settings = |mut opts: gix::open::Options| { opts.permissions.config.git_binary = git_installation; opts.permissions.attributes.git_binary = git_installation; if config.is_empty() { opts } else { opts.cli_overrides(config.clone()) } }; mapping.full.modify(to_match_settings); mapping.reduced.modify(to_match_settings); let mut repo = gix::ThreadSafeRepository::discover_with_environment_overrides_opts( repository, Default::default(), mapping, ) .map(gix::Repository::from)?; if !config.is_empty() { repo.config_snapshot_mut() .append_config(config.iter(), gix::config::Source::Cli) .context("Unable to parse command-line configuration")?; } { let mut config_mut = repo.config_snapshot_mut(); // Enable precious file parsing unless the user made a choice. if config_mut .boolean(gix::config::tree::Gitoxide::PARSE_PRECIOUS)? .is_none() { config_mut.set_raw_value(gix::config::tree::Gitoxide::PARSE_PRECIOUS, "true")?; } } Ok(repo) } }; let progress; let progress_keep_open; #[cfg(feature = "prodash-render-tui")] { progress = args.progress; progress_keep_open = args.progress_keep_open; } #[cfg(not(feature = "prodash-render-tui"))] { progress = false; progress_keep_open = false; } let auto_verbose = !progress && !args.no_verbose; let should_interrupt = Arc::new(AtomicBool::new(false)); #[expect(unsafe_code)] unsafe { // SAFETY: The closure doesn't use mutexes or memory allocation, so it should be safe to call from a signal handler. gix::interrupt::init_handler(1, { let should_interrupt = Arc::clone(&should_interrupt); move || should_interrupt.store(true, Ordering::SeqCst) })?; } match cmd { Subcommands::Env => prepare_and_run( "env", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| core::env(out, format), ), Subcommands::Merge(merge::Platform { cmd }) => match cmd { merge::SubCommands::File { resolve_with, ours, base, theirs, } => prepare_and_run( "merge-file", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| { core::repository::merge::file( repository(Mode::Lenient)?, out, format, resolve_with.map(Into::into), base, ours, theirs, ) }, ), merge::SubCommands::Tree { opts: merge::SharedOptions { in_memory, file_favor, tree_favor, debug, }, message, update_head, ours, base, theirs, } => prepare_and_run( "merge-tree", trace, verbose, progress, progress_keep_open, None, move |_progress, out, err| { core::repository::merge::tree( repository(Mode::Lenient)?, out, err, base, ours, theirs, core::repository::merge::tree::Options { format, file_favor: file_favor.map(Into::into), in_memory, tree_favor: tree_favor.map(Into::into), debug, message, update_head, }, ) }, ), merge::SubCommands::Commit { opts: merge::SharedOptions { in_memory, file_favor, tree_favor, debug, }, ours, theirs, } => prepare_and_run( "merge-commit", trace, verbose, progress, progress_keep_open, None, move |_progress, out, err| { core::repository::merge::commit( repository(Mode::Lenient)?, out, err, ours, theirs, core::repository::merge::tree::Options { format, file_favor: file_favor.map(Into::into), tree_favor: tree_favor.map(Into::into), in_memory, debug, message: None, update_head: false, }, ) }, ), }, Subcommands::MergeBase(crate::plumbing::options::merge_base::Command { first, others }) => prepare_and_run( "merge-base", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| { core::repository::merge_base(repository(Mode::Lenient)?, first, others, out, format) }, ), Subcommands::Diff(crate::plumbing::options::diff::Platform { cmd }) => match cmd { crate::plumbing::options::diff::SubCommands::Tree { old_treeish, new_treeish, } => prepare_and_run( "diff-tree", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| { core::repository::diff::tree(repository(Mode::Lenient)?, out, old_treeish, new_treeish) }, ), crate::plumbing::options::diff::SubCommands::File { old_revspec, new_revspec, } => prepare_and_run( "diff-file", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| { core::repository::diff::file(repository(Mode::Lenient)?, out, old_revspec, new_revspec) }, ), }, Subcommands::Log(crate::plumbing::options::log::Platform { pathspec }) => prepare_and_run( "log", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| core::repository::log::log(repository(Mode::Lenient)?, out, pathspec), ), Subcommands::Worktree(crate::plumbing::options::worktree::Platform { cmd }) => match cmd { crate::plumbing::options::worktree::SubCommands::List => prepare_and_run( "worktree-list", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| core::repository::worktree::list(repository(Mode::Lenient)?, out, format), ), }, Subcommands::IsClean | Subcommands::IsChanged => { let mode = if matches!(cmd, Subcommands::IsClean) { core::repository::dirty::Mode::IsClean } else { core::repository::dirty::Mode::IsDirty }; prepare_and_run( "clean", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| { core::repository::dirty::check(repository(Mode::Lenient)?, mode, out, format) }, ) } #[cfg(feature = "gitoxide-core-tools-clean")] Subcommands::Clean(crate::plumbing::options::clean::Command { debug, dry_run: _, execute, ignored, precious, directories, pathspec, repositories, pathspec_matches_result, skip_hidden_repositories, find_untracked_repositories, }) => prepare_and_run( "clean", trace, verbose, progress, progress_keep_open, None, move |_progress, out, err| { core::repository::clean( repository(Mode::Lenient)?, out, err, pathspec, core::repository::clean::Options { debug, format, execute, ignored, precious, directories, repositories, pathspec_matches_result, skip_hidden_repositories: skip_hidden_repositories.map(Into::into), find_untracked_repositories: find_untracked_repositories.into(), }, ) }, ), Subcommands::Status(crate::plumbing::options::status::Platform { ignored, untracked, format: status_format, statistics, submodules, no_write, pathspec, index_worktree_renames, }) => prepare_and_run( "status", trace, auto_verbose, progress, progress_keep_open, None, move |progress, out, err| { use crate::plumbing::options::status::Submodules; core::repository::status::show( repository(Mode::Lenient)?, pathspec, out, err, progress, core::repository::status::Options { format: match status_format.unwrap_or_default() { crate::plumbing::options::status::Format::Simplified => { core::repository::status::Format::Simplified } crate::plumbing::options::status::Format::PorcelainV2 => { core::repository::status::Format::PorcelainV2 } }, ignored: ignored.map(|ignored| match ignored.unwrap_or_default() { crate::plumbing::options::status::Ignored::Matching => { core::repository::status::Ignored::Matching } crate::plumbing::options::status::Ignored::Collapsed => { core::repository::status::Ignored::Collapsed } }), untracked: untracked.map(|mode| match mode.unwrap_or_default() { crate::plumbing::options::status::Untracked::No => gix::status::UntrackedFiles::None, crate::plumbing::options::status::Untracked::Normal => { gix::status::UntrackedFiles::Collapsed } crate::plumbing::options::status::Untracked::All => gix::status::UntrackedFiles::Files, }), output_format: format, statistics, thread_limit: thread_limit.or(cfg!(target_os = "macos").then_some(3)), // TODO: make this a configurable when in `gix`, this seems to be optimal on MacOS, linux scales though! MacOS also scales if reading a lot of files for refresh index allow_write: !no_write, index_worktree_renames: index_worktree_renames.map(|percentage| percentage.unwrap_or(0.5)), submodules: submodules.map(|submodules| match submodules { Submodules::All => core::repository::status::Submodules::All, Submodules::RefChange => core::repository::status::Submodules::RefChange, Submodules::Modifications => core::repository::status::Submodules::Modifications, Submodules::None => core::repository::status::Submodules::None, }), }, ) }, ), Subcommands::Dirwalk(crate::plumbing::options::dirwalk::Platform { statistics, untracked, pathspec, }) => prepare_and_run( "dirwalk", trace, auto_verbose, progress, progress_keep_open, None, move |_progress, out, err| { core::repository::dirwalk::walk( repository(Mode::Lenient)?, pathspec, out, err, core::repository::dirwalk::Options { output_format: format, statistics, untracked: match untracked { crate::plumbing::options::dirwalk::Untracked::Collapsed => { core::repository::dirwalk::Untracked::Collapsed } crate::plumbing::options::dirwalk::Untracked::Matching => { core::repository::dirwalk::Untracked::Matching } }, }, ) }, ), Subcommands::Submodule(platform) => match platform .cmds .unwrap_or(crate::plumbing::options::submodule::Subcommands::List { dirty_suffix: None }) { crate::plumbing::options::submodule::Subcommands::List { dirty_suffix } => prepare_and_run( "submodule-list", trace, verbose, progress, progress_keep_open, None, move |_progress, out, _err| { core::repository::submodule::list( repository(Mode::Lenient)?, out,