git_policy_cli/merge/
merge_isolation_changes.rs1use crate::transaction_git::{GitContext, GitRequest, run_git_checked};
16use std::path::{Path, PathBuf};
18
19#[derive(Clone, Debug, Eq, PartialEq)]
26pub struct Change {
27 pub path: Vec<u8>,
29 pub old_mode: String,
31 pub new_mode: String,
33 pub old_oid: String,
35 pub new_oid: String,
37}
38
39impl Change {
41 pub fn existed(&self) -> bool {
43 return self.old_mode != "000000";
44 }
45
46 pub fn exists_after(&self) -> bool {
48 return self.new_mode != "000000";
49 }
50}
51
52pub fn parse_raw_changes(output: &[u8]) -> Result<Vec<Change>, String> {
63 let mut changes: Vec<Change> = Vec::new();
64 let mut fields = output.split(|byte| { return *byte == 0; }).filter(|field| { return !field.is_empty(); });
65 while let Some(header) = fields.next() {
66 let text: String = String::from_utf8_lossy(header).into_owned();
67 let parts: Vec<&str> = text.trim_start_matches(':').split(' ').collect();
68 let Some(path) = fields.next() else {
69 return Err(format!("the difference entry {text:?} has no path"));
70 };
71 if !header.starts_with(b":") || parts.len() < 5 {
72 return Err(format!("the difference entry {text:?} is not in the raw format"));
73 }
74 changes.push(Change {
75 path: path.to_vec(),
76 old_mode: String::from(parts[0]),
77 new_mode: String::from(parts[1]),
78 old_oid: String::from(parts[2]),
79 new_oid: String::from(parts[3]),
80 });
81 }
82 return Ok(changes);
83}
84
85#[derive(Clone, Debug, Eq, PartialEq)]
92pub enum WorktreeState {
93 Missing,
95 Content(String),
97 Other,
99}
100
101pub fn worktree_path(root: &Path, path: &[u8]) -> PathBuf {
108 #[cfg(unix)]
109 {
110 use std::os::unix::ffi::OsStrExt;
111 return root.join(std::ffi::OsStr::from_bytes(path));
112 }
113 #[cfg(not(unix))]
115 {
116 return root.join(String::from_utf8_lossy(path).into_owned());
117 }
118}
119
120pub fn worktree_state(context: &GitContext, root: &Path, path: &[u8]) -> Result<WorktreeState, String> {
131 let location: PathBuf = worktree_path(root, path);
132 let metadata: std::fs::Metadata = match std::fs::symlink_metadata(location.as_path()) {
133 Ok(found) => found,
134 Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(WorktreeState::Missing),
135 Err(error) if error.kind() == std::io::ErrorKind::NotADirectory => return Ok(WorktreeState::Missing),
136 Err(error) => return Err(format!("{} could not be read: {error}", location.display())),
137 };
138 if metadata.file_type().is_symlink() {
139 let target: PathBuf = std::fs::read_link(location.as_path())
140 .map_err(|error| { return format!("{} could not be read: {error}", location.display()); })?;
141 let mut request: GitRequest = GitRequest::new(root, &["hash-object", "--stdin"]);
142 request.input = Some(target.as_os_str().as_encoded_bytes().to_vec());
143 let output = run_git_checked(context, &request).map_err(|error| { return error.to_string(); })?;
144 return Ok(WorktreeState::Content(String::from_utf8_lossy(&output.stdout).trim().to_owned()));
145 }
146 if !metadata.is_file() {
147 return Ok(WorktreeState::Other);
148 }
149 let relative: std::ffi::OsString = worktree_path(Path::new(""), path).into_os_string();
151 let mut named: std::ffi::OsString = std::ffi::OsString::from("--path=");
152 named.push(relative.as_os_str());
153 let mut request: GitRequest = GitRequest::new(root, &["hash-object"]);
154 request.arguments.push(named);
155 request.arguments.push(std::ffi::OsString::from("--"));
156 request.arguments.push(relative);
157 let output = run_git_checked(context, &request).map_err(|error| { return error.to_string(); })?;
158 return Ok(WorktreeState::Content(String::from_utf8_lossy(&output.stdout).trim().to_owned()));
159}
160
161#[cfg(test)]
163#[path = "merge_isolation_changes_tests.rs"]
164mod tests;