1#![expect(missing_docs)]
16
17use std::sync::Arc;
18
19use pollster::FutureExt as _;
20
21use crate::backend;
22use crate::backend::BackendError;
23use crate::backend::BackendResult;
24use crate::backend::ChangeId;
25use crate::backend::CommitId;
26use crate::backend::Signature;
27use crate::backend::TreeId;
28use crate::commit::Commit;
29use crate::commit::is_backend_commit_empty;
30use crate::conflict_labels::ConflictLabels;
31use crate::merge::Merge;
32use crate::merged_tree::MergedTree;
33use crate::repo::MutableRepo;
34use crate::repo::Repo;
35use crate::settings::JJRng;
36use crate::settings::SignBehavior;
37use crate::settings::SignSettings;
38use crate::settings::UserSettings;
39use crate::store::Store;
40
41#[must_use]
42pub struct CommitBuilder<'repo> {
43 mut_repo: &'repo mut MutableRepo,
44 inner: DetachedCommitBuilder,
45}
46
47impl CommitBuilder<'_> {
48 pub fn detach(self) -> DetachedCommitBuilder {
51 self.inner
52 }
53
54 pub fn clear_rewrite_source(mut self) -> Self {
58 self.inner.clear_rewrite_source();
59 self
60 }
61
62 pub fn parents(&self) -> &[CommitId] {
63 self.inner.parents()
64 }
65
66 pub fn set_parents(mut self, parents: Vec<CommitId>) -> Self {
67 self.inner.set_parents(parents);
68 self
69 }
70
71 pub fn predecessors(&self) -> &[CommitId] {
72 self.inner.predecessors()
73 }
74
75 pub fn set_predecessors(mut self, predecessors: Vec<CommitId>) -> Self {
76 self.inner.set_predecessors(predecessors);
77 self
78 }
79
80 pub fn tree(&self) -> MergedTree {
81 self.inner.tree()
82 }
83
84 pub fn tree_ids(&self) -> &Merge<TreeId> {
85 self.inner.tree_ids()
86 }
87
88 pub fn set_tree(mut self, tree: MergedTree) -> Self {
89 self.inner.set_tree(tree);
90 self
91 }
92
93 pub async fn is_empty(&self) -> BackendResult<bool> {
95 self.inner.is_empty(self.mut_repo).await
96 }
97
98 pub fn change_id(&self) -> &ChangeId {
99 self.inner.change_id()
100 }
101
102 pub fn set_change_id(mut self, change_id: ChangeId) -> Self {
103 self.inner.set_change_id(change_id);
104 self
105 }
106
107 pub fn generate_new_change_id(mut self) -> Self {
108 self.inner.generate_new_change_id();
109 self
110 }
111
112 pub fn description(&self) -> &str {
113 self.inner.description()
114 }
115
116 pub fn set_description(mut self, description: impl Into<String>) -> Self {
117 self.inner.set_description(description);
118 self
119 }
120
121 pub fn author(&self) -> &Signature {
122 self.inner.author()
123 }
124
125 pub fn set_author(mut self, author: Signature) -> Self {
126 self.inner.set_author(author);
127 self
128 }
129
130 pub fn committer(&self) -> &Signature {
131 self.inner.committer()
132 }
133
134 pub fn set_committer(mut self, committer: Signature) -> Self {
135 self.inner.set_committer(committer);
136 self
137 }
138
139 pub async fn is_discardable(&self) -> BackendResult<bool> {
141 self.inner.is_discardable(self.mut_repo).await
142 }
143
144 pub fn sign_settings(&self) -> &SignSettings {
145 self.inner.sign_settings()
146 }
147
148 pub fn set_sign_behavior(mut self, sign_behavior: SignBehavior) -> Self {
149 self.inner.set_sign_behavior(sign_behavior);
150 self
151 }
152
153 pub fn set_sign_key_override(mut self, sign_key: String) -> Self {
154 self.inner.set_sign_key_override(sign_key);
155 self
156 }
157
158 pub fn clear_sign_key_override(mut self) -> Self {
159 self.inner.clear_sign_key_override();
160 self
161 }
162
163 pub async fn write(self) -> BackendResult<Commit> {
164 self.inner.write(self.mut_repo).await
165 }
166
167 pub fn abandon(self) {
170 self.inner.abandon(self.mut_repo);
171 }
172}
173
174#[derive(Debug)]
176pub struct DetachedCommitBuilder {
177 store: Arc<Store>,
178 rng: Arc<JJRng>,
179 commit: backend::Commit,
180 predecessors: Vec<CommitId>,
181 rewrite_source: Option<Commit>,
182 sign_key_override: Option<String>,
183 sign_settings: SignSettings,
184 record_predecessors_in_commit: bool,
185}
186
187impl DetachedCommitBuilder {
188 pub(crate) fn for_new_commit(
190 repo: &dyn Repo,
191 settings: &UserSettings,
192 parents: Vec<CommitId>,
193 tree: MergedTree,
194 ) -> Self {
195 let store = repo.store().clone();
196 let signature = settings.signature();
197 assert!(!parents.is_empty());
198 let rng = settings.get_rng();
199 let (root_tree, conflict_labels) = tree.into_tree_ids_and_labels();
200 let change_id = rng.new_change_id(store.change_id_length());
201 let commit = backend::Commit {
202 parents,
203 predecessors: vec![],
204 root_tree,
205 conflict_labels: conflict_labels.into_merge(),
206 change_id,
207 description: String::new(),
208 author: signature.clone(),
209 committer: signature,
210 secure_sig: None,
211 };
212 let record_predecessors_in_commit = settings
213 .get_bool("experimental.record-predecessors-in-commit")
214 .unwrap();
215 Self {
216 store,
217 rng,
218 commit,
219 rewrite_source: None,
220 predecessors: vec![],
221 sign_key_override: None,
222 sign_settings: settings.sign_settings(),
223 record_predecessors_in_commit,
224 }
225 }
226
227 pub(crate) fn for_rewrite_from(
230 repo: &dyn Repo,
231 settings: &UserSettings,
232 predecessor: &Commit,
233 ) -> Self {
234 let store = repo.store().clone();
235 let mut commit = backend::Commit::clone(predecessor.store_commit());
236 commit.predecessors = vec![];
237 commit.committer = settings.signature();
238 if commit.author.name.is_empty() {
241 commit.author.name.clone_from(&commit.committer.name);
242 }
243 if commit.author.email.is_empty() {
244 commit.author.email.clone_from(&commit.committer.email);
245 }
246
247 if commit.author.name == commit.committer.name
251 && commit.author.email == commit.committer.email
252 && predecessor
253 .is_discardable(repo)
254 .block_on()
255 .unwrap_or_default()
256 {
257 commit.author.timestamp = commit.committer.timestamp;
258 }
259
260 let record_predecessors_in_commit = settings
261 .get_bool("experimental.record-predecessors-in-commit")
262 .unwrap();
263 Self {
264 store,
265 commit,
266 rng: settings.get_rng(),
267 rewrite_source: Some(predecessor.clone()),
268 predecessors: vec![predecessor.id().clone()],
269 sign_key_override: None,
270 sign_settings: settings.sign_settings(),
271 record_predecessors_in_commit,
272 }
273 }
274
275 pub fn attach(self, mut_repo: &mut MutableRepo) -> CommitBuilder<'_> {
277 assert!(Arc::ptr_eq(&self.store, mut_repo.store()));
278 CommitBuilder {
279 mut_repo,
280 inner: self,
281 }
282 }
283
284 pub fn clear_rewrite_source(&mut self) {
288 self.rewrite_source = None;
289 }
290
291 pub fn parents(&self) -> &[CommitId] {
292 &self.commit.parents
293 }
294
295 pub fn set_parents(&mut self, parents: Vec<CommitId>) -> &mut Self {
296 assert!(!parents.is_empty());
297 self.commit.parents = parents;
298 self
299 }
300
301 pub fn predecessors(&self) -> &[CommitId] {
302 &self.predecessors
303 }
304
305 pub fn set_predecessors(&mut self, predecessors: Vec<CommitId>) -> &mut Self {
306 self.predecessors = predecessors;
307 self
308 }
309
310 pub fn tree(&self) -> MergedTree {
311 MergedTree::new(
312 self.store.clone(),
313 self.commit.root_tree.clone(),
314 ConflictLabels::from_merge(self.commit.conflict_labels.clone()),
315 )
316 }
317
318 pub fn tree_ids(&self) -> &Merge<TreeId> {
319 &self.commit.root_tree
320 }
321
322 pub fn set_tree(&mut self, tree: MergedTree) -> &mut Self {
323 assert!(Arc::ptr_eq(tree.store(), &self.store));
324 let (root_tree, conflict_labels) = tree.into_tree_ids_and_labels();
325 self.commit.root_tree = root_tree;
326 self.commit.conflict_labels = conflict_labels.into_merge();
327 self
328 }
329
330 pub async fn is_empty(&self, repo: &dyn Repo) -> BackendResult<bool> {
332 is_backend_commit_empty(repo, &self.store, &self.commit).await
333 }
334
335 pub fn change_id(&self) -> &ChangeId {
336 &self.commit.change_id
337 }
338
339 pub fn set_change_id(&mut self, change_id: ChangeId) -> &mut Self {
340 self.commit.change_id = change_id;
341 self
342 }
343
344 pub fn generate_new_change_id(&mut self) -> &mut Self {
345 self.commit.change_id = self.rng.new_change_id(self.store.change_id_length());
346 self
347 }
348
349 pub fn description(&self) -> &str {
350 &self.commit.description
351 }
352
353 pub fn set_description(&mut self, description: impl Into<String>) -> &mut Self {
354 self.commit.description = description.into();
355 self
356 }
357
358 pub fn author(&self) -> &Signature {
359 &self.commit.author
360 }
361
362 pub fn set_author(&mut self, author: Signature) -> &mut Self {
363 self.commit.author = author;
364 self
365 }
366
367 pub fn committer(&self) -> &Signature {
368 &self.commit.committer
369 }
370
371 pub fn set_committer(&mut self, committer: Signature) -> &mut Self {
372 self.commit.committer = committer;
373 self
374 }
375
376 pub async fn is_discardable(&self, repo: &dyn Repo) -> BackendResult<bool> {
378 Ok(self.description().is_empty() && self.is_empty(repo).await?)
379 }
380
381 pub fn sign_settings(&self) -> &SignSettings {
382 &self.sign_settings
383 }
384
385 pub fn set_sign_behavior(&mut self, sign_behavior: SignBehavior) -> &mut Self {
386 self.sign_settings.behavior = sign_behavior;
387 self
388 }
389
390 pub fn set_sign_key_override(&mut self, sign_key: String) -> &mut Self {
391 self.sign_key_override = Some(sign_key);
392 self
393 }
394
395 pub fn clear_sign_key_override(&mut self) -> &mut Self {
396 self.sign_key_override = None;
397 self
398 }
399
400 pub async fn write(mut self, mut_repo: &mut MutableRepo) -> BackendResult<Commit> {
402 if self.record_predecessors_in_commit {
403 self.commit.predecessors = self.predecessors.clone();
404 }
405 let commit = write_to_store(
406 &self.store,
407 self.commit,
408 self.sign_key_override.as_deref(),
409 &self.sign_settings,
410 )
411 .await?;
412 if mut_repo.is_backed_by_default_index()
414 && mut_repo
415 .index()
416 .has_id(commit.id())
417 .await
418 .map_err(|err| BackendError::Other(err.into()))?
420 {
421 return Err(BackendError::Other(
425 format!("Newly-created commit {id} already exists", id = commit.id()).into(),
426 ));
427 }
428 mut_repo.add_head(&commit).await?;
429 mut_repo.set_predecessors(commit.id().clone(), self.predecessors);
430 if let Some(rewrite_source) = self.rewrite_source {
431 mut_repo.set_rewritten_commit(rewrite_source.id().clone(), commit.id().clone());
432 }
433 Ok(commit)
434 }
435
436 pub async fn write_hidden(&self) -> BackendResult<Commit> {
441 let mut commit = self.commit.clone();
442 if self.record_predecessors_in_commit {
443 commit.predecessors = self.predecessors.clone();
444 }
445 write_to_store(
446 &self.store,
447 commit,
448 self.sign_key_override.as_deref(),
449 &self.sign_settings,
450 )
451 .await
452 }
453
454 pub fn abandon(self, mut_repo: &mut MutableRepo) {
459 let commit = self.commit;
460 if let Some(rewrite_source) = &self.rewrite_source {
461 mut_repo
462 .record_abandoned_commit_with_parents(rewrite_source.id().clone(), commit.parents);
463 }
464 }
465}
466
467async fn write_to_store(
468 store: &Arc<Store>,
469 mut commit: backend::Commit,
470 sign_key: Option<&str>,
471 sign_settings: &SignSettings,
472) -> BackendResult<Commit> {
473 let should_sign = store.signer().can_sign() && sign_settings.should_sign(&commit);
474 let sign_fn = |data: &[u8]| store.signer().sign(data, sign_key);
475
476 commit.secure_sig = None;
480
481 store
482 .write_commit(commit, should_sign.then_some(&mut &sign_fn))
483 .await
484}