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