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