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