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