Skip to content

Commit

Permalink
Add it royal-copy a degenerative copy tool
Browse files Browse the repository at this point in the history
Its main purpose is to help build test-cases from real-world repositories.
  • Loading branch information
Byron committed Aug 19, 2024
1 parent 5ac3abb commit f67249b
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 6 deletions.
21 changes: 15 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ members = [
"gix-revwalk",
"gix-fsck",
"tests/tools",
"tests/it",
"gix-diff/tests",
"gix-pack/tests",
"gix-odb/tests",
Expand Down
5 changes: 5 additions & 0 deletions tests/it/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions tests/it/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "internal-tools"
description = "internal CLI tooling to help generated test-cases"
version = "0.0.0"
authors = ["Sebastian Thiel <[email protected]>"]
edition = "2021"
license = "MIT OR Apache-2.0"
publish = false

[[bin]]
name = "it"
path = "src/main.rs"

[dependencies]
clap = { version = "4.5.16", features = ["derive"] }
anyhow = "1.0.86"

gix = { version = "0.64.0", path = "../../gix", default-features = false, features = ["attributes"] }

52 changes: 52 additions & 0 deletions tests/it/src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use clap::builder::{OsStringValueParser, TypedValueParser};
use clap::{Arg, Command, Error};
use std::ffi::OsStr;
use std::path::PathBuf;

#[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 {
/// Copy a tree so that it diffs the same but can't be traced back uniquely to its source.
///
/// The idea is that we don't want to deal with licensing, it's more about patterns in order to
/// reproduce cases for tests.
#[clap(visible_alias = "rc")]
RoyalCopy {
/// 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<gix::pathspec::Pattern>,
},
}

#[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<Self::Value, Error> {
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)
}
}
1 change: 1 addition & 0 deletions tests/it/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub(crate) mod royal_copy;
111 changes: 111 additions & 0 deletions tests/it/src/commands/royal_copy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use anyhow::Context;
use gix::fs::Stack;
use gix::pathspec::Pattern;
use std::path::{Path, PathBuf};

pub fn doit(
dry_run: bool,
worktree_dir: &Path,
destination_dir: PathBuf,
patterns: Vec<Pattern>,
) -> anyhow::Result<()> {
let prefix = if dry_run { "WOULD" } else { "Will" };
let repo = gix::open(worktree_dir)?;
let index = repo.index()?;
let mut specs = repo.pathspec(
true,
// TODO: ideally this could accept patterns already.
patterns.into_iter().map(|p| p.to_bstring()),
true,
&index,
gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping,
)?;
let mut create_dir = CreateDir { dry_run };
let mut stack = gix::fs::Stack::new(destination_dir);
for (rela_path, _entry) in specs
.index_entries_with_paths(&index)
.context("Didn't find a single entry to copy")?
{
let rela_path = gix::path::from_bstr(rela_path);
let src = worktree_dir.join(&rela_path);
stack.make_relative_path_current(&rela_path, &mut create_dir)?;
let dst = stack.current();

eprintln!(
"{prefix} copy '{src}' to '{dst}'",
src = src.display(),
dst = dst.display()
);
if !dry_run {
let content = std::fs::read_to_string(&src).with_context(|| {
format!(
"Need UTF-8 decodable content in '{src}' - skip binaries with pathspec",
src = src.display()
)
})?;
std::fs::write(dst, remapped(content))?
}
}
Ok(())
}

fn remapped(i: String) -> String {
i.chars()
.filter_map(|c| {
Some(if c.is_alphabetic() {
if c.is_uppercase() {
match (c as u32) % 10 {
0 => 'A',
1 => 'E',
2 => 'I',
3 => 'O',
4 => 'U',
5 => 'X',
6 => 'R',
7 => 'S',
8 => 'T',
9 => 'Y',
_ => unreachable!(),
}
} else {
match (c as u32) % 10 {
0 => 'a',
1 => 'e',
2 => 'i',
3 => 'o',
4 => 'u',
5 => 'x',
6 => 'r',
7 => 's',
8 => 't',
9 => 'y',
_ => unreachable!(),
}
}
} else if c.is_whitespace() || c.is_ascii_punctuation() || c.is_ascii_digit() {
c
} else {
return None;
})
})
.collect()
}

struct CreateDir {
dry_run: bool,
}

impl gix::fs::stack::Delegate for CreateDir {
fn push_directory(&mut self, stack: &Stack) -> std::io::Result<()> {
if !self.dry_run && !stack.current().is_dir() {
std::fs::create_dir(stack.current())?;
}
Ok(())
}

fn push(&mut self, _is_last_component: bool, _stack: &Stack) -> std::io::Result<()> {
Ok(())
}

fn pop_directory(&mut self) {}
}
18 changes: 18 additions & 0 deletions tests/it/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use clap::Parser;

mod commands;

fn main() -> anyhow::Result<()> {
let args: Args = Args::parse();
match args.cmd {
Subcommands::RoyalCopy {
dry_run,
worktree_dir: worktree_root,
destination_dir,
patterns,
} => commands::royal_copy::doit(dry_run, &worktree_root, destination_dir, patterns),
}
}

mod args;
use args::{Args, Subcommands};

0 comments on commit f67249b

Please sign in to comment.