jj_lib/
commit.rs

1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![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 predecessor_ids(&self) -> &[CommitId] {
101        &self.data.predecessors
102    }
103
104    pub fn predecessors(&self) -> impl Iterator<Item = BackendResult<Commit>> + use<'_> {
105        self.data
106            .predecessors
107            .iter()
108            .map(|id| self.store.get_commit(id))
109    }
110
111    pub fn tree(&self) -> BackendResult<MergedTree> {
112        self.store.get_root_tree(&self.data.root_tree)
113    }
114
115    pub fn tree_id(&self) -> &MergedTreeId {
116        &self.data.root_tree
117    }
118
119    /// Return the parent tree, merging the parent trees if there are multiple
120    /// parents.
121    pub fn parent_tree(&self, repo: &dyn Repo) -> BackendResult<MergedTree> {
122        let parents: Vec<_> = self.parents().try_collect()?;
123        merge_commit_trees(repo, &parents)
124    }
125
126    /// Returns whether commit's content is empty. Commit description is not
127    /// taken into consideration.
128    pub fn is_empty(&self, repo: &dyn Repo) -> BackendResult<bool> {
129        is_backend_commit_empty(repo, &self.store, &self.data)
130    }
131
132    pub fn has_conflict(&self) -> BackendResult<bool> {
133        if let MergedTreeId::Merge(tree_ids) = self.tree_id() {
134            Ok(!tree_ids.is_resolved())
135        } else {
136            Ok(self.tree()?.has_conflict())
137        }
138    }
139
140    pub fn change_id(&self) -> &ChangeId {
141        &self.data.change_id
142    }
143
144    pub fn store_commit(&self) -> &Arc<backend::Commit> {
145        &self.data
146    }
147
148    pub fn description(&self) -> &str {
149        &self.data.description
150    }
151
152    pub fn author(&self) -> &Signature {
153        &self.data.author
154    }
155
156    pub fn committer(&self) -> &Signature {
157        &self.data.committer
158    }
159
160    ///  A commit is hidden if its commit id is not in the change id index.
161    pub fn is_hidden(&self, repo: &dyn Repo) -> bool {
162        let maybe_entries = repo.resolve_change_id(self.change_id());
163        maybe_entries.is_none_or(|entries| !entries.contains(&self.id))
164    }
165
166    /// A commit is discardable if it has no change from its parent, and an
167    /// empty description.
168    pub fn is_discardable(&self, repo: &dyn Repo) -> BackendResult<bool> {
169        Ok(self.description().is_empty() && self.is_empty(repo)?)
170    }
171
172    /// A quick way to just check if a signature is present.
173    pub fn is_signed(&self) -> bool {
174        self.data.secure_sig.is_some()
175    }
176
177    /// A slow (but cached) way to get the full verification.
178    pub fn verification(&self) -> SignResult<Option<Verification>> {
179        self.data
180            .secure_sig
181            .as_ref()
182            .map(|sig| self.store.signer().verify(&self.id, &sig.data, &sig.sig))
183            .transpose()
184    }
185}
186
187pub(crate) fn is_backend_commit_empty(
188    repo: &dyn Repo,
189    store: &Arc<Store>,
190    commit: &backend::Commit,
191) -> BackendResult<bool> {
192    if let [parent_id] = &*commit.parents {
193        return Ok(commit.root_tree == *store.get_commit(parent_id)?.tree_id());
194    }
195    let parents: Vec<_> = commit
196        .parents
197        .iter()
198        .map(|id| store.get_commit(id))
199        .try_collect()?;
200    let parent_tree = merge_commit_trees(repo, &parents)?;
201    Ok(commit.root_tree == parent_tree.id())
202}
203
204pub trait CommitIteratorExt<'c, I> {
205    fn ids(self) -> impl Iterator<Item = &'c CommitId>;
206}
207
208impl<'c, I> CommitIteratorExt<'c, I> for I
209where
210    I: Iterator<Item = &'c Commit>,
211{
212    fn ids(self) -> impl Iterator<Item = &'c CommitId> {
213        self.map(|commit| commit.id())
214    }
215}
216
217/// Wrapper to sort `Commit` by committer timestamp.
218#[derive(Clone, Debug, Eq, Hash, PartialEq)]
219pub(crate) struct CommitByCommitterTimestamp(pub Commit);
220
221impl Ord for CommitByCommitterTimestamp {
222    fn cmp(&self, other: &Self) -> Ordering {
223        let self_timestamp = &self.0.committer().timestamp.timestamp;
224        let other_timestamp = &other.0.committer().timestamp.timestamp;
225        self_timestamp
226            .cmp(other_timestamp)
227            .then_with(|| self.0.cmp(&other.0)) // to comply with Eq
228    }
229}
230
231impl PartialOrd for CommitByCommitterTimestamp {
232    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
233        Some(self.cmp(other))
234    }
235}