| 485 | } |
| 486 | |
| 487 | fn parse_apply_patch(input: &str) -> Result<Vec<PatchHunk>> { |
| 488 | let lines: Vec<&str> = input.lines().collect(); |
| 489 | |
| 490 | let start = lines |
| 491 | .iter() |
| 492 | .position(|l| l.trim() == "*** Begin Patch") |
| 493 | .ok_or_else(|| anyhow::anyhow!("Patch must contain *** Begin Patch"))?; |
| 494 | |
| 495 | let mut hunks = Vec::new(); |
| 496 | let mut i = start + 1; |
| 497 | |
| 498 | while i < lines.len() { |
| 499 | let line = lines[i].trim_end(); |
| 500 | if line.trim() == "*** End Patch" { |
| 501 | break; |
| 502 | } |
| 503 | |
| 504 | if let Some(path) = line.strip_prefix("*** Add File: ") { |
| 505 | let path = path.trim().to_string(); |
| 506 | i += 1; |
| 507 | let mut contents = String::new(); |
| 508 | while i < lines.len() { |
| 509 | let current = lines[i]; |
| 510 | if current.starts_with("*** ") { |
| 511 | break; |
| 512 | } |
| 513 | if let Some(added) = current.strip_prefix('+') { |
| 514 | contents.push_str(added); |
| 515 | contents.push('\n'); |
| 516 | } |
| 517 | i += 1; |
| 518 | } |
| 519 | hunks.push(PatchHunk::AddFile { path, contents }); |
| 520 | continue; |
| 521 | } |
| 522 | |
| 523 | if let Some(path) = line.strip_prefix("*** Delete File: ") { |
| 524 | hunks.push(PatchHunk::DeleteFile { |
| 525 | path: path.trim().to_string(), |
| 526 | }); |
| 527 | i += 1; |
| 528 | continue; |
| 529 | } |
| 530 | |
| 531 | if let Some(path) = line.strip_prefix("*** Update File: ") { |
| 532 | let path = path.trim().to_string(); |
| 533 | i += 1; |
| 534 | |
| 535 | let mut move_to = None; |
| 536 | if i < lines.len() |
| 537 | && let Some(target) = lines[i].trim_end().strip_prefix("*** Move to: ") |
| 538 | { |
| 539 | move_to = Some(target.trim().to_string()); |
| 540 | i += 1; |
| 541 | } |
| 542 | |
| 543 | let mut chunks = Vec::new(); |
| 544 | let mut is_first_chunk = true; |