jujutsu_lib/
transaction.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
15use std::sync::Arc;
16
17use crate::backend::Timestamp;
18use crate::dag_walk::closest_common_node;
19use crate::index::ReadonlyIndex;
20use crate::op_store;
21use crate::op_store::OperationMetadata;
22use crate::operation::Operation;
23use crate::repo::{MutableRepo, ReadonlyRepo, Repo, RepoLoader};
24use crate::settings::UserSettings;
25use crate::view::View;
26
27pub struct Transaction {
28    mut_repo: MutableRepo,
29    parent_ops: Vec<Operation>,
30    op_metadata: OperationMetadata,
31    end_time: Option<Timestamp>,
32}
33
34impl Transaction {
35    pub fn new(
36        mut_repo: MutableRepo,
37        user_settings: &UserSettings,
38        description: &str,
39    ) -> Transaction {
40        let parent_ops = vec![mut_repo.base_repo().operation().clone()];
41        let op_metadata = create_op_metadata(user_settings, description.to_string());
42        let end_time = user_settings.operation_timestamp();
43        Transaction {
44            mut_repo,
45            parent_ops,
46            op_metadata,
47            end_time,
48        }
49    }
50
51    pub fn base_repo(&self) -> &Arc<ReadonlyRepo> {
52        self.mut_repo.base_repo()
53    }
54
55    pub fn set_tag(&mut self, key: String, value: String) {
56        self.op_metadata.tags.insert(key, value);
57    }
58
59    pub fn repo(&self) -> &MutableRepo {
60        &self.mut_repo
61    }
62
63    pub fn mut_repo(&mut self) -> &mut MutableRepo {
64        &mut self.mut_repo
65    }
66
67    pub fn merge_operation(&mut self, other_op: Operation) {
68        let ancestor_op = closest_common_node(
69            self.parent_ops.clone(),
70            vec![other_op.clone()],
71            &|op: &Operation| op.parents(),
72            &|op: &Operation| op.id().clone(),
73        )
74        .unwrap();
75        let repo_loader = self.base_repo().loader();
76        let base_repo = repo_loader.load_at(&ancestor_op);
77        let other_repo = repo_loader.load_at(&other_op);
78        self.parent_ops.push(other_op);
79        let merged_repo = self.mut_repo();
80        merged_repo.merge(&base_repo, &other_repo);
81    }
82
83    /// Writes the transaction to the operation store and publishes it.
84    pub fn commit(self) -> Arc<ReadonlyRepo> {
85        self.write().publish()
86    }
87
88    /// Writes the transaction to the operation store, but does not publish it.
89    /// That means that a repo can be loaded at the operation, but the
90    /// operation will not be seen when loading the repo at head.
91    pub fn write(mut self) -> UnpublishedOperation {
92        let mut_repo = self.mut_repo;
93        // TODO: Should we instead just do the rebasing here if necessary?
94        assert!(
95            !mut_repo.has_rewrites(),
96            "BUG: Descendants have not been rebased after the last rewrites."
97        );
98        let base_repo = mut_repo.base_repo().clone();
99        let (mut_index, view) = mut_repo.consume();
100        let index = base_repo.index_store().write_index(mut_index).unwrap();
101
102        let view_id = base_repo.op_store().write_view(view.store_view()).unwrap();
103        self.op_metadata.end_time = self.end_time.unwrap_or_else(Timestamp::now);
104        let parents = self.parent_ops.iter().map(|op| op.id().clone()).collect();
105        let store_operation = op_store::Operation {
106            view_id,
107            parents,
108            metadata: self.op_metadata,
109        };
110        let new_op_id = base_repo
111            .op_store()
112            .write_operation(&store_operation)
113            .unwrap();
114        let operation = Operation::new(base_repo.op_store().clone(), new_op_id, store_operation);
115
116        base_repo
117            .index_store()
118            .associate_file_with_operation(&index, operation.id())
119            .unwrap();
120        UnpublishedOperation::new(base_repo.loader(), operation, view, index)
121    }
122}
123
124pub fn create_op_metadata(user_settings: &UserSettings, description: String) -> OperationMetadata {
125    let start_time = user_settings
126        .operation_timestamp()
127        .unwrap_or_else(Timestamp::now);
128    let end_time = start_time.clone();
129    let hostname = user_settings.operation_hostname();
130    let username = user_settings.operation_username();
131    OperationMetadata {
132        start_time,
133        end_time,
134        description,
135        hostname,
136        username,
137        tags: Default::default(),
138    }
139}
140
141struct NewRepoData {
142    operation: Operation,
143    view: View,
144    index: Arc<ReadonlyIndex>,
145}
146
147pub struct UnpublishedOperation {
148    repo_loader: RepoLoader,
149    data: Option<NewRepoData>,
150    closed: bool,
151}
152
153impl UnpublishedOperation {
154    fn new(
155        repo_loader: RepoLoader,
156        operation: Operation,
157        view: View,
158        index: Arc<ReadonlyIndex>,
159    ) -> Self {
160        let data = Some(NewRepoData {
161            operation,
162            view,
163            index,
164        });
165        UnpublishedOperation {
166            repo_loader,
167            data,
168            closed: false,
169        }
170    }
171
172    pub fn operation(&self) -> &Operation {
173        &self.data.as_ref().unwrap().operation
174    }
175
176    pub fn publish(mut self) -> Arc<ReadonlyRepo> {
177        let data = self.data.take().unwrap();
178        self.repo_loader
179            .op_heads_store()
180            .lock()
181            .promote_new_op(&data.operation);
182        let repo = self
183            .repo_loader
184            .create_from(data.operation, data.view, data.index);
185        self.closed = true;
186        repo
187    }
188
189    pub fn leave_unpublished(mut self) -> Arc<ReadonlyRepo> {
190        let data = self.data.take().unwrap();
191        let repo = self
192            .repo_loader
193            .create_from(data.operation, data.view, data.index);
194        self.closed = true;
195        repo
196    }
197}
198
199impl Drop for UnpublishedOperation {
200    fn drop(&mut self) {
201        if !self.closed && !std::thread::panicking() {
202            eprintln!("BUG: UnpublishedOperation was dropped without being closed.");
203        }
204    }
205}