jj_lib/
commit_builder.rs

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 [`MutableRepo::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 [`MutableRepo::rewrite_commit`]. Use that function
201    /// instead.
202    pub(crate) fn for_rewrite_from(
203        repo: &dyn Repo,
204        settings: &UserSettings,
205        predecessor: &Commit,
206    ) -> Self {
207        let store = repo.store().clone();
208        let mut commit = backend::Commit::clone(predecessor.store_commit());
209        commit.predecessors = vec![predecessor.id().clone()];
210        commit.committer = settings.signature();
211        // If the user had not configured a name and email before but now they have,
212        // update the author fields with the new information.
213        if commit.author.name.is_empty()
214            || commit.author.name == UserSettings::USER_NAME_PLACEHOLDER
215        {
216            commit.author.name.clone_from(&commit.committer.name);
217        }
218        if commit.author.email.is_empty()
219            || commit.author.email == UserSettings::USER_EMAIL_PLACEHOLDER
220        {
221            commit.author.email.clone_from(&commit.committer.email);
222        }
223
224        // Reset author timestamp on discardable commits if the author is the
225        // committer. While it's unlikely we'll have somebody else's commit
226        // with no description in our repo, we'd like to be extra safe.
227        if commit.author.name == commit.committer.name
228            && commit.author.email == commit.committer.email
229            && predecessor.is_discardable(repo).unwrap_or_default()
230        {
231            commit.author.timestamp = commit.committer.timestamp;
232        }
233
234        DetachedCommitBuilder {
235            store,
236            commit,
237            rng: settings.get_rng(),
238            rewrite_source: Some(predecessor.clone()),
239            sign_settings: settings.sign_settings(),
240        }
241    }
242
243    /// Attaches the underlying `mut_repo`.
244    pub fn attach(self, mut_repo: &mut MutableRepo) -> CommitBuilder<'_> {
245        assert!(Arc::ptr_eq(&self.store, mut_repo.store()));
246        CommitBuilder {
247            mut_repo,
248            inner: self,
249        }
250    }
251
252    pub fn parents(&self) -> &[CommitId] {
253        &self.commit.parents
254    }
255
256    pub fn set_parents(&mut self, parents: Vec<CommitId>) -> &mut Self {
257        assert!(!parents.is_empty());
258        self.commit.parents = parents;
259        self
260    }
261
262    pub fn predecessors(&self) -> &[CommitId] {
263        &self.commit.predecessors
264    }
265
266    pub fn set_predecessors(&mut self, predecessors: Vec<CommitId>) -> &mut Self {
267        self.commit.predecessors = predecessors;
268        self
269    }
270
271    pub fn tree_id(&self) -> &MergedTreeId {
272        &self.commit.root_tree
273    }
274
275    pub fn set_tree_id(&mut self, tree_id: MergedTreeId) -> &mut Self {
276        self.commit.root_tree = tree_id;
277        self
278    }
279
280    /// [`Commit::is_empty()`] for the new commit.
281    pub fn is_empty(&self, repo: &dyn Repo) -> BackendResult<bool> {
282        is_backend_commit_empty(repo, &self.store, &self.commit)
283    }
284
285    pub fn change_id(&self) -> &ChangeId {
286        &self.commit.change_id
287    }
288
289    pub fn set_change_id(&mut self, change_id: ChangeId) -> &mut Self {
290        self.commit.change_id = change_id;
291        self
292    }
293
294    pub fn generate_new_change_id(&mut self) -> &mut Self {
295        self.commit.change_id = self.rng.new_change_id(self.store.change_id_length());
296        self
297    }
298
299    pub fn description(&self) -> &str {
300        &self.commit.description
301    }
302
303    pub fn set_description(&mut self, description: impl Into<String>) -> &mut Self {
304        self.commit.description = description.into();
305        self
306    }
307
308    pub fn author(&self) -> &Signature {
309        &self.commit.author
310    }
311
312    pub fn set_author(&mut self, author: Signature) -> &mut Self {
313        self.commit.author = author;
314        self
315    }
316
317    pub fn committer(&self) -> &Signature {
318        &self.commit.committer
319    }
320
321    pub fn set_committer(&mut self, committer: Signature) -> &mut Self {
322        self.commit.committer = committer;
323        self
324    }
325
326    /// [`Commit::is_discardable()`] for the new commit.
327    pub fn is_discardable(&self, repo: &dyn Repo) -> BackendResult<bool> {
328        Ok(self.description().is_empty() && self.is_empty(repo)?)
329    }
330
331    pub fn sign_settings(&self) -> &SignSettings {
332        &self.sign_settings
333    }
334
335    pub fn set_sign_behavior(&mut self, sign_behavior: SignBehavior) -> &mut Self {
336        self.sign_settings.behavior = sign_behavior;
337        self
338    }
339
340    pub fn set_sign_key(&mut self, sign_key: String) -> &mut Self {
341        self.sign_settings.key = Some(sign_key);
342        self
343    }
344
345    pub fn clear_sign_key(&mut self) -> &mut Self {
346        self.sign_settings.key = None;
347        self
348    }
349
350    /// Writes new commit and makes it visible in the `mut_repo`.
351    pub fn write(self, mut_repo: &mut MutableRepo) -> BackendResult<Commit> {
352        let predecessors = self.commit.predecessors.clone();
353        let commit = write_to_store(&self.store, self.commit, &self.sign_settings)?;
354        mut_repo.add_head(&commit)?;
355        mut_repo.set_predecessors(commit.id().clone(), predecessors);
356        if let Some(rewrite_source) = self.rewrite_source {
357            if rewrite_source.change_id() == commit.change_id() {
358                mut_repo.set_rewritten_commit(rewrite_source.id().clone(), commit.id().clone());
359            }
360        }
361        Ok(commit)
362    }
363
364    /// Writes new commit without making it visible in the repo.
365    ///
366    /// This does not consume the builder, so you can reuse the current
367    /// configuration to create another commit later.
368    pub fn write_hidden(&self) -> BackendResult<Commit> {
369        write_to_store(&self.store, self.commit.clone(), &self.sign_settings)
370    }
371
372    /// Records the old commit as abandoned in the `mut_repo`.
373    ///
374    /// This is noop if there's no old commit that would be rewritten to the new
375    /// commit by `write()`.
376    pub fn abandon(self, mut_repo: &mut MutableRepo) {
377        let commit = self.commit;
378        if let Some(rewrite_source) = &self.rewrite_source {
379            if rewrite_source.change_id() == &commit.change_id {
380                mut_repo.record_abandoned_commit_with_parents(
381                    rewrite_source.id().clone(),
382                    commit.parents,
383                );
384            }
385        }
386    }
387}
388
389fn write_to_store(
390    store: &Arc<Store>,
391    mut commit: backend::Commit,
392    sign_settings: &SignSettings,
393) -> BackendResult<Commit> {
394    let should_sign = store.signer().can_sign() && sign_settings.should_sign(&commit);
395    let sign_fn = |data: &[u8]| store.signer().sign(data, sign_settings.key.as_deref());
396
397    // Commit backend doesn't use secure_sig for writing and enforces it with an
398    // assert, but sign_settings.should_sign check above will want to know
399    // if we're rewriting a signed commit
400    commit.secure_sig = None;
401
402    store
403        .write_commit(commit, should_sign.then_some(&mut &sign_fn))
404        .block_on()
405}