-
-
Notifications
You must be signed in to change notification settings - Fork 328
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
it royal-copy
a degenerative copy tool
Its main purpose is to help build test-cases from real-world repositories.
- Loading branch information
Showing
5 changed files
with
225 additions
and
6 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] } | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
use clap::Parser; | ||
|
||
mod commands { | ||
pub(crate) mod royal_copy { | ||
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_digit(10) { | ||
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) {} | ||
} | ||
} | ||
} | ||
|
||
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 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) | ||
} | ||
} | ||
} | ||
use args::{Args, Subcommands}; |