1#![allow(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::BackendResult;
31use crate::backend::ChangeId;
32use crate::backend::CommitId;
33use crate::backend::MergedTreeId;
34use crate::backend::Signature;
35use crate::merged_tree::MergedTree;
36use crate::repo::Repo;
37use crate::rewrite::merge_commit_trees;
38use crate::signing::SignResult;
39use crate::signing::Verification;
40use crate::store::Store;
41
42#[derive(Clone, serde::Serialize)]
43pub struct Commit {
44 #[serde(skip)]
45 store: Arc<Store>,
46 #[serde(rename = "commit_id")]
47 id: CommitId,
48 #[serde(flatten)]
49 data: Arc<backend::Commit>,
50}
51
52impl Debug for Commit {
53 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
54 f.debug_struct("Commit").field("id", &self.id).finish()
55 }
56}
57
58impl PartialEq for Commit {
59 fn eq(&self, other: &Self) -> bool {
60 self.id == other.id
61 }
62}
63
64impl Eq for Commit {}
65
66impl Ord for Commit {
67 fn cmp(&self, other: &Self) -> Ordering {
68 self.id.cmp(&other.id)
69 }
70}
71
72impl PartialOrd for Commit {
73 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
74 Some(self.cmp(other))
75 }
76}
77
78impl Hash for Commit {
79 fn hash<H: Hasher>(&self, state: &mut H) {
80 self.id.hash(state);
81 }
82}
83
84impl Commit {
85 pub fn new(store: Arc<Store>, id: CommitId, data: Arc<backend::Commit>) -> Self {
86 Self { store, id, data }
87 }
88
89 pub fn store(&self) -> &Arc<Store> {
90 &self.store
91 }
92
93 pub fn id(&self) -> &CommitId {
94 &self.id
95 }
96
97 pub fn parent_ids(&self) -> &[CommitId] {
98 &self.data.parents
99 }
100
101 pub fn parents(&self) -> impl Iterator<Item = BackendResult<Self>> + use<'_> {
102 self.data.parents.iter().map(|id| self.store.get_commit(id))
103 }
104
105 pub async fn parents_async(&self) -> BackendResult<Vec<Self>> {
106 try_join_all(
107 self.data
108 .parents
109 .iter()
110 .map(|id| self.store.get_commit_async(id)),
111 )
112 .await
113 }
114
115 pub fn tree(&self) -> BackendResult<MergedTree> {
116 self.tree_async().block_on()
117 }
118
119 pub async fn tree_async(&self) -> BackendResult<MergedTree> {
120 self.store.get_root_tree_async(&self.data.root_tree).await
121 }
122
123 pub fn tree_id(&self) -> &MergedTreeId {
124 &self.data.root_tree
125 }
126
127 pub fn parent_tree(&self, repo: &dyn Repo) -> BackendResult<MergedTree> {
130 let parents: Vec<_> = self.parents().try_collect()?;
131 merge_commit_trees(repo, &parents).block_on()
132 }
133
134 pub fn is_empty(&self, repo: &dyn Repo) -> BackendResult<bool> {
137 is_backend_commit_empty(repo, &self.store, &self.data)
138 }
139
140 pub fn has_conflict(&self) -> BackendResult<bool> {
141 if let MergedTreeId::Merge(tree_ids) = self.tree_id() {
142 Ok(!tree_ids.is_resolved())
143 } else {
144 Ok(self.tree()?.has_conflict())
145 }
146 }
147
148 pub fn change_id(&self) -> &ChangeId {
149 &self.data.change_id
150 }
151
152 pub fn store_commit(&self) -> &Arc<backend::Commit> {
153 &self.data
154 }
155
156 pub fn description(&self) -> &str {
157 &self.data.description
158 }
159
160 pub fn author(&self) -> &Signature {
161 &self.data.author
162 }
163
164 pub fn committer(&self) -> &Signature {
165 &self.data.committer
166 }
167
168 pub fn is_hidden(&self, repo: &dyn Repo) -> bool {
170 let maybe_entries = repo.resolve_change_id(self.change_id());
171 maybe_entries.is_none_or(|entries| !entries.contains(&self.id))
172 }
173
174 pub fn is_discardable(&self, repo: &dyn Repo) -> BackendResult<bool> {
177 Ok(self.description().is_empty() && self.is_empty(repo)?)
178 }
179
180 pub fn is_signed(&self) -> bool {
182 self.data.secure_sig.is_some()
183 }
184
185 pub fn verification(&self) -> SignResult<Option<Verification>> {
187 self.data
188 .secure_sig
189 .as_ref()
190 .map(|sig| self.store.signer().verify(&self.id, &sig.data, &sig.sig))
191 .transpose()
192 }
193}
194
195pub(crate) fn is_backend_commit_empty(
196 repo: &dyn Repo,
197 store: &Arc<Store>,
198 commit: &backend::Commit,
199) -> BackendResult<bool> {
200 if let [parent_id] = &*commit.parents {
201 return Ok(commit.root_tree == *store.get_commit(parent_id)?.tree_id());
202 }
203 let parents: Vec<_> = commit
204 .parents
205 .iter()
206 .map(|id| store.get_commit(id))
207 .try_collect()?;
208 let parent_tree = merge_commit_trees(repo, &parents).block_on()?;
209 Ok(commit.root_tree == parent_tree.id())
210}
211
212pub trait CommitIteratorExt<'c, I> {
213 fn ids(self) -> impl Iterator<Item = &'c CommitId>;
214}
215
216impl<'c, I> CommitIteratorExt<'c, I> for I
217where
218 I: Iterator<Item = &'c Commit>,
219{
220 fn ids(self) -> impl Iterator<Item = &'c CommitId> {
221 self.map(|commit| commit.id())
222 }
223}
224
225#[derive(Clone, Debug, Eq, Hash, PartialEq)]
227pub(crate) struct CommitByCommitterTimestamp(pub Commit);
228
229impl Ord for CommitByCommitterTimestamp {
230 fn cmp(&self, other: &Self) -> Ordering {
231 let self_timestamp = &self.0.committer().timestamp.timestamp;
232 let other_timestamp = &other.0.committer().timestamp.timestamp;
233 self_timestamp
234 .cmp(other_timestamp)
235 .then_with(|| self.0.cmp(&other.0)) }
237}
238
239impl PartialOrd for CommitByCommitterTimestamp {
240 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
241 Some(self.cmp(other))
242 }
243}