just playing with tangled
at gvimdiff 12 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::sync::Arc; 18 19use pollster::FutureExt as _; 20 21use crate::backend; 22use crate::backend::BackendResult; 23use crate::backend::ChangeId; 24use crate::backend::CommitId; 25use crate::backend::MergedTreeId; 26use crate::backend::Signature; 27use crate::commit::is_backend_commit_empty; 28use crate::commit::Commit; 29use crate::repo::MutableRepo; 30use crate::repo::Repo; 31use crate::settings::JJRng; 32use crate::settings::SignSettings; 33use crate::settings::UserSettings; 34use crate::signing::SignBehavior; 35use crate::store::Store; 36 37#[must_use] 38pub struct CommitBuilder<'repo> { 39 mut_repo: &'repo mut MutableRepo, 40 inner: DetachedCommitBuilder, 41} 42 43impl CommitBuilder<'_> { 44 /// Detaches from `&'repo mut` lifetime. The returned builder can be used in 45 /// order to obtain a temporary commit object. 46 pub fn detach(self) -> DetachedCommitBuilder { 47 self.inner 48 } 49 50 pub fn parents(&self) -> &[CommitId] { 51 self.inner.parents() 52 } 53 54 pub fn set_parents(mut self, parents: Vec<CommitId>) -> Self { 55 self.inner.set_parents(parents); 56 self 57 } 58 59 pub fn predecessors(&self) -> &[CommitId] { 60 self.inner.predecessors() 61 } 62 63 pub fn set_predecessors(mut self, predecessors: Vec<CommitId>) -> Self { 64 self.inner.set_predecessors(predecessors); 65 self 66 } 67 68 pub fn tree_id(&self) -> &MergedTreeId { 69 self.inner.tree_id() 70 } 71 72 pub fn set_tree_id(mut self, tree_id: MergedTreeId) -> Self { 73 self.inner.set_tree_id(tree_id); 74 self 75 } 76 77 /// [`Commit::is_empty()`] for the new commit. 78 pub fn is_empty(&self) -> BackendResult<bool> { 79 self.inner.is_empty(self.mut_repo) 80 } 81 82 pub fn change_id(&self) -> &ChangeId { 83 self.inner.change_id() 84 } 85 86 pub fn set_change_id(mut self, change_id: ChangeId) -> Self { 87 self.inner.set_change_id(change_id); 88 self 89 } 90 91 pub fn generate_new_change_id(mut self) -> Self { 92 self.inner.generate_new_change_id(); 93 self 94 } 95 96 pub fn description(&self) -> &str { 97 self.inner.description() 98 } 99 100 pub fn set_description(mut self, description: impl Into<String>) -> Self { 101 self.inner.set_description(description); 102 self 103 } 104 105 pub fn author(&self) -> &Signature { 106 self.inner.author() 107 } 108 109 pub fn set_author(mut self, author: Signature) -> Self { 110 self.inner.set_author(author); 111 self 112 } 113 114 pub fn committer(&self) -> &Signature { 115 self.inner.committer() 116 } 117 118 pub fn set_committer(mut self, committer: Signature) -> Self { 119 self.inner.set_committer(committer); 120 self 121 } 122 123 /// [`Commit::is_discardable()`] for the new commit. 124 pub fn is_discardable(&self) -> BackendResult<bool> { 125 self.inner.is_discardable(self.mut_repo) 126 } 127 128 pub fn sign_settings(&self) -> &SignSettings { 129 self.inner.sign_settings() 130 } 131 132 pub fn set_sign_behavior(mut self, sign_behavior: SignBehavior) -> Self { 133 self.inner.set_sign_behavior(sign_behavior); 134 self 135 } 136 137 pub fn set_sign_key(mut self, sign_key: String) -> Self { 138 self.inner.set_sign_key(sign_key); 139 self 140 } 141 142 pub fn clear_sign_key(mut self) -> Self { 143 self.inner.clear_sign_key(); 144 self 145 } 146 147 pub fn write(self) -> BackendResult<Commit> { 148 self.inner.write(self.mut_repo) 149 } 150 151 /// Records the old commit as abandoned instead of writing new commit. This 152 /// is noop for the builder created by [`MutableRepo::new_commit()`]. 153 pub fn abandon(self) { 154 self.inner.abandon(self.mut_repo); 155 } 156} 157 158/// Like `CommitBuilder`, but doesn't mutably borrow `MutableRepo`. 159#[derive(Debug)] 160pub struct DetachedCommitBuilder { 161 store: Arc<Store>, 162 rng: Arc<JJRng>, 163 commit: backend::Commit, 164 rewrite_source: Option<Commit>, 165 sign_settings: SignSettings, 166} 167 168impl DetachedCommitBuilder { 169 /// Only called from [`MutRepo::new_commit`]. Use that function instead. 170 pub(crate) fn for_new_commit( 171 repo: &dyn Repo, 172 settings: &UserSettings, 173 parents: Vec<CommitId>, 174 tree_id: MergedTreeId, 175 ) -> Self { 176 let store = repo.store().clone(); 177 let signature = settings.signature(); 178 assert!(!parents.is_empty()); 179 let rng = settings.get_rng(); 180 let change_id = rng.new_change_id(store.change_id_length()); 181 let commit = backend::Commit { 182 parents, 183 predecessors: vec![], 184 root_tree: tree_id, 185 change_id, 186 description: String::new(), 187 author: signature.clone(), 188 committer: signature, 189 secure_sig: None, 190 }; 191 DetachedCommitBuilder { 192 store, 193 rng, 194 commit, 195 rewrite_source: None, 196 sign_settings: settings.sign_settings(), 197 } 198 } 199 200 /// Only called from [`MutRepo::rewrite_commit`]. Use that function instead. 201 pub(crate) fn for_rewrite_from( 202 repo: &dyn Repo, 203 settings: &UserSettings, 204 predecessor: &Commit, 205 ) -> Self { 206 let store = repo.store().clone(); 207 let mut commit = predecessor.store_commit().clone(); 208 commit.predecessors = vec![predecessor.id().clone()]; 209 commit.committer = settings.signature(); 210 // If the user had not configured a name and email before but now they have, 211 // update the author fields with the new information. 212 if commit.author.name.is_empty() 213 || commit.author.name == UserSettings::USER_NAME_PLACEHOLDER 214 { 215 commit.author.name.clone_from(&commit.committer.name); 216 } 217 if commit.author.email.is_empty() 218 || commit.author.email == UserSettings::USER_EMAIL_PLACEHOLDER 219 { 220 commit.author.email.clone_from(&commit.committer.email); 221 } 222 223 // Reset author timestamp on discardable commits if the author is the 224 // committer. While it's unlikely we'll have somebody else's commit 225 // with no description in our repo, we'd like to be extra safe. 226 if commit.author.name == commit.committer.name 227 && commit.author.email == commit.committer.email 228 && predecessor.is_discardable(repo).unwrap_or_default() 229 { 230 commit.author.timestamp = commit.committer.timestamp; 231 } 232 233 DetachedCommitBuilder { 234 store, 235 commit, 236 rng: settings.get_rng(), 237 rewrite_source: Some(predecessor.clone()), 238 sign_settings: settings.sign_settings(), 239 } 240 } 241 242 /// Attaches the underlying `mut_repo`. 243 pub fn attach(self, mut_repo: &mut MutableRepo) -> CommitBuilder<'_> { 244 assert!(Arc::ptr_eq(&self.store, mut_repo.store())); 245 CommitBuilder { 246 mut_repo, 247 inner: self, 248 } 249 } 250 251 pub fn parents(&self) -> &[CommitId] { 252 &self.commit.parents 253 } 254 255 pub fn set_parents(&mut self, parents: Vec<CommitId>) -> &mut Self { 256 assert!(!parents.is_empty()); 257 self.commit.parents = parents; 258 self 259 } 260 261 pub fn predecessors(&self) -> &[CommitId] { 262 &self.commit.predecessors 263 } 264 265 pub fn set_predecessors(&mut self, predecessors: Vec<CommitId>) -> &mut Self { 266 self.commit.predecessors = predecessors; 267 self 268 } 269 270 pub fn tree_id(&self) -> &MergedTreeId { 271 &self.commit.root_tree 272 } 273 274 pub fn set_tree_id(&mut self, tree_id: MergedTreeId) -> &mut Self { 275 self.commit.root_tree = tree_id; 276 self 277 } 278 279 /// [`Commit::is_empty()`] for the new commit. 280 pub fn is_empty(&self, repo: &dyn Repo) -> BackendResult<bool> { 281 is_backend_commit_empty(repo, &self.store, &self.commit) 282 } 283 284 pub fn change_id(&self) -> &ChangeId { 285 &self.commit.change_id 286 } 287 288 pub fn set_change_id(&mut self, change_id: ChangeId) -> &mut Self { 289 self.commit.change_id = change_id; 290 self 291 } 292 293 pub fn generate_new_change_id(&mut self) -> &mut Self { 294 self.commit.change_id = self.rng.new_change_id(self.store.change_id_length()); 295 self 296 } 297 298 pub fn description(&self) -> &str { 299 &self.commit.description 300 } 301 302 pub fn set_description(&mut self, description: impl Into<String>) -> &mut Self { 303 self.commit.description = description.into(); 304 self 305 } 306 307 pub fn author(&self) -> &Signature { 308 &self.commit.author 309 } 310 311 pub fn set_author(&mut self, author: Signature) -> &mut Self { 312 self.commit.author = author; 313 self 314 } 315 316 pub fn committer(&self) -> &Signature { 317 &self.commit.committer 318 } 319 320 pub fn set_committer(&mut self, committer: Signature) -> &mut Self { 321 self.commit.committer = committer; 322 self 323 } 324 325 /// [`Commit::is_discardable()`] for the new commit. 326 pub fn is_discardable(&self, repo: &dyn Repo) -> BackendResult<bool> { 327 Ok(self.description().is_empty() && self.is_empty(repo)?) 328 } 329 330 pub fn sign_settings(&self) -> &SignSettings { 331 &self.sign_settings 332 } 333 334 pub fn set_sign_behavior(&mut self, sign_behavior: SignBehavior) -> &mut Self { 335 self.sign_settings.behavior = sign_behavior; 336 self 337 } 338 339 pub fn set_sign_key(&mut self, sign_key: String) -> &mut Self { 340 self.sign_settings.key = Some(sign_key); 341 self 342 } 343 344 pub fn clear_sign_key(&mut self) -> &mut Self { 345 self.sign_settings.key = None; 346 self 347 } 348 349 /// Writes new commit and makes it visible in the `mut_repo`. 350 pub fn write(self, mut_repo: &mut MutableRepo) -> BackendResult<Commit> { 351 let commit = write_to_store(&self.store, self.commit, &self.sign_settings)?; 352 mut_repo.add_head(&commit)?; 353 if let Some(rewrite_source) = self.rewrite_source { 354 if rewrite_source.change_id() == commit.change_id() { 355 mut_repo.set_rewritten_commit(rewrite_source.id().clone(), commit.id().clone()); 356 } 357 } 358 Ok(commit) 359 } 360 361 /// Writes new commit without making it visible in the repo. 362 /// 363 /// This does not consume the builder, so you can reuse the current 364 /// configuration to create another commit later. 365 pub fn write_hidden(&self) -> BackendResult<Commit> { 366 write_to_store(&self.store, self.commit.clone(), &self.sign_settings) 367 } 368 369 /// Records the old commit as abandoned in the `mut_repo`. 370 /// 371 /// This is noop if there's no old commit that would be rewritten to the new 372 /// commit by `write()`. 373 pub fn abandon(self, mut_repo: &mut MutableRepo) { 374 let commit = self.commit; 375 if let Some(rewrite_source) = &self.rewrite_source { 376 if rewrite_source.change_id() == &commit.change_id { 377 mut_repo.record_abandoned_commit_with_parents( 378 rewrite_source.id().clone(), 379 commit.parents, 380 ); 381 } 382 } 383 } 384} 385 386fn write_to_store( 387 store: &Arc<Store>, 388 mut commit: backend::Commit, 389 sign_settings: &SignSettings, 390) -> BackendResult<Commit> { 391 let should_sign = store.signer().can_sign() && sign_settings.should_sign(&commit); 392 let sign_fn = |data: &[u8]| store.signer().sign(data, sign_settings.key.as_deref()); 393 394 // Commit backend doesn't use secure_sig for writing and enforces it with an 395 // assert, but sign_settings.should_sign check above will want to know 396 // if we're rewriting a signed commit 397 commit.secure_sig = None; 398 399 store 400 .write_commit(commit, should_sign.then_some(&mut &sign_fn)) 401 .block_on() 402}