Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
520ac47
line.rs: add `write_line_to`
pflanze Aug 17, 2026
1c40ebb
line.rs: add `Line::with_changed_contents`
pflanze Aug 19, 2026
91123b3
bumpalo_cow.rs: implement CloneIn for shared references
pflanze Aug 17, 2026
0923421
bumpalo_utils.rs: add `try_split_before_in`
pflanze Aug 18, 2026
7ac6e0b
bumpalo_utils.rs: allow FnMut arguments
pflanze Aug 20, 2026
2e91a65
split-patch: implement --check, --check-only, --ignore-range-errors
pflanze Aug 20, 2026
3322336
test-split-patches-in-dir.rs: use `--check` option
pflanze Aug 21, 2026
de4d3ba
split-patch/test/: fix expected outputs
pflanze Aug 19, 2026
9189f33
split_options.rs: add an explicit `--dry-run` option
pflanze Aug 20, 2026
61c649b
split-options: add --regenerate option
pflanze Aug 20, 2026
36c4576
test-split-patches-in-dir.rs: test `--regenerate`
pflanze Aug 20, 2026
2a227fa
split-patch/test/: add outputs for the `--regenerate` test
pflanze Aug 20, 2026
5712533
patchparser: do not make "@@ " part of the `head_post` field
pflanze Aug 21, 2026
ffa210b
patchparser: add type CheckErrorHandler for the `handle_check_error` API
pflanze Aug 21, 2026
8dd6854
patchparser: replace CheckErrorHandler with trait HandleCheckError
pflanze Aug 21, 2026
d57446f
Remove some superfluous lifetime names
pflanze Aug 21, 2026
66ad061
Some fixes via clippy
pflanze Aug 21, 2026
6cddb25
split-patch: add -r as alias for --regenerate
pflanze Aug 21, 2026
dd8db14
split-patch: change --regenerate to imply --ignore-range-errors
pflanze Aug 21, 2026
d869945
split-patch: warn about somewhat non-sensible option combination
pflanze Aug 21, 2026
95151fb
docs: various improvements
yusufraji Sep 3, 2026
add5181
line.rs: add From implementations
yusufraji Sep 3, 2026
4f60a9d
patchparser: patch.rs: implement WriteTo
yusufraji Sep 3, 2026
0bbeec3
patchparser: remove the last two instances of owned datastructure in …
yusufraji Sep 3, 2026
efc1375
patchparser: add a first example
yusufraji Sep 3, 2026
4164ac3
Makefile: add examples to the targets
yusufraji Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ check_formatting: fmt
cargo_test:
@echo "++ Run cargo $(OUR_CARGO_FLAGS) test on both crates"
( cd patchparser && cargo $(OUR_CARGO_FLAGS) test )
( cd patchparser && cargo $(OUR_CARGO_FLAGS) test --examples )
( cd split-patch && cargo $(OUR_CARGO_FLAGS) test )

cargo_check:
( cd patchparser && cargo $(OUR_CARGO_FLAGS) test --color=always )
( cd patchparser && cargo $(OUR_CARGO_FLAGS) build --examples --color=always )
( cd split-patch && cargo $(OUR_CARGO_FLAGS) test --color=always )

# This target is to abstract running clippy on everything (and can be run manually)
clippy:
( cd patchparser && cargo clippy --color=always --all-targets --all-features $(CLIPPY_ARGS) )
( cd patchparser && cargo clippy --color=always --all-targets --all-features --examples $(CLIPPY_ARGS) )
( cd split-patch && cargo clippy --color=always --all-targets --all-features $(CLIPPY_ARGS) )

# This is for use in CI.
Expand Down
88 changes: 88 additions & 0 deletions patchparser/examples/generate_patch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use std::io::{stdout, Write};

use anyhow::Result;
use bumpalo::Bump;
use patchparser::{
patch::{
change::Change,
diff::{Diff, DiffDifferences},
hunk::Hunk,
parsed_hunk::{MinimalHunkHead, ParsedHunk},
patch::{Patch, PatchHead, PatchHeadHeader},
},
write_to::WriteTo,
};

fn main() -> Result<()> {
let hunk = ParsedHunk {
head: MinimalHunkHead {
orig_start: 1,
patched_start: 4,
head_post: Some("Head".as_ref()),
},
changes: &[
&Change {
post_from_previous_change: &[],
pre: &["hello".into(), "world".into()],
minus: &["foo".into(), "bar".into()],
plus: &["food".into()],
post: &["some".into(), "post".into(), "lines".into()],
backslash: false,
},
&Change {
post_from_previous_change: &[],
pre: &["a new pre line".into()],
minus: &["ome".into(), "thing".into()],
plus: &["".into()],
post: &["some".into(), "post".into(), "lines".into()],
backslash: false,
},
],
};

let mut out = stdout().lock();
hunk.write_to(&mut out)?;

writeln!(&mut out, "--------")?;
let bump = &Bump::new();
for (idx, hunk) in hunk.split_by_change(bump).iter().enumerate() {
let diff_path_a = format!("a/foo-{idx}");
let diff = Diff {
diff_line: "diff -- blabla".into(),
diff_path_a_full: Some(diff_path_a.as_str().as_ref()),
diff_path_b_full: Some(
bumpalo::format!(in bump, "b/foo-{idx}",)
.into_bump_str()
.as_ref(),
),
newfile_line: None,
deleted_line: None,
similarity_line: None,
rename_from_line: None,
rename_to_line: None,
differences: Some(DiffDifferences {
index_line: Some("index line".into()),
minus_line: "minus line".into(),
plus_line: "plus line".into(),
hunks: &[Hunk::Parsed(hunk.clone())],
}),
};

// let patch = Patch::from_lines(lines, bump, parse_mode, handle_check_error)?;
let patch = Patch {
head: &PatchHead {
header: Some(&PatchHeadHeader {
from_line: "From ...".into(),
header_lines: &["Author: bla".into()],
}),
remaining_lines: &["Hey there!".into(), "".into()],
},
diffs: bump.alloc([diff]),
footer: &[],
};

patch.write_to(&mut out)?;
out.flush()?;
}
Ok(())
}
7 changes: 4 additions & 3 deletions patchparser/src/anyhow_once.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use anyhow::anyhow;
/// generic error
///
/// This is a work-around for the issue with lazy wrappers that errors
/// must be stored, but anyhow::Error does not implement Clone, and
/// Arc<anyhow::Error> is not anyhow-compatible (i.e. cannot be used
/// with .context)
/// must be stored, but `anyhow::Error` does not implement `Clone`,
/// and `Arc<anyhow::Error>` is not anyhow-compatible (i.e. cannot be
/// used with .context)
pub struct AnyhowOnce(Mutex<Option<anyhow::Error>>);

impl From<anyhow::Error> for AnyhowOnce {
Expand All @@ -18,6 +18,7 @@ impl From<anyhow::Error> for AnyhowOnce {
}

impl AnyhowOnce {
/// Remove the error, or panics if called a second time.
pub fn take(&self) -> anyhow::Error {
let mut guard = self.0.lock().expect("no panics");
if let Some(e) = guard.take() {
Expand Down
1 change: 1 addition & 0 deletions patchparser/src/bumpalo_bstring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ impl<'bump> BString<'bump> {
Self(Vec::new_in(bump))
}

#[must_use]
pub fn into_bump_slice(self) -> &'bump BStr {
self.0.into_bump_slice().as_ref()
}
Expand Down
9 changes: 8 additions & 1 deletion patchparser/src/bumpalo_cow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,20 @@ pub trait ToOwnedIn<'b> {

// Also see `ReborrowIn`
pub trait CloneIn<'b>: Sized {
#[must_use]
fn clone_in(&self, bump: &'b Bump) -> Self;

fn clone_from_in(&mut self, source: &Self, bump: &'b Bump)
// where
// Self: ~const Destruct,
{
*self = source.clone_in(bump)
*self = source.clone_in(bump);
}
}

impl<'a, T> CloneIn<'a> for &'a T {
fn clone_in(&self, _bump: &'a Bump) -> Self {
self
}
}

Expand Down
27 changes: 27 additions & 0 deletions patchparser/src/bumpalo_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,30 @@ fn t_split_before() {
[vec!["@a"], vec!["@b"], vec!["@c", "d"], vec!["@e"],]
);
}

/// Variant of `split_before_in` that stops on errors
pub fn try_split_before_in<'a, 'b, T, G, E>(
items: &'a [T],
mut is_boundary: impl FnMut(&'a T) -> bool,
mut group_constructor: impl FnMut(&'a [T]) -> Result<G, E>,
bump: &'b Bump,
) -> Result<bc::Vec<'b, G>, E> {
let mut finish_group = |groups: &mut bc::Vec<G>, current_group: &'a [T]| -> Result<(), E> {
if !current_group.is_empty() {
groups.push(group_constructor(current_group)?);
}
Ok(())
};

let mut groups = bc::Vec::new_in(bump);
let mut current_group_start = 0;
for (i, item) in items.iter().enumerate() {
if is_boundary(item) {
finish_group(&mut groups, &items[current_group_start..i])?;
current_group_start = i;
}
}
finish_group(&mut groups, &items[current_group_start..])?;

Ok(groups)
}
4 changes: 2 additions & 2 deletions patchparser/src/format_binary.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! A bit of a hack to make creating BString instances easier, from a
//! A bit of a hack to make creating `BString` instances easier, from a
//! mix of byte sequences and Display and Debug based format strings.
//!
//! Does not allocate, although it does use some indirections (could
Expand Down Expand Up @@ -130,7 +130,7 @@ macro_rules! ___make_bstring {
/// Usage: use `+` to join segments, each of which can either be a
/// format string instance in round parens (which can only deal with
/// proper strings), or between curly braces any expression that
/// evaluates to a byte slice / vector or BStr / BString or normal
/// evaluates to a byte slice / vector or `BStr` / `BString` or normal
/// string, which is then added directly as bytes.
///
/// See example in the module docs.
Expand Down
7 changes: 6 additions & 1 deletion patchparser/src/from_lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ use bumpalo::Bump;

use crate::line::Line;

/// This trait is somewhat obsolete, because often more information is
/// needed (e.g. for how to check parsing inconsistencies), thus other
/// `from_lines` methods are implemented outside the trait now; also
/// it was originally for converting stringified representations
/// automatically back, but that has been removed.
pub trait FromLines<'t>: Sized {
fn from_lines(lines: &'t [Line<'t>], bump: &'t Bump) -> Result<Self, anyhow::Error>;
fn from_lines(lines: &'t [Line<'t>], bump: &'t Bump) -> Result<&'t mut Self, anyhow::Error>;
}
33 changes: 30 additions & 3 deletions patchparser/src/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ impl<'a> Display for Line<'a> {
}
}

impl<'a> From<&'a str> for Line<'a> {
fn from(value: &'a str) -> Self {
Self::from_generated_content(value.into())
}
}

impl<'a> From<&'a BStr> for Line<'a> {
fn from(value: &'a BStr) -> Self {
Self::from_generated_content(value)
}
}

impl<'a> Line<'a> {
pub fn from_lineno0_bstr(line_no0: usize, contents: &'a BStr) -> Self {
Self { line_no0, contents }
Expand Down Expand Up @@ -88,6 +100,16 @@ impl<'a> Line<'a> {
self.line_no0 = usize::MAX;
}

/// Keep the original line number; only use for slicing, not new
/// content, or errors would be confusing.
#[must_use]
pub fn with_changed_contents<'b>(&self, contents: &'b BStr) -> Line<'b> {
Line {
line_no0: self.line_no0,
contents,
}
}

/// 0-based line number; None if the line was generated (has no location)
pub fn line_no0(&self) -> Option<usize> {
if self.line_no0 == usize::MAX {
Expand All @@ -103,22 +125,27 @@ impl<'a> Line<'a> {
}
}

/// Write the line string out with line ending added
pub fn write_line_to<'a>(line: &Line<'a>, mut out: impl Write) -> Result<(), std::io::Error> {
out.write_all(line)?;
out.write_all(b"\n")
}

/// Write the line strings out with line endings added
pub fn write_lines_to<'a>(
lines: impl IntoIterator<Item = &'a Line<'a>>,
mut out: impl Write,
) -> Result<(), std::io::Error> {
for line in lines {
out.write_all(line)?;
out.write_all(b"\n")?;
write_line_to(line, &mut out)?;
}
Ok(())
}

pub fn read_in<'b, P: AsRef<Path>>(path: P, bump: &'b Bump) -> Result<bc::Vec<'b, u8>> {
let mut input = std::fs::File::open(path).context("opening file for reading")?;
let len = input.metadata()?.len();
let len_usize = usize::try_from(len).expect("file is too large");
let len_usize = usize::try_from(len).context("file is too large")?;
let mut contents = bc::Vec::<u8>::with_capacity_in(len_usize, bump);
unsafe {
// Safe because we'll never read from the bytes unless they
Expand Down
Loading
Loading