Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/target
Cargo.lock

.DS_Store
66 changes: 66 additions & 0 deletions src/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1040,4 +1040,70 @@ mod test {
.join("\n");
insta::assert_snapshot!(relevant_lines);
}

#[test]
fn test_create_new_file() {
// Test creating a new file (original is /dev/null)
let patch = r#"--- /dev/null
+++ b/new_file.txt
@@ -0,0 +1,3 @@
+line 1
+line 2
+line 3
"#;
let diff = Diff::from_str(patch).unwrap();

// Check parsing
assert_eq!(
diff.original(),
None,
"original should be None for /dev/null"
);
assert!(diff.modified().is_some());

// Apply to empty base (since file doesn't exist yet)
let (content, stats) = apply("", &diff).expect("Should apply file creation patch");

assert_eq!(content, "line 1\nline 2\nline 3\n");
assert_eq!(stats.lines_added, 3);
assert_eq!(stats.lines_deleted, 0);
assert!(stats.has_changes());
}

#[test]
fn test_create_file_multi_patch() {
// Test case from user report: multi-file patch that creates new files
// All files have "--- /dev/null" as the original
let base_folder = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("test-data")
.join("create-file");
let patch = std::fs::read_to_string(base_folder.join("create_file.patch")).unwrap();
let diffs = crate::patch::patch_from_str(&patch).unwrap();

// This is a multi-file patch that creates new files
assert_eq!(diffs.len(), 9, "Should parse 9 diffs");

for (i, single_diff) in diffs.iter().enumerate() {
// For file creation, original should be None (from /dev/null)
assert_eq!(
single_diff.original(),
None,
"Diff {} original should be None for file creation",
i
);

// Apply to empty base image (since file doesn't exist)
let (content, stats) =
apply("", single_diff).expect(&format!("Diff {} should apply successfully", i));

// All files should have content (lines added > 0)
assert!(stats.lines_added > 0, "Diff {} should add lines", i);
assert_eq!(stats.lines_deleted, 0, "Diff {} should not delete lines", i);
assert!(!content.is_empty(), "Diff {} should produce content", i);
}

// Snapshot the first file's content
let (content, _) = apply("", &diffs[0]).unwrap();
insta::assert_snapshot!(content);
}
}
35 changes: 31 additions & 4 deletions src/patch/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod format;
mod parse;
pub mod parse_normal;

pub use format::PatchFormatter;
pub use parse::{HunkRangeStrategy, ParsePatchError, ParserConfig};
Expand Down Expand Up @@ -85,8 +86,15 @@ impl<T: AsRef<[u8]> + ToOwned + ?Sized> Diff<'_, T> {
}
}

/// Parse a patch from a string.
///
/// Automatically detects whether the input is in unified or normal diff format.
pub fn patch_from_str(input: &str) -> Result<Patch<'_, str>, ParsePatchError> {
parse::parse_multiple(input)
if parse_normal::is_normal_diff(input) {
parse_normal::parse_normal_multiple(input)
} else {
parse::parse_multiple(input)
}
}

pub fn patch_from_str_with_config(
Expand All @@ -96,8 +104,15 @@ pub fn patch_from_str_with_config(
parse::parse_multiple_with_config(input, config)
}

/// Parse a patch from bytes.
///
/// Automatically detects whether the input is in unified or normal diff format.
pub fn patch_from_bytes(input: &[u8]) -> Result<Patch<'_, [u8]>, ParsePatchError> {
parse::parse_bytes_multiple(input)
if parse_normal::is_normal_diff(input) {
parse_normal::parse_normal_bytes_multiple(input)
} else {
parse::parse_bytes_multiple(input)
}
}

pub fn patch_from_bytes_with_config(
Expand All @@ -110,6 +125,8 @@ pub fn patch_from_bytes_with_config(
impl<'a> Diff<'a, str> {
/// Parse a `Patch` from a string
///
/// Automatically detects whether the input is in unified or normal diff format.
///
/// ```
/// use flickzeug::Diff;
///
Expand All @@ -129,14 +146,24 @@ impl<'a> Diff<'a, str> {
/// ```
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &'a str) -> Result<Diff<'a, str>, ParsePatchError> {
parse::parse(s)
if parse_normal::is_normal_diff(s) {
parse_normal::parse_normal(s)
} else {
parse::parse(s)
}
}
}

impl<'a> Diff<'a, [u8]> {
/// Parse a `Patch` from bytes
///
/// Automatically detects whether the input is in unified or normal diff format.
pub fn from_bytes(s: &'a [u8]) -> Result<Diff<'a, [u8]>, ParsePatchError> {
parse::parse_bytes(s)
if parse_normal::is_normal_diff(s) {
parse_normal::parse_normal_bytes(s)
} else {
parse::parse_bytes(s)
}
}
}

Expand Down
Loading
Loading