git_policy_cli/
manual_push_lifecycle.rs1use super::auto_push_single_flight::PushGit;
15use super::candidate_object::parse_object_id;
16use super::candidate_prediction::PushedScope;
17use super::manual_push_probe::{PushUpdate, probe_manual_push_updates};
18use super::manual_push_range::{
19 ManualPushRangeError, UpdateScope, peel_commits, published_tips, tracking_commits, update_scope,
20};
21use std::collections::BTreeMap;
23use std::ffi::OsString;
25
26#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct ManualPushFailure {
37 pub message: String,
39}
40
41impl std::fmt::Display for ManualPushFailure {
42 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 return formatter.write_str(&self.message);
45 }
46}
47
48impl std::error::Error for ManualPushFailure {}
49
50fn from_probe(error: &super::manual_push_probe::ManualPushProbeError) -> ManualPushFailure {
57 return ManualPushFailure {
58 message: error.message.clone(),
59 };
60}
61
62fn from_range(error: &ManualPushRangeError) -> ManualPushFailure {
69 return ManualPushFailure {
70 message: error.message.clone(),
71 };
72}
73
74pub fn scopes_of_updates(
84 git: &PushGit,
85 updates: &[PushUpdate],
86) -> Result<Vec<UpdateScope>, ManualPushFailure> {
87 let remote_names: Vec<String> = updates.iter().fold(Vec::new(), |mut names, update| {
88 if !names.contains(&update.remote_name) {
89 names.push(update.remote_name.clone());
90 }
91 return names;
92 });
93 let tracking: BTreeMap<String, Vec<String>> =
94 tracking_commits(git, &remote_names).map_err(|error| {
95 return from_range(&error);
96 })?;
97 let prior_oids: Vec<String> = updates
98 .iter()
99 .filter_map(|update| {
100 return update.remote_oid.clone();
101 })
102 .collect();
103 let prior: BTreeMap<String, String> = peel_commits(git, &prior_oids).map_err(|error| {
104 return from_range(&error);
105 })?;
106 let tips: BTreeMap<String, Vec<String>> = published_tips(updates, &prior, &tracking);
107 let mut scopes: Vec<UpdateScope> = Vec::new();
108 for update in updates {
109 let published: Vec<String> = tips.get(&update.remote_name).cloned().unwrap_or_default();
110 scopes.push(update_scope(git, update, &published).map_err(|error| {
111 return from_range(&error);
112 })?);
113 }
114 return Ok(scopes);
115}
116
117pub fn pushed_scope(scopes: &[UpdateScope]) -> Result<PushedScope, ManualPushFailure> {
127 let mut commits: Vec<super::candidate_object::ObjectId> = Vec::new();
128 let mut trees: Vec<super::candidate_object::ObjectId> = Vec::new();
129 for scope in scopes {
130 match scope {
131 UpdateScope::None => {}
132 UpdateScope::Commits(names) => {
133 for name in names {
134 let oid = parse_object_id(name.as_bytes()).ok_or_else(|| {
135 return ManualPushFailure {
136 message: format!(
137 "Git named a commit that is not an object name: {name}"
138 ),
139 };
140 })?;
141 if !commits.contains(&oid) {
142 commits.push(oid);
143 }
144 }
145 }
146 UpdateScope::FinalTree(tip) => {
147 let oid = parse_object_id(tip.as_bytes()).ok_or_else(|| {
148 return ManualPushFailure {
149 message: format!("Git named a commit that is not an object name: {tip}"),
150 };
151 })?;
152 if !trees.contains(&oid) {
153 trees.push(oid);
154 }
155 }
156 }
157 }
158 return Ok(PushedScope { commits, trees });
159}
160
161pub fn manual_push_scope(
173 git: &PushGit,
174 global_prefix: &[OsString],
175 post_subcommand: &[OsString],
176) -> Result<PushedScope, ManualPushFailure> {
177 let updates =
178 probe_manual_push_updates(git, global_prefix, post_subcommand).map_err(|error| {
179 return from_probe(&error);
180 })?;
181 let scopes = scopes_of_updates(git, &updates)?;
182 return pushed_scope(&scopes);
183}
184
185#[cfg(test)]
187#[path = "manual_push_lifecycle_tests.rs"]
188mod tests;