Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ clap = { version = "4", features = ["derive"] }
clap-with-warnings = { version = "0.1.4" }
derive_more = { version = "2.0.1", features = ["from", "from_str"] }
lazy_static = "1.4"
rayon = "1.10.0"
regex = "1.4.6"
10 changes: 4 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,16 @@ cargo_check:
cargo $(OUR_CARGO_FLAGS) test --color=always

test_integration:
cargo $(OUR_CARGO_FLAGS) build --quiet
@echo "++ Run tests on test/div"
test/run-test-for-input-dir test/div
OUR_CARGO_BUILD_FLAGS="" test/run-test-for-input-dir test/div
@echo "++ Run tests on test/chj-home"
test/run-test-for-input-dir test/chj-home
OUR_CARGO_BUILD_FLAGS="" test/run-test-for-input-dir test/chj-home

test_integration_opt:
cargo $(OUR_CARGO_FLAGS) build --quiet --release
@echo "++ Run tests on test/div"
SPLIT_PATCH=target/release/split-patch test/run-test-for-input-dir test/div
OUR_CARGO_BUILD_FLAGS="--release" test/run-test-for-input-dir test/div
@echo "++ Run tests on test/chj-home"
SPLIT_PATCH=target/release/split-patch test/run-test-for-input-dir test/chj-home
OUR_CARGO_BUILD_FLAGS="--release" test/run-test-for-input-dir test/chj-home

test: cargo_test test_integration

Expand Down
204 changes: 25 additions & 179 deletions src/bin/split-patch.rs
Original file line number Diff line number Diff line change
@@ -1,194 +1,40 @@
use std::{
ffi::OsStr,
io::{stdout, BufWriter, Write},
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
sync::Arc,
path::PathBuf,
};

use anyhow::{anyhow, Context, Result};
use bstr::{BStr, ByteSlice};
use bumpalo::Bump;
use cj_path_util::temp_file::temp_file_for;
use patchparser::{
from_lines::FromLines,
line::read_lines_in,
make_bstring,
patch::{
diff::Diff,
patch::{Patch, PatchHead},
},
re,
utils::add_suffix,
write_to::WriteTo,
};

use split_patch::split_patch_args::{Args, SplitOptions};

/// Receives the lines for a single diff. Returns the list of files created
fn split_diff_in<'a, 'h>(
head: &'h PatchHead<'a>,
// Guaranteed to be at least the "diff " line
diff: &'a Diff<'a>,
original_path: &Path,
split_options: &SplitOptions,
bump: &'a Bump,
) -> Result<Vec<Arc<Path>>> {
let delete_index_line = true; // XX make configurable
let b_path = diff.diff_path_b()?;

let path = {
let path_in_source_dir = add_suffix(
original_path,
OsStr::from_bytes(&*make_bstring!({ b"-" } + { b_path.replace("/", b"_") })),
)?;
if let Some(output_dir) = &split_options.output_dir {
output_dir.join(
path_in_source_dir
.file_name()
.expect("expect file name to be present as suffix was added"),
)
} else {
path_in_source_dir
}
};
assert_ne!(*path, *original_path);

let head_with_prefix =
|prefix_part: &str| _head_with_prefix(split_options, b_path, head, prefix_part, bump);

if split_options.hunks() {
// Old style sequence numbers, increasing monotonically for
// all files, for when --changes is used with
// --monotonous-numbers
let mut file_i: usize = 0;
let mut written_paths = Vec::new();

macro_rules! diff_with_hunk {
{ $hunk:expr } => {
diff.clone()
.set_hunks(bumpalo::vec![in bump; $hunk], delete_index_line)
}
}

if let Some(differences) = &diff.differences {
for (hunk_i, hunk) in differences.hunks.iter().enumerate() {
if split_options.changes {
for (change_i, change) in hunk.split_into_changes()?.into_iter().enumerate() {
let prefix_part = if split_options.monotonous_numbers {
format!("{file_i:03}")
} else {
format!("{hunk_i:03}-{change_i:03}")
};

let written_path = write_patch_file(
&head_with_prefix(&prefix_part),
diff_with_hunk!(change.to_hunk(bump)),
add_suffix(&path, format!("-{prefix_part}"))?.into(),
)?;

written_paths.push(written_path);
file_i += 1;
}
} else {
let prefix_part = format!("{hunk_i:03}");

let written_path = write_patch_file(
head_with_prefix(&prefix_part),
diff_with_hunk!(hunk.clone()),
add_suffix(&path, format!("-{prefix_part}"))?,
)?;

written_paths.push(written_path);
}
}
} else {
// Simply do not write split versions, OK? -- XX todo:
// should write such files, at least for renames. (Perl
// version doesn't, either.)
}
Ok(written_paths)
} else {
let written_path = write_patch_file(head_with_prefix(""), diff, path)?;

Ok(vec![written_path])
}
}

fn _head_with_prefix<'a, 'h>(
split_options: &SplitOptions,
b_path: &BStr,
head: &'h PatchHead<'a>,
prefix_part: &str,
bump: &'a Bump,
) -> &'h PatchHead<'a>
where
'a: 'h,
{
if split_options.no_subject_change {
head
} else {
let mut head = head.clone();
let prefix = if prefix_part.is_empty() {
make_bstring!({ b_path } + { ": " })
} else {
make_bstring!({ b_path } + { " " } + { prefix_part } + { ": " })
};
head.update_header(
"Subject",
|value| {
if !split_options.no_insert_after_patch {
if let Some(cap) = re!(r"^(\s*\[PATCH\]\s*)(.*)").captures(value) {
return Some(make_bstring!({ &cap[1] } + { &prefix } + { &cap[2] }));
}
}
// Otherwise just simply:
Some(make_bstring!({ &prefix } + { value }))
},
bump,
);
bump.alloc(head)
}
}

fn write_patch_file<'a>(
head: &PatchHead<'a>,
diff: &Diff<'a>,
output_path: PathBuf,
) -> Result<Arc<Path>> {
let mut file = temp_file_for(&*output_path, None)?;
head.write_to(&mut *file)?;
diff.write_to(&mut *file)?;
Ok(file.persist()?)
}

/// Returns the list of files created
fn split_patch(patch_file_path: &Path, split_options: &SplitOptions) -> Result<Vec<Arc<Path>>> {
let bump = Bump::new();
let lines = read_lines_in(patch_file_path, &bump)?.into_bump_slice();
let patch = Patch::from_lines(lines, &bump)?;

// XX consumes patch.diffs; should make it to be OK with & instead
let diffs = patch.diffs.into_bump_slice();

// Write the diffs to individual (separate) files
let mut written = Vec::new();
for (diff_i, diff) in diffs.iter().enumerate() {
let written_paths =
split_diff_in(&patch.head, &diff, patch_file_path, split_options, &bump)
.with_context(|| format!("splitting diff no. {}/{}", diff_i + 1, diffs.len()))?;

written.extend(written_paths);
}

Ok(written)
use clap_with_warnings::clap_with_warnings;

use split_patch::{core::split_patch, split_options::SplitArgs};

/// Split the given patchfile(s) into new files
///
/// So that each new file only contains the part of the patch for
/// one particular target file, or even only one hunk or change.
#[clap_with_warnings]
#[derive(Debug, Clone, clap::Parser)]
#[command(version, about, long_about, allow_hyphen_values = true)]
pub struct Args {
/// Path(s) to patch file(s)
#[clap(required = true)]
pub patch_file: Vec<PathBuf>,

#[clap(flatten)]
pub split_args: SplitArgs,

/// Do not print the list of generated files.
#[clap(short, long)]
pub quiet: bool,
}

fn main() -> Result<()> {
let args = Args::parse();
let split_options = args.split_args.into();

for patch_file in &args.patch_file {
let written = split_patch(&patch_file, &args.split_options)
let written = split_patch(&patch_file, &split_options)
.with_context(|| anyhow!("splitting the patch file {patch_file:?}"))?;

if !args.quiet {
Expand Down
Loading
Loading