git_policy_cli/
service_amend.rs1use super::commit_transaction::{TransactionStop, failed};
17use super::service_commit_args::ServiceCommitArgs;
18use super::service_message::{author_overrides, identity};
19use super::transaction_git::{GitContext, GitRequest, run_git};
20use std::path::Path;
21
22#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct AmendSource {
25 pub base: String,
27 pub parents: Vec<String>,
29 pub parent_tree: Option<String>,
31 pub author: String,
33 pub message: String,
35}
36
37#[derive(Clone, Debug, Eq, PartialEq)]
39pub struct StoredCommit {
40 pub parents: Vec<String>,
42 pub author: String,
44 pub message: String,
46}
47
48pub fn parse_stored_commit(raw: &str) -> Option<StoredCommit> {
60 let (header, message) = raw.split_once("\n\n")?;
61 let mut parents: Vec<String> = Vec::new();
62 let mut author: Option<String> = None;
63 let mut in_signature: bool = false;
64 let mut has_tree: bool = false;
65 for line in header.lines() {
66 if in_signature && line.starts_with(' ') {
67 continue;
68 }
69 in_signature = false;
70 if line.starts_with("tree ") {
71 has_tree = true;
72 } else if let Some(parent) = line.strip_prefix("parent ") {
73 parents.push(parent.to_string());
74 } else if let Some(found) = line.strip_prefix("author ") {
75 author = Some(found.to_string());
76 } else if line.starts_with("committer ") {
77 } else if line.starts_with("gpgsig ") || line.starts_with("gpgsig-sha256 ") {
79 in_signature = true;
80 } else {
81 return None;
82 }
83 }
84 if !has_tree {
85 return None;
86 }
87 return Some(StoredCommit { parents, author: author?, message: message.to_string() });
88}
89
90fn query(context: &GitContext, cwd: &Path, arguments: &[&str]) -> Result<Option<String>, TransactionStop> {
92 let output = run_git(context, &GitRequest::new(cwd, arguments)).map_err(|error| { return failed(format!("reading the commit to amend failed: {error:?}")); })?;
93 return Ok(if output.code == Some(0) { String::from_utf8(output.stdout).ok() } else { None });
94}
95
96pub fn read_amend_source(context: &GitContext, cwd: &Path) -> Result<Option<AmendSource>, TransactionStop> {
107 let Some(named) = query(context, cwd, &["rev-parse", "--verify", "--quiet", "HEAD^{commit}"])? else { return Ok(None) };
108 let base: String = named.trim().to_string();
109 let Some(raw) = query(context, cwd, &["cat-file", "commit", base.as_str()])? else { return Ok(None) };
110 let Some(stored) = parse_stored_commit(raw.as_str()) else { return Ok(None) };
111 let parent_tree: Option<String> = match stored.parents.first() {
112 Some(parent) => {
113 let Some(tree) = query(context, cwd, &["rev-parse", "--verify", "--quiet", format!("{parent}^{{tree}}").as_str()])? else {
114 return Ok(None);
115 };
116 Some(tree.trim().to_string())
117 }
118 None => None,
119 };
120 return Ok(Some(AmendSource { base, parents: stored.parents, parent_tree, author: stored.author, message: stored.message }));
121}
122
123fn split_author(line: &str) -> Option<(String, String, String)> {
125 let (name, rest) = line.rsplit_once(" <")?;
126 let (email, date) = rest.split_once("> ")?;
127 return Some((name.to_string(), email.to_string(), date.to_string()));
128}
129
130pub fn amended_author(context: &GitContext, cwd: &Path, source: &AmendSource, parsed: &ServiceCommitArgs) -> Result<Option<String>, TransactionStop> {
142 if parsed.reset_author {
143 return identity(context, cwd, "GIT_AUTHOR_IDENT", &[]);
144 }
145 let replaced: Vec<(&'static str, String)> = author_overrides(parsed);
146 if replaced.is_empty() {
147 return Ok(Some(source.author.clone()));
148 }
149 let Some((name, email, date)) = split_author(source.author.as_str()) else { return Ok(None) };
150 let mut parts: Vec<(&'static str, String)> = vec![("GIT_AUTHOR_NAME", name), ("GIT_AUTHOR_EMAIL", email), ("GIT_AUTHOR_DATE", date)];
151 for (key, value) in replaced {
152 if let Some(slot) = parts.iter_mut().find(|(existing, _)| { return *existing == key; }) {
153 slot.1 = value;
154 }
155 }
156 return identity(context, cwd, "GIT_AUTHOR_IDENT", parts.as_slice());
157}
158
159#[cfg(test)]
161#[path = "service_amend_tests.rs"]
162mod tests;