git_policy_cli/commit/
commit_worktree_records.rs1use crate::candidate_object::CandidateMode;
14use crate::direct_fix_install::InstallChange;
15use crate::path_bytes::os_from_path_bytes;
16use crate::transaction_git::{GitContext, GitFailure, GitOutput, GitRequest, run_git_checked};
17use crate::transaction_journal::AddedPath;
18use crate::worktree_completion::{WorktreeFileState, inspect_worktree_file};
19use std::path::Path;
20
21fn ordinary_git_mode(mode: CandidateMode) -> Option<&'static str> {
31 return match mode {
32 CandidateMode::Regular => Some("100644"),
33 CandidateMode::Executable => Some("100755"),
34 _ => None,
35 };
36}
37
38fn blob_id(context: &GitContext, cwd: &Path, bytes: &[u8]) -> Result<String, GitFailure> {
48 let mut request: GitRequest = GitRequest::new(cwd, &["hash-object", "--stdin"]);
49 request.without_prefix = true;
50 request.input = Some(bytes.to_vec());
51 let output: GitOutput = run_git_checked(context, &request)?;
52 return Ok(String::from_utf8_lossy(output.stdout.as_slice()).trim().to_string());
53}
54
55pub fn settled_entry(
65 context: &GitContext,
66 cwd: &Path,
67 index: &Path,
68 path: &[u8],
69) -> Result<Option<(String, String)>, GitFailure> {
70 let mut pathspec: Vec<u8> = b":(top,literal)".to_vec();
71 pathspec.extend_from_slice(path);
72 let mut request: GitRequest = GitRequest::new(cwd, &["ls-files", "--stage", "-z", "--"]);
73 request.arguments.push(os_from_path_bytes(pathspec.as_slice()));
74 request.without_prefix = true;
75 request.index = Some(index.to_path_buf());
76 let output: GitOutput = run_git_checked(context, &request)?;
77 for record in output.stdout.split(|byte| { return *byte == 0; }) {
78 let Some(tab) = record.iter().position(|byte| { return *byte == b'\t'; }) else {
79 continue;
80 };
81 let metadata: String = String::from_utf8_lossy(&record[..tab]).into_owned();
82 let fields: Vec<&str> = metadata.split(' ').collect();
83 if fields.len() == 3 && fields[2] == "0" {
84 return Ok(Some((String::from(fields[0]), String::from(fields[1]))));
85 }
86 }
87 return Ok(None);
88}
89
90pub fn selected_worktree_records(
101 context: &GitContext,
102 cwd: &Path,
103 repository_root: &Path,
104 index: &Path,
105 changes: &[InstallChange],
106) -> Result<Vec<AddedPath>, GitFailure> {
107 let mut records: Vec<AddedPath> = Vec::new();
108 for change in changes {
109 let Some(git_mode) = ordinary_git_mode(change.mode) else {
110 continue;
111 };
112 let original_oid: String = blob_id(context, cwd, &change.original)?;
113 let Some((entry_mode, entry_oid)) = settled_entry(context, cwd, index, change.path.as_slice())? else {
114 continue;
115 };
116 if entry_mode != git_mode || entry_oid == original_oid {
117 continue;
118 }
119 let destination = repository_root.join(path_from_bytes(change.path.as_slice()));
120 match inspect_worktree_file(destination.as_path(), git_mode, &change.original, &change.replacement) {
121 Ok(WorktreeFileState::Original(_)) => {}
122 Ok(_) => continue,
124 Err(error) => {
125 return Err(GitFailure(format!(
126 "reading {} failed: {error}",
127 destination.display()
128 )));
129 }
130 }
131 records.push(AddedPath {
132 path: change.path.clone(),
133 git_mode: String::from(git_mode),
134 original_oid,
135 intended_oid: entry_oid,
136 });
137 }
138 return Ok(records);
139}
140
141fn path_from_bytes(bytes: &[u8]) -> std::path::PathBuf {
151 return std::path::PathBuf::from(os_from_path_bytes(bytes));
152}
153
154#[cfg(test)]
156#[path = "commit_worktree_records_tests.rs"]
157mod tests;