use crate::transaction_git::{GitContext, GitRequest, run_git_checked};
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Change {
pub path: Vec<u8>,
pub old_mode: String,
pub new_mode: String,
pub old_oid: String,
pub new_oid: String,
}
impl Change {
pub fn existed(&self) -> bool {
return self.old_mode != "000000";
}
pub fn exists_after(&self) -> bool {
return self.new_mode != "000000";
}
}
pub fn parse_raw_changes(output: &[u8]) -> Result<Vec<Change>, String> {
let mut changes: Vec<Change> = Vec::new();
let mut fields = output.split(|byte| { return *byte == 0; }).filter(|field| { return !field.is_empty(); });
while let Some(header) = fields.next() {
let text: String = String::from_utf8_lossy(header).into_owned();
let parts: Vec<&str> = text.trim_start_matches(':').split(' ').collect();
let Some(path) = fields.next() else {
return Err(format!("the difference entry {text:?} has no path"));
};
if !header.starts_with(b":") || parts.len() < 5 {
return Err(format!("the difference entry {text:?} is not in the raw format"));
}
changes.push(Change {
path: path.to_vec(),
old_mode: String::from(parts[0]),
new_mode: String::from(parts[1]),
old_oid: String::from(parts[2]),
new_oid: String::from(parts[3]),
});
}
return Ok(changes);
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum WorktreeState {
Missing,
Content(String),
Other,
}
pub fn worktree_path(root: &Path, path: &[u8]) -> PathBuf {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
return root.join(std::ffi::OsStr::from_bytes(path));
}
#[cfg(not(unix))]
{
return root.join(String::from_utf8_lossy(path).into_owned());
}
}
pub fn worktree_state(context: &GitContext, root: &Path, path: &[u8]) -> Result<WorktreeState, String> {
let location: PathBuf = worktree_path(root, path);
let metadata: std::fs::Metadata = match std::fs::symlink_metadata(location.as_path()) {
Ok(found) => found,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(WorktreeState::Missing),
Err(error) if error.kind() == std::io::ErrorKind::NotADirectory => return Ok(WorktreeState::Missing),
Err(error) => return Err(format!("{} could not be read: {error}", location.display())),
};
if metadata.file_type().is_symlink() {
let target: PathBuf = std::fs::read_link(location.as_path())
.map_err(|error| { return format!("{} could not be read: {error}", location.display()); })?;
let mut request: GitRequest = GitRequest::new(root, &["hash-object", "--stdin"]);
request.input = Some(target.as_os_str().as_encoded_bytes().to_vec());
let output = run_git_checked(context, &request).map_err(|error| { return error.to_string(); })?;
return Ok(WorktreeState::Content(String::from_utf8_lossy(&output.stdout).trim().to_owned()));
}
if !metadata.is_file() {
return Ok(WorktreeState::Other);
}
let relative: std::ffi::OsString = worktree_path(Path::new(""), path).into_os_string();
let mut named: std::ffi::OsString = std::ffi::OsString::from("--path=");
named.push(relative.as_os_str());
let mut request: GitRequest = GitRequest::new(root, &["hash-object"]);
request.arguments.push(named);
request.arguments.push(std::ffi::OsString::from("--"));
request.arguments.push(relative);
let output = run_git_checked(context, &request).map_err(|error| { return error.to_string(); })?;
return Ok(WorktreeState::Content(String::from_utf8_lossy(&output.stdout).trim().to_owned()));
}
#[cfg(test)]
#[path = "merge_isolation_changes_tests.rs"]
mod tests;