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