summaryrefslogtreecommitdiffstats
path: root/pkgs/by-name/ro
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/by-name/ro')
-rw-r--r--pkgs/by-name/ro/rollup/Cargo.lock112
-rw-r--r--pkgs/by-name/ro/rollup/Cargo.toml2
-rw-r--r--pkgs/by-name/ro/rollup/src/config.rs107
-rw-r--r--pkgs/by-name/ro/rollup/src/main.rs254
4 files changed, 418 insertions, 57 deletions
diff --git a/pkgs/by-name/ro/rollup/Cargo.lock b/pkgs/by-name/ro/rollup/Cargo.lock
index 4184cc11..e4994453 100644
--- a/pkgs/by-name/ro/rollup/Cargo.lock
+++ b/pkgs/by-name/ro/rollup/Cargo.lock
@@ -111,12 +111,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
+name = "equivalent"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
+name = "indexmap"
+version = "2.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -163,6 +185,47 @@ dependencies = [
"anyhow",
"clap",
"pretty_assertions",
+ "serde",
+ "toml",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c187e0a369471c550af028cba56d4f4fa2a489ad28151ae95a4d340a2a8b782"
+dependencies = [
+ "serde_core",
]
[[package]]
@@ -173,9 +236,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
-version = "3.0.9"
+version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
+checksum = "4ae9ccbea3a7b0b09443036490c8f34a8b1b760fd345e5c0dae0aac718faea79"
dependencies = [
"proc-macro2",
"quote",
@@ -183,6 +246,45 @@ dependencies = [
]
[[package]]
+name = "toml"
+version = "1.2.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5755c51863cdac711f1956baf8eb9588e060755f7d3c4c979e70ea1e1f32cd7"
+dependencies = [
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.3+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "173f25c883cd2176a2d8212155638c0a60d484a9d84546d270866f40f56b6e49"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.2.0+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "009dbb7700f11c55ef83aebbe4c0e9cf1f1ac0844bd8d6f00dc38f302026e13a"
+
+[[package]]
name = "unicode-ident"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -210,6 +312,12 @@ dependencies = [
]
[[package]]
+name = "winnow"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3b93199df0ab874cdf0c6a94f0a0dc3423fe41f1cafb62b8bd7b1deb277aef"
+
+[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/pkgs/by-name/ro/rollup/Cargo.toml b/pkgs/by-name/ro/rollup/Cargo.toml
index 51fecd59..0ce87401 100644
--- a/pkgs/by-name/ro/rollup/Cargo.toml
+++ b/pkgs/by-name/ro/rollup/Cargo.toml
@@ -19,3 +19,5 @@ homepage = ""
clap = { version = "4.6", features = ["derive"] }
anyhow = "1.0.104"
pretty_assertions = "1.4.1"
+serde = { version = "1.0.229", features = ["derive"] }
+toml = "1.2.1"
diff --git a/pkgs/by-name/ro/rollup/src/config.rs b/pkgs/by-name/ro/rollup/src/config.rs
new file mode 100644
index 00000000..66bd2366
--- /dev/null
+++ b/pkgs/by-name/ro/rollup/src/config.rs
@@ -0,0 +1,107 @@
+use std::{
+ collections::HashMap,
+ ffi::OsStr,
+ fs::{self},
+ path::PathBuf,
+};
+
+use anyhow::{Result, bail};
+use serde::{Deserialize, Serialize};
+
+pub(crate) const MAIN_CONFIG_NAME: &str = "rollup.toml";
+pub(crate) const DROP_IN_NAME: &str = ".rollup.owner";
+
+#[derive(Serialize, Deserialize, Debug)]
+pub(crate) struct MainConfig {
+ pub(crate) owners: Vec<Owner>,
+ pub(crate) drop_ins: HashMap<PathBuf, DropInConfig>,
+}
+
+impl MainConfig {
+ pub(crate) fn get_owner(&self, owner: &str) -> Result<&Owner> {
+ if let Some(own) = self.owners.iter().find(|o| o.name == owner) {
+ Ok(own)
+ } else {
+ bail!("Failed to find owner with name: `{}`", owner)
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub(crate) struct Owner {
+ pub(crate) name: String,
+ pub(crate) applies_to: PathBuf,
+ pub(crate) last_applied_tag: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub(crate) struct DropInConfig {
+ pub(crate) owners: Vec<String>,
+}
+
+pub(crate) fn load(root: &PathBuf) -> Result<MainConfig> {
+ let mut main: MainConfig = {
+ let path = root.join(MAIN_CONFIG_NAME);
+ let content = fs::read_to_string(path)?;
+ toml::from_str(&content)?
+ };
+
+ let drop_ins = collect_drop_ins(root)?;
+
+ for (path, config) in drop_ins {
+ assert!(main.drop_ins.insert(path, config).is_none());
+ }
+
+ Ok(main)
+}
+
+fn collect_drop_ins(path: &PathBuf) -> Result<Vec<(PathBuf, DropInConfig)>> {
+ let mut output = vec![];
+ for entry in fs::read_dir(path)? {
+ let entry = entry?;
+ let ft = entry.file_type()?;
+
+ if ft.is_dir() {
+ output.extend(collect_drop_ins(&entry.path())?);
+ } else if ft.is_file() && entry.file_name() == OsStr::new(DROP_IN_NAME) {
+ let drop_in: toml::Value = {
+ let content = fs::read_to_string(entry.path())?;
+ toml::from_str(&content)?
+ };
+
+ let drop_in_config = DropInConfig {
+ owners: {
+ let Some(owner) = drop_in.get("owner") else {
+ bail!("Drop in config without `owner` field")
+ };
+ if owner.is_str() {
+ vec![owner.as_str().expect("We checked").to_owned()]
+ } else if owner.is_array() {
+ let mut output = vec![];
+ for v in owner.as_array().expect("We checked") {
+ if let Some(v) = v.as_str() {
+ output.push(v.to_owned())
+ } else {
+ bail!("Owner value in arry not string: {}", v);
+ }
+ }
+ output
+ } else {
+ bail!("Owner value in drop in config not string or array")
+ }
+ },
+ };
+
+ output.push((
+ entry
+ .path()
+ .parent()
+ .expect("All drop-ins have a parent")
+ .to_owned(),
+ drop_in_config,
+ ));
+ }
+ }
+
+ Ok(output)
+}
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()
+ );
+}