use std::path::PathBuf; use clap_complete::Shell; #[derive(Debug, clap::Parser)]#[clap(name = "ein", about = "The rusty git", version = option_env!("GIX_VERSION"))]#[clap(subcommand_required = true)]pub struct Args { /// Do not display verbose messages and progress information. #[clap(long, short = 'q')] pub quiet: bool, /// Bring up a terminal user interface displaying progress visually. #[clap(long, conflicts_with("quiet"))] pub progress: bool, /// The number of threads to use. If unset, use all cores, if 0 use all physical cores. #[clap(short = 't', long)] pub threads: Option<usize>, /// The progress TUI will stay up even though the work is already completed. /// /// Use this to be able to read progress messages or additional information visible in the TUI log pane. #[clap(long, conflicts_with("quiet"), requires("progress"))] pub progress_keep_open: bool, #[clap(subcommand)] pub cmd: Subcommands,} #[derive(Debug, clap::Subcommand)]pub enum Subcommands { /// Initialize the repository in the current directory. #[clap(visible_alias = "initialize")] Init { /// The directory in which to initialize a new git repository. /// /// Defaults to the current working directory. directory: Option<PathBuf>, }, /// A selection of useful tools. #[cfg(feature = "gitoxide-core-tools")] #[clap(subcommand)] Tool(ToolCommands), /// Generate shell completions to stdout or a directory. #[clap(visible_alias = "generate-completions", visible_alias = "shell-completions")] Completions { /// The shell to generate completions for. Otherwise it's derived from the environment. #[clap(long, short)] shell: Option<Shell>, /// The output directory in case multiple files are generated. If not provided, will write to stdout. out_dir: Option<String>, }, /// Panic immediately, to test panic behavior. #[cfg(debug_assertions)] Panic,} #[cfg(feature = "gitoxide-core-tools")]#[derive(Debug, clap::Subcommand)]#[clap(subcommand_required = true)]#[clap(visible_alias = "t")]pub enum ToolCommands { /// Find all repositories in a given directory. Find { /// If set, print additional information to help understand why the traversal is slow. /// /// Typically it will encounter too many paths without a git repository, forcing a lot /// of additional paths to be searched unnecessarily. #[clap(long, short = 'd')] debug: bool, /// The directory in which to find all git repositories. /// /// Defaults to the current working directory. root: Option<PathBuf>, }, /// Move all repositories found in a directory into a structure matching their clone URLs. Organize { #[clap(long)] /// The operation will be in dry-run mode unless this flag is set. execute: bool, #[clap(long, short = 'f')] /// The directory to use when finding input repositories to move into position. /// /// Defaults to the current working directory. repository_source: Option<PathBuf>, #[clap(long, short = 't')] /// The directory to which to move repositories found in the repository-source. /// /// Defaults to the current working directory. destination_directory: Option<PathBuf>, }, #[cfg(feature = "gitoxide-core-tools-query")] Query(tools::Query), EstimateHours(tools::EstimateHours),} #[cfg(feature = "gitoxide-core-tools")]pub mod tools { use std::path::PathBuf; use gix::bstr::BString; #[cfg(feature = "gitoxide-core-tools-query")] #[derive(Debug, clap::Parser)] #[command( about = "a database accelerated engine to extract information and query it", visible_alias = "q" )] pub struct Query { /// The total amount of object cache memory in MB. Bigger repos may benefit from more memory. /// /// 0 disables it. #[arg(long, short = 'o', default_value_t = 200)] pub object_cache_size_mb: usize, /// Find identical copies in the entire tree, not only in the set of modified files. /// /// This is an expensive option, and typically cuts speed in half. #[arg(long, short = 'C')] pub find_copies_harder: bool, /// Path to the Git repository to generate the database for. #[arg(default_value = ".")] pub repo_dir: std::path::PathBuf, #[clap(subcommand)] pub cmd: Option<query::Command>, } #[cfg(feature = "gitoxide-core-tools-query")] pub mod query { use crate::shared::AsPathSpec; #[derive(Debug, clap::Subcommand)] pub enum Command { /// Follow a file through the entire history reachable from HEAD. #[command(visible_alias = "trace-file")] TracePath { /// The path to trace through history. // This can't be here anymore as we also need to compile without `Send` support. #[clap(value_parser = AsPathSpec)] path: gix::bstr::BString, }, } } #[derive(Debug, clap::Parser)] #[clap( about = "Estimate hours worked based on a commit history", long_about = "See https://github.com/kimmobrunfeldt/git-hours#how-it-works for details", visible_alias = "h", visible_alias = "hours" )] pub struct EstimateHours { /// The directory containing a '.git/' folder. #[clap(value_parser = validator::IsRepo)] #[clap(default_value = ".")] pub working_dir: PathBuf, /// The name of the revision as spec, like 'HEAD' or 'main' at which to start iterating the commit graph. #[clap(default_value("HEAD"), value_parser = crate::shared::AsBString)] pub rev_spec: BString, /// Ignore github bots which match the `[bot]` search string. #[clap(short = 'b', long)] pub no_bots: bool, /// Collect additional information about file modifications, additions and deletions (without rename tracking). #[clap(short = 'f', long)] pub file_stats: bool, /// Collect additional information about lines added and deleted (without rename tracking). /// /// Note that this implies the work to be done for file-stats, so it should be set as well. #[clap(short = 'l', long)] pub line_stats: bool, /// Show personally identifiable information before the summary. Includes names and email addresses. #[clap(short = 'p', long)] pub show_pii: bool, /// Omit unifying identities by name and email which can lead to the same author appear multiple times /// due to using different names or email addresses. #[clap(short = 'i', long)] pub omit_unify_identities: bool, } mod validator { use std::{ffi::OsStr, path::PathBuf}; use anyhow::Context; #[derive(Clone)] pub struct IsRepo; impl clap::builder::TypedValueParser for IsRepo { type Value = PathBuf; fn parse_ref( &self, cmd: &clap::Command, _arg: Option<&clap::Arg>, value: &OsStr, ) -> Result<Self::Value, clap::Error> { assure_is_repo(value).map_err(|e| { let mut err = clap::Error::new(clap::error::ErrorKind::InvalidValue).with_cmd(cmd); err.insert( clap::error::ContextKind::InvalidValue, clap::error::ContextValue::String(e.to_string()), ); err })?; Ok(value.into()) } } fn assure_is_repo(dir: &OsStr) -> anyhow::Result<()> { let git_dir = PathBuf::from(dir).join(".git"); let p = gix::path::realpath(&git_dir) .with_context(|| format!("Could not canonicalize git repository at '{}'", git_dir.display()))?; if p.extension().unwrap_or_default() == "git" || p.file_name().unwrap_or_default() == ".git" || p.join("HEAD").is_file() { Ok(()) } else { Err(anyhow::anyhow!( "Path '{}' needs to be a directory containing '.git/'", p.display() )) } } }}
use std::path::PathBuf;
use clap_complete::Shell;
#[derive(Debug, clap::Parser)]
#[clap(name = "ein", about = "The rusty git", version = option_env!("GIX_VERSION"))]
#[clap(subcommand_required = true)]
pub struct Args {
/// Do not display verbose messages and progress information.
#[clap(long, short = 'q')]
pub quiet: bool,
/// Bring up a terminal user interface displaying progress visually.
#[clap(long, conflicts_with("quiet"))]
pub progress: bool,
/// The number of threads to use. If unset, use all cores, if 0 use all physical cores.
#[clap(short = 't', long)]
pub threads: Option<usize>,
/// The progress TUI will stay up even though the work is already completed.
///
/// Use this to be able to read progress messages or additional information visible in the TUI log pane.
#[clap(long, conflicts_with("quiet"), requires("progress"))]
pub progress_keep_open: bool,
#[clap(subcommand)]
pub cmd: Subcommands,
}
#[derive(Debug, clap::Subcommand)]
pub enum Subcommands {
/// Initialize the repository in the current directory.
#[clap(visible_alias = "initialize")]
Init {
/// The directory in which to initialize a new git repository.
///
/// Defaults to the current working directory.
directory: Option<PathBuf>,
},
/// A selection of useful tools.
#[cfg(feature = "gitoxide-core-tools")]
#[clap(subcommand)]
Tool(ToolCommands),
/// Generate shell completions to stdout or a directory.
#[clap(visible_alias = "generate-completions", visible_alias = "shell-completions")]
Completions {
/// The shell to generate completions for. Otherwise it's derived from the environment.
#[clap(long, short)]
shell: Option<Shell>,
/// The output directory in case multiple files are generated. If not provided, will write to stdout.
out_dir: Option<String>,
},
/// Panic immediately, to test panic behavior.
#[cfg(debug_assertions)]
Panic,
}
#[cfg(feature = "gitoxide-core-tools")]
#[derive(Debug, clap::Subcommand)]
#[clap(subcommand_required = true)]
#[clap(visible_alias = "t")]
pub enum ToolCommands {
/// Find all repositories in a given directory.
Find {
/// If set, print additional information to help understand why the traversal is slow.
///
/// Typically it will encounter too many paths without a git repository, forcing a lot
/// of additional paths to be searched unnecessarily.
#[clap(long, short = 'd')]
debug: bool,
/// The directory in which to find all git repositories.
///
/// Defaults to the current working directory.
root: Option<PathBuf>,
},
/// Move all repositories found in a directory into a structure matching their clone URLs.
Organize {
#[clap(long)]
/// The operation will be in dry-run mode unless this flag is set.
execute: bool,
#[clap(long, short = 'f')]
/// The directory to use when finding input repositories to move into position.
///
/// Defaults to the current working directory.
repository_source: Option<PathBuf>,
#[clap(long, short = 't')]
/// The directory to which to move repositories found in the repository-source.
///
/// Defaults to the current working directory.
destination_directory: Option<PathBuf>,
},
#[cfg(feature = "gitoxide-core-tools-query")]
Query(tools::Query),
EstimateHours(tools::EstimateHours),
}
#[cfg(feature = "gitoxide-core-tools")]
pub mod tools {
use std::path::PathBuf;
use gix::bstr::BString;
#[cfg(feature = "gitoxide-core-tools-query")]
#[derive(Debug, clap::Parser)]
#[command(
about = "a database accelerated engine to extract information and query it",
visible_alias = "q"
)]
pub struct Query {
/// The total amount of object cache memory in MB. Bigger repos may benefit from more memory.
///
/// 0 disables it.
#[arg(long, short = 'o', default_value_t = 200)]
pub object_cache_size_mb: usize,
/// Find identical copies in the entire tree, not only in the set of modified files.
///
/// This is an expensive option, and typically cuts speed in half.
#[arg(long, short = 'C')]
pub find_copies_harder: bool,
/// Path to the Git repository to generate the database for.
#[arg(default_value = ".")]
pub repo_dir: std::path::PathBuf,
#[clap(subcommand)]
pub cmd: Option<query::Command>,
}
#[cfg(feature = "gitoxide-core-tools-query")]
pub mod query {
use crate::shared::AsPathSpec;
#[derive(Debug, clap::Subcommand)]
pub enum Command {
/// Follow a file through the entire history reachable from HEAD.
#[command(visible_alias = "trace-file")]
TracePath {
/// The path to trace through history.
// This can't be here anymore as we also need to compile without `Send` support.
#[clap(value_parser = AsPathSpec)]
path: gix::bstr::BString,
},
}
}
#[derive(Debug, clap::Parser)]
#[clap(
about = "Estimate hours worked based on a commit history",
long_about = "See https://github.com/kimmobrunfeldt/git-hours#how-it-works for details",
visible_alias = "h",
visible_alias = "hours"
)]
pub struct EstimateHours {
/// The directory containing a '.git/' folder.
#[clap(value_parser = validator::IsRepo)]
#[clap(default_value = ".")]
pub working_dir: PathBuf,
/// The name of the revision as spec, like 'HEAD' or 'main' at which to start iterating the commit graph.
#[clap(default_value("HEAD"), value_parser = crate::shared::AsBString)]
pub rev_spec: BString,
/// Ignore github bots which match the `[bot]` search string.
#[clap(short = 'b', long)]
pub no_bots: bool,
/// Collect additional information about file modifications, additions and deletions (without rename tracking).
#[clap(short = 'f', long)]
pub file_stats: bool,
/// Collect additional information about lines added and deleted (without rename tracking).
///
/// Note that this implies the work to be done for file-stats, so it should be set as well.
#[clap(short = 'l', long)]
pub line_stats: bool,
/// Show personally identifiable information before the summary. Includes names and email addresses.
#[clap(short = 'p', long)]
pub show_pii: bool,
/// Omit unifying identities by name and email which can lead to the same author appear multiple times
/// due to using different names or email addresses.
#[clap(short = 'i', long)]
pub omit_unify_identities: bool,
}
mod validator {
use std::{ffi::OsStr, path::PathBuf};
use anyhow::Context;
#[derive(Clone)]
pub struct IsRepo;
impl clap::builder::TypedValueParser for IsRepo {
type Value = PathBuf;
fn parse_ref(
&self,
cmd: &clap::Command,
_arg: Option<&clap::Arg>,
value: &OsStr,
) -> Result<Self::Value, clap::Error> {
assure_is_repo(value).map_err(|e| {
let mut err = clap::Error::new(clap::error::ErrorKind::InvalidValue).with_cmd(cmd);
err.insert(
clap::error::ContextKind::InvalidValue,
clap::error::ContextValue::String(e.to_string()),
);
err
})?;
Ok(value.into())
}
}
fn assure_is_repo(dir: &OsStr) -> anyhow::Result<()> {
let git_dir = PathBuf::from(dir).join(".git");
let p = gix::path::realpath(&git_dir)
.with_context(|| format!("Could not canonicalize git repository at '{}'", git_dir.display()))?;
if p.extension().unwrap_or_default() == "git"
|| p.file_name().unwrap_or_default() == ".git"
|| p.join("HEAD").is_file()
{
Ok(())
} else {
Err(anyhow::anyhow!(
"Path '{}' needs to be a directory containing '.git/'",
p.display()
))
}
}
}
}