Skip to content

Commit 56b94e1

Browse files
authored
fix: patch parsing fixes (#1641)
1 parent 8159c83 commit 56b94e1

File tree

4 files changed

+94
-75
lines changed

4 files changed

+94
-75
lines changed

Cargo.lock

Lines changed: 0 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ throbber-widgets-tui = { version = "0.8", optional = true }
133133
tui-input = { version = "0.11.1", optional = true }
134134
reflink-copy = "0.1.26"
135135
rayon = "1.10.0"
136-
gitpatch = "0.7.1"
137136
regex = "1.11.1"
138137
async-recursion = "1.1.1"
139138
opendal = { version = "0.53.1", default-features = false, features = [

py-rattler-build/Cargo.lock

Lines changed: 0 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/source/patch.rs

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,76 @@
11
//! Functions for applying patches to a work directory.
22
use std::{
3-
io::Write,
4-
ops::Deref,
3+
collections::HashSet,
4+
io::{BufRead, BufReader, Write},
55
path::{Path, PathBuf},
66
process::Stdio,
77
};
88

9-
use gitpatch::Patch;
10-
119
use super::SourceError;
1210
use crate::system_tools::{SystemTools, Tool};
1311

12+
fn parse_patch_file<P: AsRef<Path>>(patch_file: P) -> std::io::Result<HashSet<PathBuf>> {
13+
let file = fs_err::File::open(patch_file.as_ref())?;
14+
let reader = BufReader::new(file);
15+
let mut affected_files = HashSet::new();
16+
17+
// Common patch file patterns
18+
let unified_pattern = "--- ";
19+
let git_pattern = "diff --git ";
20+
let traditional_pattern = "Index: ";
21+
let mut is_git = false;
22+
for line in reader.lines() {
23+
let line = line?;
24+
25+
if let Some(git_line) = line.strip_prefix(git_pattern) {
26+
is_git = true;
27+
if let Some(file_path) = extract_git_file_path(git_line) {
28+
affected_files.insert(file_path);
29+
}
30+
} else if let Some(unified_line) = line.strip_prefix(unified_pattern) {
31+
if is_git || unified_line.contains("/dev/null") {
32+
continue;
33+
}
34+
if let Some(file_path) = clean_file_path(unified_line) {
35+
affected_files.insert(file_path);
36+
}
37+
} else if let Some(traditional_line) = line.strip_prefix(traditional_pattern) {
38+
if let Some(file_path) = clean_file_path(traditional_line) {
39+
affected_files.insert(file_path);
40+
}
41+
}
42+
}
43+
44+
Ok(affected_files)
45+
}
46+
47+
fn clean_file_path(path_str: &str) -> Option<PathBuf> {
48+
let path = path_str.trim();
49+
50+
// Handle timestamp in unified diff format (file.txt\t2023-05-10 10:00:00)
51+
let path = path.split('\t').next().unwrap_or(path);
52+
53+
// Skip /dev/null entries
54+
if path.is_empty() || path == "/dev/null" {
55+
return None;
56+
}
57+
58+
Some(PathBuf::from(path))
59+
}
60+
61+
fn extract_git_file_path(content: &str) -> Option<PathBuf> {
62+
// Format: "a/file.txt b/file.txt"
63+
let parts: Vec<&str> = content.split(' ').collect();
64+
if parts.len() >= 2 {
65+
let a_file = parts[0];
66+
if a_file.starts_with("a/") && a_file != "a/dev/null" {
67+
return Some(PathBuf::from(&a_file));
68+
}
69+
}
70+
71+
None
72+
}
73+
1474
/// We try to guess the "strip level" for a patch application. This is done by checking
1575
/// what files are present in the work directory and comparing them to the paths in the patch.
1676
///
@@ -21,21 +81,22 @@ use crate::system_tools::{SystemTools, Tool};
2181
/// But in our work directory, we only have `contents/file.c`. In this case, we can guess that the
2282
/// strip level is 2 and we can apply the patch successfully.
2383
fn guess_strip_level(patch: &Path, work_dir: &Path) -> Result<usize, std::io::Error> {
24-
let text = fs_err::read_to_string(patch)?;
25-
let Ok(patches) = Patch::from_multiple(&text) else {
26-
return Ok(1);
27-
};
84+
let patched_files = parse_patch_file(patch).map_err(|e| {
85+
std::io::Error::new(
86+
std::io::ErrorKind::InvalidData,
87+
format!("Failed to parse patch file: {}", e),
88+
)
89+
})?;
2890

2991
// Try to guess the strip level by checking if the path exists in the work directory
30-
for p in patches {
31-
let path = PathBuf::from(p.old.path.deref());
92+
for file in patched_files {
3293
// This means the patch is creating an entirely new file so we can't guess the strip level
33-
if path == Path::new("/dev/null") {
94+
if file == Path::new("/dev/null") {
3495
continue;
3596
}
36-
for strip_level in 0..path.components().count() {
97+
for strip_level in 0..file.components().count() {
3798
let mut new_path = work_dir.to_path_buf();
38-
new_path.extend(path.components().skip(strip_level));
99+
new_path.extend(file.components().skip(strip_level));
39100
if new_path.exists() {
40101
return Ok(strip_level);
41102
}
@@ -122,7 +183,6 @@ mod tests {
122183
use crate::source::copy_dir::CopyDir;
123184

124185
use super::*;
125-
use gitpatch::Patch;
126186
use line_ending::LineEnding;
127187
use tempfile::TempDir;
128188

@@ -139,13 +199,31 @@ mod tests {
139199
continue;
140200
}
141201

142-
let ps = fs_err::read_to_string(&patch_path).unwrap();
143-
let parsed = Patch::from_multiple(&ps);
202+
let parsed = parse_patch_file(&patch_path);
203+
if let Err(e) = &parsed {
204+
eprintln!("Failed to parse patch: {} {}", patch_path.display(), e);
205+
}
144206

145207
println!("Parsing patch: {} {}", patch_path.display(), parsed.is_ok());
146208
}
147209
}
148210

211+
#[test]
212+
fn get_affected_files() {
213+
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
214+
let patches_dir = manifest_dir.join("test-data/patch_application/patches");
215+
216+
let patched_paths = parse_patch_file(patches_dir.join("test.patch")).unwrap();
217+
assert_eq!(patched_paths.len(), 1);
218+
assert!(patched_paths.contains(&PathBuf::from("a/text.md")));
219+
220+
let patched_paths =
221+
parse_patch_file(patches_dir.join("0001-increase-minimum-cmake-version.patch"))
222+
.unwrap();
223+
assert_eq!(patched_paths.len(), 1);
224+
assert!(patched_paths.contains(&PathBuf::from("a/CMakeLists.txt")));
225+
}
226+
149227
fn setup_patch_test_dir() -> (TempDir, PathBuf) {
150228
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
151229
let patch_test_dir = manifest_dir.join("test-data/patch_application");

0 commit comments

Comments
 (0)