just playing with tangled
at gvimdiff 6.6 kB view raw
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) -> &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}