1#![expect(missing_docs)]
16
17use std::cmp::Ordering;
18use std::fmt::Debug;
19use std::fmt::Error;
20use std::fmt::Formatter;
21use std::hash::Hash;
22use std::hash::Hasher;
23use std::sync::Arc;
24
25use futures::future::try_join_all;
26use itertools::Itertools as _;
27use pollster::FutureExt as _;
28
29use crate::backend;
30use crate::backend::BackendError;
31use crate::backend::BackendResult;
32use crate::backend::ChangeId;
33use crate::backend::CommitId;
34use crate::backend::Signature;
35use crate::backend::TreeId;
36use crate::index::IndexResult;
37use crate::merge::Merge;
38use crate::merged_tree::MergedTree;
39use crate::repo::Repo;
40use crate::rewrite::merge_commit_trees;
41use crate::signing::SignResult;
42use crate::signing::Verification;
43use crate::store::Store;
44
45#[derive(Clone, serde::Serialize)]
46pub struct Commit {
47 #[serde(skip)]
48 store: Arc<Store>,
49 #[serde(rename = "commit_id")]
50 id: CommitId,
51 #[serde(flatten)]
52 data: Arc<backend::Commit>,
53}
54
55impl Debug for Commit {
56 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
57 f.debug_struct("Commit").field("id", &self.id).finish()
58 }
64}
65
66impl PartialEq for Commit {
67 fn eq(&self, other: &Self) -> bool {
68 self.id == other.id
69 }
70}
71
72impl Eq for Commit {}
73
74impl Ord for Commit {
75 fn cmp(&self, other: &Self) -> Ordering {
76 self.id.cmp(&other.id)
77 }
78}
79
80impl PartialOrd for Commit {
81 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
82 Some(self.cmp(other))
83 }
84}
85
86impl Hash for Commit {
87 fn hash<H: Hasher>(&self, state: &mut H) {
88 self.id.hash(state);
89 }
90}
91
92impl Commit {
93 pub fn new(store: Arc<Store>, id: CommitId, data: Arc<backend::Commit>) -> Self {
94 Self { store, id, data }
95 }
96
97 pub fn store(&self) -> &Arc<Store> {
98 &self.store
99 }
100
101 pub fn id(&self) -> &CommitId {
102 &self.id
103 }
104
105 pub fn parent_ids(&self) -> &[CommitId] {
106 &self.data.parents
107 }
108
109 pub fn parents(&self) -> impl Iterator<Item = BackendResult<Self>> {
110 self.data.parents.iter().map(|id| self.store.get_commit(id))
111 }
112
113 pub async fn parents_async(&self) -> BackendResult<Vec<Self>> {
114 try_join_all(
115 self.data
116 .parents
117 .iter()
118 .map(|id| self.store.get_commit_async(id)),
119 )
120 .await
121 }
122
123 pub fn tree(&self) -> MergedTree {
124 MergedTree::new(self.store.clone(), self.data.root_tree.clone())
125 }
126
127 pub fn tree_ids(&self) -> &Merge<TreeId> {
128 &self.data.root_tree
129 }
130
131 pub fn parent_tree(&self, repo: &dyn Repo) -> BackendResult<MergedTree> {
132 self.parent_tree_async(repo).block_on()
133 }
134
135 pub async fn parent_tree_async(&self, repo: &dyn Repo) -> BackendResult<MergedTree> {
138 if is_commit_empty_by_index(repo, &self.id)? == Some(true) {
142 return Ok(self.tree());
143 }
144 let parents: Vec<_> = self.parents_async().await?;
145 merge_commit_trees(repo, &parents).await
146 }
147
148 pub fn is_empty(&self, repo: &dyn Repo) -> BackendResult<bool> {
151 if let Some(empty) = is_commit_empty_by_index(repo, &self.id)? {
152 return Ok(empty);
153 }
154 is_backend_commit_empty(repo, &self.store, &self.data)
155 }
156
157 pub fn has_conflict(&self) -> bool {
158 !self.tree_ids().is_resolved()
159 }
160
161 pub fn change_id(&self) -> &ChangeId {
162 &self.data.change_id
163 }
164
165 pub fn store_commit(&self) -> &Arc<backend::Commit> {
166 &self.data
167 }
168
169 pub fn description(&self) -> &str {
170 &self.data.description
171 }
172
173 pub fn author(&self) -> &Signature {
174 &self.data.author
175 }
176
177 pub fn committer(&self) -> &Signature {
178 &self.data.committer
179 }
180
181 pub fn is_hidden(&self, repo: &dyn Repo) -> IndexResult<bool> {
183 let maybe_entries = repo.resolve_change_id(self.change_id())?;
184 Ok(maybe_entries.is_none_or(|entries| !entries.contains(&self.id)))
185 }
186
187 pub fn is_discardable(&self, repo: &dyn Repo) -> BackendResult<bool> {
190 Ok(self.description().is_empty() && self.is_empty(repo)?)
191 }
192
193 pub fn is_signed(&self) -> bool {
195 self.data.secure_sig.is_some()
196 }
197
198 pub fn verification(&self) -> SignResult<Option<Verification>> {
200 self.data
201 .secure_sig
202 .as_ref()
203 .map(|sig| self.store.signer().verify(&self.id, &sig.data, &sig.sig))
204 .transpose()
205 }
206}
207
208pub(crate) fn is_backend_commit_empty(
209 repo: &dyn Repo,
210 store: &Arc<Store>,
211 commit: &backend::Commit,
212) -> BackendResult<bool> {
213 if let [parent_id] = &*commit.parents {
214 return Ok(commit.root_tree == *store.get_commit(parent_id)?.tree_ids());
215 }
216 let parents: Vec<_> = commit
217 .parents
218 .iter()
219 .map(|id| store.get_commit(id))
220 .try_collect()?;
221 let parent_tree = merge_commit_trees(repo, &parents).block_on()?;
222 Ok(commit.root_tree == *parent_tree.tree_ids())
223}
224
225fn is_commit_empty_by_index(repo: &dyn Repo, id: &CommitId) -> BackendResult<Option<bool>> {
226 let maybe_paths = repo
227 .index()
228 .changed_paths_in_commit(id)
229 .map_err(|err| BackendError::Other(err.into()))?;
231 Ok(maybe_paths.map(|mut paths| paths.next().is_none()))
232}
233
234pub trait CommitIteratorExt<'c, I> {
235 fn ids(self) -> impl Iterator<Item = &'c CommitId>;
236}
237
238impl<'c, I> CommitIteratorExt<'c, I> for I
239where
240 I: Iterator<Item = &'c Commit>,
241{
242 fn ids(self) -> impl Iterator<Item = &'c CommitId> {
243 self.map(|commit| commit.id())
244 }
245}
246
247#[derive(Clone, Debug, Eq, Hash, PartialEq)]
249pub(crate) struct CommitByCommitterTimestamp(pub Commit);
250
251impl Ord for CommitByCommitterTimestamp {
252 fn cmp(&self, other: &Self) -> Ordering {
253 let self_timestamp = &self.0.committer().timestamp.timestamp;
254 let other_timestamp = &other.0.committer().timestamp.timestamp;
255 self_timestamp
256 .cmp(other_timestamp)
257 .then_with(|| self.0.cmp(&other.0)) }
259}
260
261impl PartialOrd for CommitByCommitterTimestamp {
262 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
263 Some(self.cmp(other))
264 }
265}