summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pkgs/by-name/ro/rollup/src/main.rs58
-rw-r--r--pkgs/by-name/ro/rollup/src/parse.rs405
2 files changed, 293 insertions, 170 deletions
diff --git a/pkgs/by-name/ro/rollup/src/main.rs b/pkgs/by-name/ro/rollup/src/main.rs
index 4083259f..71a54049 100644
--- a/pkgs/by-name/ro/rollup/src/main.rs
+++ b/pkgs/by-name/ro/rollup/src/main.rs
@@ -27,7 +27,7 @@ struct Cli {
}
macro_rules! git {
- (@base for $finalizer:ident in $to:expr; $($args:literal),*) => {
+ (@_base for $finalizer:ident in $to:expr; $($args:expr),*) => {
Command::new("git")
.args([
"-C",
@@ -38,34 +38,38 @@ macro_rules! git {
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.$finalizer()
- .context(concat!("Failed to run `git", $($args),*, "`"))?
+ .with_context(|| format!("Failed to run {}", git!(@_fmt_cmd $($args),*)))?
};
- (@handle_status $status:expr; $($args:literal),*) => {{
- if !$status.success() {
- bail!(concat!("`git", $($args),*, "` failed!"))
+ (@_handle_status $output:expr; $($args:expr),*) => {{
+ if !$output.status.success() {
+ bail!("{} failed!", git!(@_fmt_cmd $($args),*))
}
+ if !$output.stderr.is_empty() {
+ let stderr = String::from_utf8($output.stderr).expect("git should always produce utf8");
+ bail!("{} returned some errors:\n{stderr}", git!(@_fmt_cmd $($args),*));
+ }
+ }};
+ (@_fmt_cmd $($args:expr),*) => {{
+ let all = ["git", $($args),*].join(" ");
+ format!("`{}`", all)
}};
- (@get_output in $to:expr; $($args:literal),*) => {{
- let output = git!(@base for output in $to; $($args),*);
- git!(@handle_status output.status; $($args),*);
+ (in $to:expr; $($args:expr),*) => {{
+ let output = git!(@_base for output in $to; $($args),*);
+ git!(@_handle_status output; $($args),*);
let stdout = String::from_utf8(output.stdout).expect("git should always produce utf8");
let stderr = String::from_utf8(output.stderr).expect("git should always produce utf8");
(stdout, stderr)
}};
- (in $to:expr; $($args:literal),*) => {{
- let status = git!(@base for status in $to; $($args),*);
- git!(@handle_status status; $($args),*);
- }};
- (@set_stdin_to $stdin:expr; in $to:expr; $($args:literal),*) => {{
- let mut child = git!(@base for spawn in $to; $($args),*);
+ (@set_stdin_to $stdin:expr; in $to:expr; $($args:expr),*) => {{
+ let mut child = git!(@_base for spawn in $to; $($args),*);
let mut stdin = child.stdin.take().expect("is piped");
write!(stdin, "{}", $stdin)?;
drop(stdin);
let output = child.wait_with_output()
- .context(concat!("Failed to run `git", $($args),*, "`"))?;
- git!(@handle_status output.status; $($args),*);
+ .with_context(|| format!("Failed to run `{}`", git!(@_fmt_cmd $($args),*)))?;
+ git!(@_handle_status output; $($args),*);
let stdout = String::from_utf8(output.stdout).expect("git should always produce utf8");
let stderr = String::from_utf8(output.stderr).expect("git should always produce utf8");
@@ -85,7 +89,7 @@ fn main() -> Result<()> {
eprintln!("Successfully parsed all {} patch files.", patches.len());
{
- let (stdout, _) = git!(@get_output in args.to; "status", "--porcelain");
+ let (stdout, _) = git!(in args.to; "status", "--porcelain");
if !stdout.is_empty() {
bail!(
"`--to` (`{}`) workspace is dirty, refusing to continue.",
@@ -109,10 +113,12 @@ fn main() -> Result<()> {
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());
}
FileDiff::Remove { path, .. } => {
if args.to.join(path).exists() {
final_diffs.push(file_diff.to_owned());
+ // eprintln!(" > Keeping remove of `{}`", path.display());
} else {
eprintln!(
" > Dropping remove of `{}`, as the file does not exist in the rollup target.",
@@ -120,7 +126,8 @@ fn main() -> Result<()> {
);
}
}
- FileDiff::Rename { from, to, .. } => {
+ FileDiff::RenameNoChange { from, to, .. }
+ | FileDiff::RenameWithChange { from, to, .. } => {
if args.to.join(from).exists() {
if args.to.join(to).exists() {
bail!(
@@ -130,6 +137,7 @@ fn main() -> Result<()> {
)
}
final_diffs.push(file_diff.to_owned());
+ // eprintln!(" > Keeping rename of `{}` to `{}`", from.display(), to.display());
} else {
eprintln!(
" > Dropping rename from `{}` to `{}`, as the from file does not exist in the rollup target.",
@@ -151,17 +159,19 @@ fn main() -> Result<()> {
}
}
+ if final_diffs.is_empty() {
+ eprintln!(" -> Patch is empty after dropping, skipping it.",);
+ continue;
+ }
+
let filtered_patch = Patch {
files: final_diffs,
- commit_message: patch.commit_message.clone(),
+ ..patch.to_owned()
};
- git!(@set_stdin_to filtered_patch; in args.to; "apply", "-");
+ git!(@set_stdin_to filtered_patch; in args.to; "apply", "-", "--whitespace=nowarn");
git!(in args.to; "add", ".");
- let (_, stderr) = git!(@set_stdin_to filtered_patch.commit_message; in args.to; "commit", "--file=-", "--quiet");
- if !stderr.is_empty() {
- bail!("git commit returned some errors: \n{stderr}");
- }
+ git!(@set_stdin_to filtered_patch.commit_message; in args.to; "commit", "--file=-", format!("--date={}", filtered_patch.date).as_str(), "--quiet");
}
Ok(())
diff --git a/pkgs/by-name/ro/rollup/src/parse.rs b/pkgs/by-name/ro/rollup/src/parse.rs
index fb5cd05e..ac30eaa4 100644
--- a/pkgs/by-name/ro/rollup/src/parse.rs
+++ b/pkgs/by-name/ro/rollup/src/parse.rs
@@ -1,16 +1,21 @@
+#![allow(clippy::no_effect)]
+
use core::fmt;
use std::{
fs,
path::{Path, PathBuf},
- process::Command,
};
-use anyhow::{Context, Result};
+use anyhow::{Result, bail};
use pretty_assertions::assert_eq;
+#[derive(Clone)]
pub(crate) struct Patch {
pub(crate) files: Vec<FileDiff>,
pub(crate) commit_message: String,
+ pub(crate) date: String,
+ pub(crate) final_whitespace: &'static str,
+ pub(crate) generating_version: String,
}
impl fmt::Display for Patch {
@@ -18,6 +23,8 @@ impl fmt::Display for Patch {
for file in &self.files {
write!(f, "{}", file)?;
}
+ writeln!(f, "--{}", self.final_whitespace)?;
+ writeln!(f, "{}", self.generating_version)?;
Ok(())
}
@@ -28,67 +35,45 @@ pub(crate) enum FileDiff {
Add {
path: PathBuf,
diff: String,
+ index: String,
+ new_file_mode: String,
},
Remove {
path: PathBuf,
diff: String,
index: String,
- deleted: String,
+ deleted_mode: String,
},
Change {
path: PathBuf,
diff: String,
index: String,
},
- Rename {
+ RenameNoChange {
from: PathBuf,
to: PathBuf,
- diff: String,
- similarity: String,
},
-}
-impl FileDiff {
- fn from_parts(
+ RenameWithChange {
from: PathBuf,
to: PathBuf,
+ similarity: String,
diff: String,
- index: Option<String>,
- deleted: Option<String>,
- similarity: Option<String>,
- ) -> Self {
- let diff = diff[..diff.len() - 1].to_owned();
-
- if from.display().to_string() == "/dev/null" {
- Self::Add { path: to, diff }
- } else if to.display().to_string() == "/dev/null" {
- Self::Remove {
- path: from,
- diff,
- index: index.unwrap(),
- deleted: deleted.unwrap(),
- }
- } else if from == to {
- Self::Change {
- path: from,
- diff,
- index: index.unwrap(),
- }
- } else {
- Self::Rename {
- from,
- to,
- diff,
- similarity: similarity.unwrap(),
- }
- }
- }
+ index: String,
+ },
}
impl fmt::Display for FileDiff {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- FileDiff::Add { path, diff } => {
- // writeln!(f, "diff --git /dev/null b/{}", path.display())?;
+ FileDiff::Add {
+ path,
+ diff,
+ index,
+ new_file_mode,
+ } => {
+ writeln!(f, "diff --git a/{} b/{}", path.display(), path.display())?;
+ writeln!(f, "new file mode {}", new_file_mode)?;
+ writeln!(f, "index {}", index)?;
writeln!(f, "--- /dev/null")?;
writeln!(f, "+++ b/{}", path.display())?;
writeln!(f, "{}", diff)?;
@@ -97,11 +82,12 @@ impl fmt::Display for FileDiff {
path,
diff,
index,
- deleted,
+ deleted_mode,
} => {
writeln!(f, "diff --git a/{} b/{}", path.display(), path.display())?;
- writeln!(f, "deleted {}", deleted)?;
+ writeln!(f, "deleted file mode {}", deleted_mode)?;
writeln!(f, "index {}", index)?;
+ writeln!(f, "--- a/{}", path.display())?;
writeln!(f, "+++ /dev/null")?;
writeln!(f, "{}", diff)?;
}
@@ -112,15 +98,25 @@ impl fmt::Display for FileDiff {
writeln!(f, "+++ b/{}", path.display())?;
writeln!(f, "{}", diff)?;
}
- FileDiff::Rename {
+ FileDiff::RenameNoChange { from, to } => {
+ writeln!(f, "diff --git a/{} b/{}", from.display(), to.display())?;
+ writeln!(f, "similarity index 100%")?;
+ writeln!(f, "rename from {}", from.display())?;
+ writeln!(f, "rename to {}", to.display())?;
+ }
+ FileDiff::RenameWithChange {
from,
to,
- diff,
similarity,
+ diff,
+ index,
} => {
writeln!(f, "diff --git a/{} b/{}", from.display(), to.display())?;
writeln!(f, "similarity index {}", similarity)?;
writeln!(f, "rename from {}", from.display())?;
+ writeln!(f, "rename to {}", to.display())?;
+ writeln!(f, "index {}", index)?;
+ writeln!(f, "--- a/{}", from.display())?;
writeln!(f, "+++ b/{}", to.display())?;
writeln!(f, "{}", diff)?;
}
@@ -155,28 +151,67 @@ macro_rules! expect {
};
}
-pub(crate) fn parse_file(file: &Path, debug: bool) -> Result<Patch> {
- let data = Command::new("filterdiff")
- .arg(file)
- .output()
- .context("Failed to run `filterdiff` for patch analysis")?;
+macro_rules! parser {
+ (
+ $iterator:ident,
+ $debug:ident,
+ $($prefix:literal @ $prefix_var:ident => {$($expr:tt)*};)* $(;)?
+ .. $diff:ident
+ ) => {
+ $(
+ let $prefix_var = {
+ let next = $iterator.next().unwrap();
+ if $debug {
+ eprintln!(" {}", next);
+ }
+ next
+ }.strip_prefix($prefix).unwrap().to_owned();
+ $(
+ $expr
+ )*;
+ )*
+ let $diff = {
+ let mut diff = vec![];
+ let mut iter = $iterator.clone();
+ while let Some(next) = iter.next() {
+ if next.starts_with("diff --git") {
+ break;
+ }
+ if (next == "-- " || next == "--")
+ && iter.peek().map(|v| v.starts_with("2")) == Some(true)
+ {
+ break;
+ }
+ diff.push(next);
+ }
+ diff
+ };
- eprintln!("Started parsing `{}`..", file.display());
+ for _ in 0..$diff.len() {
+ let next = $iterator.next().unwrap();
+ if $debug {
+ eprintln!(" {}", next);
+ }
+ }
+ let $diff = $diff.join("\n");
- let filterdiff_output = String::from_utf8(data.stdout).unwrap();
+ };
+}
+
+pub(crate) fn parse_file(file: &Path, debug: bool) -> Result<Patch> {
if debug {
- eprintln!("{}", filterdiff_output);
- eprintln!("****************************");
+ eprintln!("Started parsing `{}`..", file.display());
}
+ let patch_text = fs::read_to_string(file)?;
- let commit_message = {
- let raw_message = fs::read_to_string(file)?;
-
+ let (commit_message, date, mut cut_idx) = {
let mut commit = None;
let mut date = None;
let mut author = None;
let mut final_message = None;
- for line in raw_message.lines() {
+ let mut finished_headers = false;
+ let mut cut_idx = None;
+ for (idx, line) in patch_text.lines().enumerate() {
expect!(if commit -> "From " from line; assign |strp_line| {
let (id, fake_date) = strp_line.split_once(' ').unwrap();
assert_eq!(fake_date, "Mon Sep 17 00:00:00 2001");
@@ -192,133 +227,211 @@ pub(crate) fn parse_file(file: &Path, debug: bool) -> Result<Patch> {
if strp_line.starts_with('[') {
let chars = strp_line.chars().take_while(|ch| *ch != ']').collect::<String>();
assert_eq!(&chars[1..=5], "PATCH");
- strp_line[chars.len()+1..].to_owned()
+ strp_line[chars.len()+2..].to_owned()
} else {
unreachable!("Email subjects always start with [PATCH x/y]");
}
});
+ if line.starts_with("MIME-Version: ")
+ || line.starts_with("Content-Type: ")
+ || line.starts_with("Content-Transfer-Encoding: ")
+ {
+ continue;
+ }
+
+ if line.starts_with(" ") {
+ // this line is a continuation of the previous header. We just assume
+ // it's the subject header.
+ final_message.as_mut().unwrap().push_str(line);
+ continue;
+ }
+
+ if line.is_empty() {
+ // add the newline to end the subject message
+ final_message.as_mut().unwrap().push('\n');
+ // and one newline to mark the end of the header and the start for more text.
+ final_message.as_mut().unwrap().push('\n');
+ finished_headers = true;
+ continue;
+ }
+
if line == "---" {
+ cut_idx = Some(idx);
break;
}
- final_message.as_mut().unwrap().push_str(line);
- final_message.as_mut().unwrap().push('\n');
+
+ if finished_headers {
+ // If we finished all the headers, the rest must be commit message stuff.
+ // One per line.
+
+ final_message.as_mut().unwrap().push_str(line);
+ final_message.as_mut().unwrap().push('\n');
+ } else {
+ bail!("Failed to recognize commit message header: `{line}`, while parsing patch.")
+ }
}
if debug {
eprintln!("Patch data:");
eprintln!(" Author: {}", author.unwrap());
eprintln!(" Commit: {}", commit.unwrap());
- eprintln!(" Date: {}", date.unwrap());
+ eprintln!(" Date: {}", date.as_ref().unwrap());
for line in final_message.as_ref().unwrap().lines() {
eprintln!(" {}", line);
}
eprintln!("****************************");
}
- final_message.unwrap().trim().to_owned()
+ (
+ final_message.unwrap().trim().to_owned(),
+ date.unwrap().trim().to_owned(),
+ cut_idx.unwrap(),
+ )
};
- let mut diff = Patch {
- files: vec![],
- commit_message,
- };
- let mut fd_from = None;
- let mut fd_to = None;
- let mut fd_diff: Option<String> = None;
- let mut fd_index: Option<String> = None;
- let mut fd_deleted: Option<String> = None;
- let mut fd_similarity: Option<String> = None;
- let mut git_diff = None;
- for (idx, line) in filterdiff_output.lines().enumerate() {
+ let mut patch_files = vec![];
+ let mut final_whitespace = None;
+ let mut generating_version = None;
+ let mut skipping_frontmatter = true;
+ let mut iterator = patch_text
+ .lines()
+ .skip(cut_idx)
+ // skip one more to go over the `---` line
+ .skip(1)
+ .peekable();
+ while let Some(line) = iterator.peek() {
if debug {
eprintln!("{}", line);
}
+ if (line.starts_with(" ") || line.is_empty()) && skipping_frontmatter {
+ iterator.next().unwrap();
+ cut_idx += 1;
+ continue;
+ }
+
if line.starts_with("diff --git") {
- if idx != 0 {
- diff.files.push(FileDiff::from_parts(
- fd_from.take().unwrap(),
- fd_to.take().unwrap(),
- fd_diff.take().unwrap(),
- fd_index.take(),
- fd_deleted.take(),
- fd_similarity.take(),
- ));
+ if skipping_frontmatter {
+ skipping_frontmatter = false;
}
- git_diff = Some(line.to_owned());
+ iterator.next().unwrap();
continue;
}
- if line.starts_with("similarity index") {
- expect!(if fd_similarity -> "similarity index" from line; assign |strp_line| {
- strp_line.to_owned()
- });
- }
- if line.starts_with("rename from") {
- expect!(if fd_from -> "rename from" from line; assign |strp_line| {
- assert_ne!(strp_line, "/dev/null");
- PathBuf::from(strp_line)
- });
- }
- if line.starts_with("deleted") {
- expect!(if fd_deleted -> "deleted" from line; assign |strp_line| {
- strp_line.to_owned()
- });
- }
+ match line {
+ line if line.starts_with("new file mode") => {
+ parser! {
+ iterator, debug,
+ "new file mode " @ mode => {let new_file_mode = mode.to_owned()};
+ "index " @ index => {let index = index.to_owned()};
+ "--- " @ from => {assert_eq!(from, "/dev/null")};
+ "+++ b/" @ to => {let path = PathBuf::from(to)};
+ ..diff
+ }
+ patch_files.push(FileDiff::Add {
+ path,
+ diff,
+ index,
+ new_file_mode,
+ })
+ }
+ line if line.starts_with("index") => {
+ parser! {
+ iterator, debug,
+ "index " @ index => {()};
+ "--- a/" @ from => {()};
+ "+++ b/" @ to => {assert_eq!(from, to); let path = PathBuf::from(to)};
+ ..diff
+ }
+ patch_files.push(FileDiff::Change { path, diff, index })
+ }
+ line if line.starts_with("similarity index 100%") => {
+ parser! {
+ iterator, debug,
+ "similarity index 100%" @ none => {assert!(none.is_empty())};
+ "rename from " @ from => {let from = PathBuf::from(from)};
+ "rename to " @ to => {let to = PathBuf::from(to)};
+ .. no_diff
+ }
+ assert!(no_diff.is_empty());
- if line.starts_with("index") {
- expect!(if fd_index -> "index" from line; assign |strp_line| {
- strp_line.to_owned()
- });
- }
+ patch_files.push(FileDiff::RenameNoChange { from, to })
+ }
+ line if line.starts_with("similarity index ") => {
+ parser! {
+ iterator, debug,
+ "similarity index " @ similarity => {()};
+ "rename from " @ from_1 => {let from = PathBuf::from(&from_1)};
+ "rename to " @ to_1 => {let to = PathBuf::from(&to_1)};
+ "index " @ index => {()};
+ "--- a/" @ from_2 => {assert_eq!(from_1, from_2)};
+ "+++ b/" @ to_2 => {assert_eq!(to_1, to_2)};
+ .. diff
+ }
- if fd_deleted.is_some() {
- let git_diff = git_diff.clone().unwrap();
- expect!(@no_wrap if fd_from -> "diff --git a/" from git_diff; assign |strp_line| {
- let (from, _to) = strp_line.split_once(' ').unwrap();
- PathBuf::from(from)
- });
- } else {
- expect!(if fd_from -> "---" from line; assign |strp_line| {
- if strp_line != "/dev/null" {
- PathBuf::from(strp_line.strip_prefix("a/").unwrap())
- } else {
- PathBuf::from(strp_line)
+ patch_files.push(FileDiff::RenameWithChange {
+ from,
+ to,
+ similarity,
+ diff,
+ index,
+ })
+ }
+ line if line.starts_with("deleted") => {
+ parser! {
+ iterator, debug,
+ "deleted file mode " @ deleted_mode => {()};
+ "index " @ index => {()};
+ "--- a/" @ from => {let path = PathBuf::from(from)};
+ "+++ /dev/null" @ rest => {assert!(rest.is_empty())};
+ ..diff
}
- });
- }
- expect!(if fd_to -> "+++" from line; assign |strp_line| {
- if strp_line != "/dev/null" {
- PathBuf::from(strp_line.strip_prefix("b/").unwrap())
- } else {
- PathBuf::from(strp_line)
+ patch_files.push(FileDiff::Remove {
+ path,
+ diff,
+ index,
+ deleted_mode,
+ })
}
- });
-
- match fd_diff.as_mut() {
- Some(str) => {
- str.push_str(line);
- str.push('\n');
+ line if *line == "--" || *line == "-- " => {
+ final_whitespace = Some( match *line {
+ "-- " => " ",
+ "--" => "",
+ _ => unreachable!(),
+ });
+ iterator.next().unwrap();
+ generating_version = Some(iterator.next().unwrap().to_owned());
+ assert_eq!(iterator.next(), Some(""));
+ assert_eq!(iterator.next(), None);
+ break;
}
- None => {
- let mut base = line.to_owned();
- base.push('\n');
- fd_diff = Some(base);
+ _ => {
+ unreachable!("All possible line starts should be covered, but: `{line}` is not.")
}
}
}
- diff.files.push(FileDiff::from_parts(
- fd_from.take().unwrap(),
- fd_to.take().unwrap(),
- fd_diff.take().unwrap(),
- fd_index.take(),
- fd_deleted.take(),
- fd_similarity.take(),
- ));
- let check_content = diff.to_string();
+ let patch = Patch {
+ files: patch_files,
+ commit_message,
+ date,
+ final_whitespace: final_whitespace.unwrap(),
+ generating_version: generating_version.unwrap(),
+ };
+
+ let patch_text = patch_text
+ .lines()
+ .skip(cut_idx)
+ // skip one more to go over the `---` line
+ .skip(1)
+ .collect::<Vec<_>>()
+ .join("\n");
+ let check_content = patch.to_string();
- assert_eq!(filterdiff_output, check_content);
+ assert_eq!(
+ check_content, patch_text,
+ "Expected parsed patchtext to be the same as our the original patch"
+ );
- Ok(diff)
+ Ok(patch)
}