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