1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
use super::execute::{TxState, read_file_content};
use crate::ops::patch::{ApplyHunksOptions, apply_patch_with_loader};
use crate::plan::Operation;
/// Execute a patch operation within a transaction.
pub(crate) fn execute_patch_op(op: &Operation, tx: &mut TxState<'_>) -> anyhow::Result<usize> {
match op {
Operation::PatchApply {
diff,
on_stale,
allow_conflicts,
} => {
let options = ApplyHunksOptions {
on_stale: *on_stale,
allow_conflicts: *allow_conflicts,
};
// Conflicts without allow_conflicts surface as ConflictsError from
// apply_patch_with_loader (ops/patch early-err). When allow_conflicts
// is true, content includes conflict markers and is staged normally.
let patched_files = apply_patch_with_loader(
diff,
|path| {
let file_path = tx.cwd.join(path);
Ok(read_file_content(tx.pending, tx.existed_before, &file_path)?.to_string())
},
options,
)?;
for result in patched_files {
if result.is_deletion {
let file_path = tx.cwd.join(&result.path);
// File deletion via patch: mark for deletion.
tx.deletions.insert(file_path.clone());
tx.write_targets.insert(file_path);
} else if let Some(ref from) = result.rename_from {
// Git rename: load was from `from`; stage rename + new content (#2101).
let from_path = tx.cwd.join(from);
let to_path = tx.cwd.join(&result.path);
// Refuse overwrite of an existing dest (parity with file.rename
// without force). Case-only renames still allowed.
let dest_exists = (tx.pending.contains_key(&to_path)
&& !tx.deletions.contains(&to_path))
|| (!tx.deletions.contains(&to_path)
&& crate::ops::file::path_entry_exists(&to_path));
if crate::ops::patch::rename_would_clobber_dest(from, &result.path, dest_exists)
{
return Err(crate::exit::AlreadyExistsError {
msg: crate::ops::patch::rename_dest_exists_msg(&result.path),
}
.into());
}
// Ensure source is in pending (loader already did); record rename
// so commit uses fs::rename then write dest content.
if !tx.existed_before.contains(&from_path)
&& crate::ops::file::path_entry_exists(&from_path)
{
tx.existed_before.insert(from_path.clone());
}
if crate::ops::file::path_entry_exists(&to_path)
&& !tx.existed_before.contains(&to_path)
{
// Dest exists only for case-only renames after the check above.
let _ = read_file_content(tx.pending, tx.existed_before, &to_path)?;
}
tx.renames.push((from_path.clone(), to_path.clone()));
tx.write_file(&to_path, result.content);
// Stage source delete (same as file.rename).
if let Some((original, _)) = tx.pending.get(&from_path) {
let orig = original.clone();
tx.pending.insert(from_path.clone(), (orig, String::new()));
} else {
tx.pending
.insert(from_path.clone(), (String::new(), String::new()));
}
tx.deletions.insert(from_path);
} else {
let file_path = tx.cwd.join(&result.path);
tx.write_file(&file_path, result.content);
}
}
Ok(0)
}
_ => unreachable!("execute_patch_op called with non-Patch operation"),
}
}