use std::{ffi::OsStr, path::PathBuf}; use clap::{ Arg, Command, Error, builder::{OsStringValueParser, TypedValueParser}, }; use gix::bstr::BString; #[derive(Debug, clap::Parser)] #[clap(name = "it", about = "internal tools to help create test cases")] pub struct Args { #[clap(subcommand)] pub cmd: Subcommands, } #[derive(Debug, clap::Subcommand)] pub enum Subcommands { /// Generate a shell script that creates a git repository containing all commits that are /// traversed when following a given file through the Git history just as `git blame` would. /// /// This command extracts the file’s history so that blame, when run on the repository created /// by the script, shows the same characteristics, in particular bugs, as the original, but in /// a way that does not resemble the original source file's content to any greater extent than /// is useful and necessary. /// /// Note that this should not be used to redact sensitive information. The obfuscation leaves /// numerous properties of the source intact, such that it may be feasible to reconstruct the /// input. /// /// This command can also be helpful in debugging the blame algorithm itself. /// /// ### Terminology /// /// A **blame history** is the set of commits that the blame algorithm, at some point, treated /// as potential suspects for any line in a file. It is a subset of all commits that ever /// changed a file in its history. /// /// With respect to branches and merge commits, the **blame history** will not necessarily be /// identical to the file's history in the source repository. This is because the blame /// algorithm will stop following a file's history for branches that only touch lines for which /// the source has already been found. The **blame history**, thus, looks likely "cleaner" and /// "simpler" than the source history. #[clap(visible_alias = "bcr")] BlameCopyRoyal { /// Don't really copy anything. #[clap(long, short = 'n')] dry_run: bool, /// The git root whose history to extract the blame-relevant parts from. worktree_dir: PathBuf, /// The directory into which to copy the files. destination_dir: PathBuf, /// The directory to place assets in. #[clap(long)] asset_dir: Option, /// The file to extract the history for. file: std::ffi::OsString, /// Do not use `copy-royal` to obfuscate the content of blobs, but copy it verbatim. /// /// Note that, for producing cases for the gitoxide test suite, we usually prefer only to /// take blobs verbatim if the source repository was purely for testing. #[clap(long)] verbatim: bool, }, /// Copy a tree so that it diffs the same but does not resemble the original files' content to /// any greater extent than is useful and necessary. /// /// The idea is that this preserves the patterns that are usually sufficient to reproduce cases /// for tests of diffs, both for making the tests work and for keeping the diffs understandable /// to developers working on the tests, while avoiding keeping large verbatim fragments of code /// based on which the test cases were created. The benefits of "reducing" the code to these /// patterns include that the original meaning and function of code will not be confused with /// the code of gitoxide itself, will not distract from the effects observed in their diffs, /// and will not inadvertently be caught up in code cleanup efforts (e.g. attempting to adjust /// style or fix bugs) that would make sense in code of gitoxide itself but that would subtly /// break data test fixtures if done on their data. /// /// Note that this should not be used to redact sensitive information. The obfuscation leaves /// numerous properties of the source intact, such that it may be feasible to reconstruct the /// input. #[clap(visible_alias = "cr")] CopyRoyal { /// Don't really copy anything. #[clap(long, short = 'n')] dry_run: bool, /// The git root whose tracked files to copy. worktree_dir: PathBuf, /// The directory into which to copy the files. destination_dir: PathBuf, /// The pathspecs to determine which paths to copy from `worktree_dir`. /// /// None will copy everything. #[clap(value_parser = AsPathSpec)] patterns: Vec, }, /// Serialize a git repository as linear history while degenerating content into a shell script that reproduces it. #[clap(visible_alias = "gts")] GitToSh { /// The amount of commits to copy from `committish`. /// /// If 0, all traversable commits will be copied. #[clap(long, short = 'c', default_value_t = 0)] count: usize, /// Do not use `copy-royal` to degenerate information of blobs, but take blobs verbatim. /// /// Note that, for producing cases for the gitoxide test suite, we usually prefer only to /// take blobs verbatim if the source repository was purely for testing. #[clap(long)] verbatim: bool, /// The directory into which the blobs and tree declarations will be written. #[clap(long, short = 'o', default_value = ".")] output_dir: PathBuf, /// The path to the git repository to serialize. repo_dir: PathBuf, /// The name of the directory within `output_dir` for storing blobs and trees. name: String, /// A revspec of the commit to start the iteration from, like `@`. /// /// Note that the history will be serialized, and multiple parents aren't allowed. committish: String, /// The pathspecs to determine which paths to copy from each commit's tree. /// /// None will copy everything. #[clap(value_parser = AsPathSpec)] patterns: Vec, }, /// Take a slider file generated with the help of [diff-slider-tools] and turn it into a series /// of baseline diffs to be used in [slider-rs]. /// /// See [make-diff-for-sliders-repo] for details. /// /// [diff-slider-tools]: https://github.com/mhagger/diff-slider-tools /// [slider-rs]: gix-diff/tests/diff/blob/slider.rs /// [make-diff-for-sliders-repo]: gix-diff/tests/fixtures/make_diff_for_sliders_repo.sh CreateDiffCases { /// Don't really copy anything. #[clap(long, short = 'n')] dry_run: bool, /// The `.sliders` file that contains a list of sliders. #[clap(long)] sliders_file: PathBuf, /// The git root to extract the diff-related parts from. #[clap(long)] worktree_dir: PathBuf, /// The directory into which to copy the files. #[clap(long)] destination_dir: PathBuf, /// The number of sliders to generate test cases for. #[clap(long, default_value_t = 10)] count: usize, /// The directory to place assets in. #[clap(long)] asset_dir: Option, }, /// Check for executable bits that disagree with shebangs. /// /// This checks committed and staged files, but not anything unstaged, to find shell scripts /// that either begin with a `#!` but not `+x` permissions, or do not begin with `#!` but do /// have `+x` permissions. Such mismatches are reported but not automatically corrected. Some /// platforms (at least Windows) do not have such permissions, but Git still represents them. /// /// This currently only checks files name with an `.sh` suffix, and only operates on the /// current repository. Its main use is checking that fixture scripts have correct modes. #[clap(visible_alias = "cm")] CheckMode {}, /// Print environment variables as `NAME=value` lines. /// /// It is useful to be able to observe environment variables that are set when running code /// with tools such as `cargo` or `cross`. Commands like `cargo run -p internal-tools -- env` /// include environment changes from `cargo` itself. With `cross`, changes are more extensive, /// due to effects of `build.env.passthrough`, container customization, and preexisting special /// cases in wrapper scripts shipped in default `cross` containers (such as to `LD_PRELOAD`). /// /// Since one use for checking environment variables is to investigate the effects of /// environments that contain variable names or values that are not valid Unicode, this avoids /// requiring that environment variables all be Unicode. Any name or value that is not Unicode /// is shown in its Rust debug representation. This is always quoted. To decrease ambiguity, /// any name or value containing a literal double quote or newline is also shown in its debug /// representation. Names and values without such content are shown literally and not quoted. #[clap(visible_alias = "e")] Env {}, } #[derive(Clone)] pub struct AsPathSpec; impl TypedValueParser for AsPathSpec { type Value = gix::pathspec::Pattern; fn parse_ref(&self, cmd: &Command, arg: Option<&Arg>, value: &OsStr) -> Result { let pathspec_defaults = gix::pathspec::Defaults::from_environment(&mut |n| std::env::var_os(n)).unwrap_or_default(); OsStringValueParser::new() .try_map(move |arg| { let arg: &std::path::Path = arg.as_os_str().as_ref(); gix::pathspec::parse(gix::path::into_bstr(arg).as_ref(), pathspec_defaults) }) .parse_ref(cmd, arg, value) } }