use crate::candidate_object::CandidateMode;
use crate::direct_fix_install::InstallChange;
use crate::path_bytes::os_from_path_bytes;
use crate::transaction_git::{GitContext, GitFailure, GitOutput, GitRequest, run_git_checked};
use crate::transaction_journal::AddedPath;
use crate::worktree_completion::{WorktreeFileState, inspect_worktree_file};
use std::path::Path;
fn ordinary_git_mode(mode: CandidateMode) -> Option<&'static str> {
return match mode {
CandidateMode::Regular => Some("100644"),
CandidateMode::Executable => Some("100755"),
_ => None,
};
}
fn blob_id(context: &GitContext, cwd: &Path, bytes: &[u8]) -> Result<String, GitFailure> {
let mut request: GitRequest = GitRequest::new(cwd, &["hash-object", "--stdin"]);
request.without_prefix = true;
request.input = Some(bytes.to_vec());
let output: GitOutput = run_git_checked(context, &request)?;
return Ok(String::from_utf8_lossy(output.stdout.as_slice()).trim().to_string());
}
pub fn settled_entry(
context: &GitContext,
cwd: &Path,
index: &Path,
path: &[u8],
) -> Result<Option<(String, String)>, GitFailure> {
let mut pathspec: Vec<u8> = b":(top,literal)".to_vec();
pathspec.extend_from_slice(path);
let mut request: GitRequest = GitRequest::new(cwd, &["ls-files", "--stage", "-z", "--"]);
request.arguments.push(os_from_path_bytes(pathspec.as_slice()));
request.without_prefix = true;
request.index = Some(index.to_path_buf());
let output: GitOutput = run_git_checked(context, &request)?;
for record in output.stdout.split(|byte| { return *byte == 0; }) {
let Some(tab) = record.iter().position(|byte| { return *byte == b'\t'; }) else {
continue;
};
let metadata: String = String::from_utf8_lossy(&record[..tab]).into_owned();
let fields: Vec<&str> = metadata.split(' ').collect();
if fields.len() == 3 && fields[2] == "0" {
return Ok(Some((String::from(fields[0]), String::from(fields[1]))));
}
}
return Ok(None);
}
pub fn selected_worktree_records(
context: &GitContext,
cwd: &Path,
repository_root: &Path,
index: &Path,
changes: &[InstallChange],
) -> Result<Vec<AddedPath>, GitFailure> {
let mut records: Vec<AddedPath> = Vec::new();
for change in changes {
let Some(git_mode) = ordinary_git_mode(change.mode) else {
continue;
};
let original_oid: String = blob_id(context, cwd, &change.original)?;
let Some((entry_mode, entry_oid)) = settled_entry(context, cwd, index, change.path.as_slice())? else {
continue;
};
if entry_mode != git_mode || entry_oid == original_oid {
continue;
}
let destination = repository_root.join(path_from_bytes(change.path.as_slice()));
match inspect_worktree_file(destination.as_path(), git_mode, &change.original, &change.replacement) {
Ok(WorktreeFileState::Original(_)) => {}
Ok(_) => continue,
Err(error) => {
return Err(GitFailure(format!(
"reading {} failed: {error}",
destination.display()
)));
}
}
records.push(AddedPath {
path: change.path.clone(),
git_mode: String::from(git_mode),
original_oid,
intended_oid: entry_oid,
});
}
return Ok(records);
}
fn path_from_bytes(bytes: &[u8]) -> std::path::PathBuf {
return std::path::PathBuf::from(os_from_path_bytes(bytes));
}
#[cfg(test)]
#[path = "commit_worktree_records_tests.rs"]
mod tests;