diff options
Diffstat (limited to 'pkgs/by-name/ro/rollup/src/main.rs')
| -rw-r--r-- | pkgs/by-name/ro/rollup/src/main.rs | 254 |
1 files changed, 199 insertions, 55 deletions
diff --git a/pkgs/by-name/ro/rollup/src/main.rs b/pkgs/by-name/ro/rollup/src/main.rs index ed4e29b4..28ef0fea 100644 --- a/pkgs/by-name/ro/rollup/src/main.rs +++ b/pkgs/by-name/ro/rollup/src/main.rs @@ -1,5 +1,7 @@ use std::{ + collections::HashMap, env::current_dir, + ffi::OsStr, fs, io::Write, path::{Path, PathBuf}, @@ -9,12 +11,20 @@ use std::{ use anyhow::{Context, Result, bail}; use clap::{Parser, Subcommand}; -use crate::parse::{FileDiff, Patch}; +use crate::{ + config::{DROP_IN_NAME, DropInConfig, MAIN_CONFIG_NAME, MainConfig, Owner}, + parse::{FileDiff, Patch}, +}; +mod config; mod parse; #[derive(Parser)] struct Cli { + /// Don't actually do anything, just check' + #[arg(long, global = true)] + dry_run: bool, + #[command(subcommand)] subcommand: Cmd, } @@ -27,10 +37,11 @@ enum Cmd { patch_files: Vec<PathBuf>, /// Which repository to use as a target for the rollup + /// It is specified via the owner, declared in the rollup.toml config. /// /// The repository needs to be clean. #[arg(short, long)] - to: PathBuf, + to: String, /// Show debug information related to the parsing of patch files. #[arg(short, long)] @@ -39,22 +50,23 @@ enum Cmd { /// Create the patch files (based on the given tag) Create { - /// Which tag to use to mark the already applied patches. - #[arg(default_value = "CONFIG_APPLIED")] - tag: String, + /// Which repository to use for the tag to base created tags on. + /// It is specified via the owner, declared in the rollup.toml config. + /// + /// The repository needs to be clean. + #[arg(short, long)] + to: String, }, /// First create the patches, and then automatically apply them. CreateApply { - /// Which tag to use to mark the already applied patches. - #[arg(default_value = "CONFIG_APPLIED")] - tag: String, - - /// Which repository to use as a target for the rollup + /// Which repository to use as a target for the rollup and the tag for new + /// patches. + /// It is specified via the owner, declared in the rollup.toml config. /// /// The repository needs to be clean. #[arg(short, long)] - to: PathBuf, + to: String, /// Show debug information related to the parsing of patch files. #[arg(short, long)] @@ -118,8 +130,24 @@ macro_rules! git { }}; } +macro_rules! drop_msg { + ($fmt:literal, $($args:expr),*) => { + eprintln!(" > {}", format!($fmt, $($args),*)); + }; +} + fn main() -> Result<()> { let args = Cli::parse(); + let root = { + let (stdout, _) = git!(in current_dir()?; "rev-parse", "--show-toplevel"); + PathBuf::from(stdout.trim()) + }; + let config = config::load(&root).with_context(|| { + format!( + "Failed to load the config (`{MAIN_CONFIG_NAME}`), in root: `{}`", + root.display() + ) + })?; match args.subcommand { Cmd::Apply { @@ -127,46 +155,69 @@ fn main() -> Result<()> { to, debug_parse, } => { - check_to(&to)?; - apply_patches(patch_files, to, debug_parse)? + let owner = config.get_owner(&to)?; + check_owner(owner)?; + apply_patches(patch_files, owner, debug_parse, &config, root, args.dry_run)? } - Cmd::Create { tag } => { - create_patches(tag)?; + Cmd::Create { to } => { + let owner = config.get_owner(&to)?; + create_patches(&owner.last_applied_tag, args.dry_run)?; } - Cmd::CreateApply { - tag, - to, - debug_parse, - } => { - check_to(&to)?; + Cmd::CreateApply { to, debug_parse } => { + let owner = config.get_owner(&to)?; + check_owner(owner)?; - let Some(patches_directory) = create_patches(tag)? else { + let Some(patches_directory) = create_patches(&owner.last_applied_tag, args.dry_run)? + else { return Ok(()); }; + let patch_files = { + let iter = fs::read_dir(&patches_directory)?; + let mut output = vec![None; iter.count()]; + let iter = fs::read_dir(patches_directory)?; - let mut output = vec![]; for entry in iter { let entry = entry?; if !entry.file_type()?.is_file() { unreachable!("Should not be able to get non-files in patches directory"); } - output.push(entry.path()); + let path = entry.path(); + let idx: usize = { + let mut file_name = path + .file_name() + .expect("Should all have a filename") + .to_str() + .expect("Should be valid utf8") + .chars(); + let number = file_name.clone().take(4).collect::<String>(); + assert_eq!(file_name.nth(4), Some('-')); + + number.parse().expect("Should parse as usize number") + }; + output[idx - 1] = Some(path); } output.sort(); output }; - apply_patches(patch_files, to, debug_parse)?; + apply_patches( + patch_files.into_iter().map(|s| s.unwrap()).collect(), + owner, + debug_parse, + &config, + root, + args.dry_run, + )?; } } Ok(()) } -fn create_patches(tag: String) -> Result<Option<PathBuf>> { +fn create_patches(tag: &str, dry_run: bool) -> Result<Option<PathBuf>> { let current_dir = current_dir()?; - let (stdout, _) = git!(in current_dir; "tag", "--contains", "HEAD", tag.as_str()); + let (stdout, _) = git!(in current_dir; "tag", "--contains", "HEAD", tag); if !stdout.is_empty() { println!( "There are no tags to generate. Tag {} already points to HEAD.", @@ -186,15 +237,29 @@ fn create_patches(tag: String) -> Result<Option<PathBuf>> { let tempdir = PathBuf::from(String::from_utf8(mktemp.stdout).unwrap().trim()); git!(in current_dir; "format-patch", format!("{}..", tag).as_str(), "-o", tempdir.display().to_string().as_str()); - git!(in current_dir; "tag", "--delete", tag.as_str()); - git!(in current_dir; "tag", "--annotate", tag.as_str(), "-m", ""); + + if dry_run { + eprintln!("Dry-run is on; Not actually moving patch tag `{tag}` forward.") + } else { + git!(in current_dir; "tag", "--delete", tag); + git!(in current_dir; "tag", "--annotate", tag, "-m", ""); + } println!("Patches have been generated here: `{}`", tempdir.display()); Ok(Some(tempdir)) } -fn apply_patches(patch_files: Vec<PathBuf>, to: PathBuf, debug_parse: bool) -> Result<()> { +fn apply_patches( + patch_files: Vec<PathBuf>, + owner: &Owner, + debug_parse: bool, + config: &MainConfig, + root: PathBuf, + dry_run: bool, +) -> Result<()> { + let applies_to = &owner.applies_to; + let mut patches = vec![]; for file in &patch_files { patches.push(parse::parse_file(file, debug_parse)?); @@ -203,11 +268,11 @@ fn apply_patches(patch_files: Vec<PathBuf>, to: PathBuf, debug_parse: bool) -> R eprintln!("Successfully parsed all {} patch files.", patches.len()); { - let (stdout, _) = git!(in to; "status", "--porcelain"); + let (stdout, _) = git!(in applies_to; "status", "--porcelain"); if !stdout.is_empty() { bail!( "`--to` (`{}`) workspace is dirty, refusing to continue.", - to.display() + applies_to.display() ); } } @@ -216,56 +281,64 @@ fn apply_patches(patch_files: Vec<PathBuf>, to: PathBuf, debug_parse: bool) -> R eprintln!( "Applying {}. patch: `{}`... ", idx + 1, - patch_files[idx].display() + patch_files[idx] + .file_name() + .expect("Has a file name") + .display() ); let mut final_diffs = vec![]; for file_diff in &patch.files { match file_diff { FileDiff::Add { path, .. } => { - if to.join(path).exists() { + if applies_to.join(path).exists() { bail!("A file was added, that already existed. This should be impossible.") } - final_diffs.push(file_diff.to_owned()); - // eprintln!(" > Keeping add of `{}`", path.display()); + if drop_in_applies(&config.drop_ins, owner, &root, path)? { + final_diffs.push(file_diff.to_owned()); + } } FileDiff::Remove { path, .. } => { - if to.join(path).exists() { - final_diffs.push(file_diff.to_owned()); - // eprintln!(" > Keeping remove of `{}`", path.display()); + if applies_to.join(path).exists() { + if drop_in_applies(&config.drop_ins, owner, &root, path)? { + final_diffs.push(file_diff.to_owned()); + } } else { - eprintln!( - " > Dropping remove of `{}`, as the file does not exist in the rollup target.", + drop_msg!( + "Dropping remove of `{}`, as the file does not exist in the rollup target.", path.display() ); } } FileDiff::RenameNoChange { from, to, .. } | FileDiff::RenameWithChange { from, to, .. } => { - if to.join(from).exists() { - if to.join(to).exists() { + if applies_to.join(from).exists() { + if applies_to.join(to).exists() { bail!( "A rename (`{}` -> `{}`) tried to rename a path to an already existing path. This should be impossible.", from.display(), to.display() ) } - final_diffs.push(file_diff.to_owned()); - // eprintln!(" > Keeping rename of `{}` to `{}`", from.display(), to.display()); + if drop_in_applies(&config.drop_ins, owner, &root, to)? { + final_diffs.push(file_diff.to_owned()); + } } else { - eprintln!( - " > Dropping rename from `{}` to `{}`, as the from file does not exist in the rollup target.", + drop_msg!( + "Dropping rename from `{}` to `{}`, as the from file does not exist in the rollup target.", from.display(), to.display() ); } } FileDiff::Change { path, .. } => { - if to.join(path).exists() { - final_diffs.push(file_diff.to_owned()); + if applies_to.join(path).exists() { + if drop_in_applies(&config.drop_ins, owner, &root, path)? { + final_diffs.push(file_diff.to_owned()); + } } else { - eprintln!( - " > Dropping change to `{}`, as the file does not exist in the rollup target.", + drop_msg!( + "Dropping change to `{}`, as the file does not exist in the rollup target.", path.display() ); } @@ -283,15 +356,20 @@ fn apply_patches(patch_files: Vec<PathBuf>, to: PathBuf, debug_parse: bool) -> R ..patch.to_owned() }; - git!(@set_stdin_to filtered_patch; in to; "apply", "-", "--whitespace=nowarn"); - git!(in to; "add", "."); - git!(@set_stdin_to filtered_patch.commit_message; in to; "commit", "--file=-", format!("--date={}", filtered_patch.date).as_str(), "--quiet"); + if dry_run { + eprintln!(" (Dry-run is on; Not applying.)"); + } else { + git!(@set_stdin_to filtered_patch; in applies_to; "apply", "-", "--whitespace=nowarn"); + git!(in applies_to; "add", "."); + git!(@set_stdin_to filtered_patch.commit_message; in applies_to; "commit", "--file=-", format!("--date={}", filtered_patch.date).as_str(), "--quiet"); + } } Ok(()) } -fn check_to(to: &Path) -> Result<()> { +fn check_owner(owner: &Owner) -> Result<()> { + let to = &owner.applies_to; let to = to.join(".git"); // NOTE: We cannot just try to stat() the path, because landlock might be in use and does not @@ -313,3 +391,69 @@ fn check_to(to: &Path) -> Result<()> { Ok(()) } + +fn drop_in_applies( + drop_ins: &HashMap<PathBuf, DropInConfig>, + owner: &Owner, + root: &Path, + original_path: &Path, +) -> Result<bool> { + let path = &original_path.canonicalize()?; + + let file_name = path.file_name().expect("All paths are to files"); + if file_name == OsStr::new(DROP_IN_NAME) || file_name == OsStr::new(MAIN_CONFIG_NAME) { + drop_msg!( + "Not applying change to `{}`, because it's a rollup config or drop in.", + path.display() + ); + + return Ok(false); + } + + if path.parent() == Some(root) { + return Ok(true); + } + + let path = PathBuf::from( + &path + .display() + .to_string() + .strip_prefix(format!("{}/", root.display()).as_str()) + .expect("All paths are in root"), + ); + + if let Some(conf) = drop_ins.get(&path) + && conf.owners.contains(&owner.name) + { + return Ok(true); + } + + let mut parent: &Path = &path; + while let Some(next) = parent.parent() { + // eprintln!("Checking `{}` for drop-ins..", next.display()); + + if let Some(conf) = drop_ins.get(&root.join(next)) { + // eprint!("Found a drop-in for `{}`", next.display()); + if !conf.owners.contains(&owner.name) { + // eprintln!(", but it's for `{}`, so ignoring path.", conf.owner); + drop_msg!( + "Dropping `{}` as `{}` says this dir is for `{}` (but we look for `{}`).", + original_path.display(), + next.join(DROP_IN_NAME).display(), + conf.owners.join(", "), + owner.name + ); + return Ok(false); + } else { + // eprintln!(", which matches."); + return Ok(true); + } + } + parent = next; + } + + bail!( + "There was no drop-in config defining path ownership for: `{}`", + path.display() + ); +} |
