just playing with tangled
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 itertools::Itertools as _;
20
21use crate::backend::Timestamp;
22use crate::dag_walk;
23use crate::index::ReadonlyIndex;
24use crate::op_heads_store::OpHeadsStore;
25use crate::op_heads_store::OpHeadsStoreError;
26use crate::op_store;
27use crate::op_store::OperationMetadata;
28use crate::operation::Operation;
29use crate::repo::MutableRepo;
30use crate::repo::ReadonlyRepo;
31use crate::repo::Repo as _;
32use crate::repo::RepoLoader;
33use crate::repo::RepoLoaderError;
34use crate::settings::UserSettings;
35use crate::view::View;
36
37/// An in-memory representation of a repo and any changes being made to it.
38///
39/// Within the scope of a transaction, changes to the repository are made
40/// in-memory to `mut_repo` and published to the repo backend when
41/// [`Transaction::commit`] is called. When a transaction is committed, it
42/// becomes atomically visible as an Operation in the op log that represents the
43/// transaction itself, and as a View that represents the state of the repo
44/// after the transaction. This is similar to how a Commit represents a change
45/// to the contents of the repository and a Tree represents the repository's
46/// contents after the change. See the documentation for [`op_store::Operation`]
47/// and [`op_store::View`] for more information.
48pub struct Transaction {
49 mut_repo: MutableRepo,
50 parent_ops: Vec<Operation>,
51 op_metadata: OperationMetadata,
52 end_time: Option<Timestamp>,
53}
54
55impl Transaction {
56 pub fn new(mut_repo: MutableRepo, user_settings: &UserSettings) -> Transaction {
57 let parent_ops = vec![mut_repo.base_repo().operation().clone()];
58 let op_metadata = create_op_metadata(user_settings, "".to_string(), false);
59 let end_time = user_settings.operation_timestamp();
60 Transaction {
61 mut_repo,
62 parent_ops,
63 op_metadata,
64 end_time,
65 }
66 }
67
68 pub fn base_repo(&self) -> &Arc<ReadonlyRepo> {
69 self.mut_repo.base_repo()
70 }
71
72 pub fn set_tag(&mut self, key: String, value: String) {
73 self.op_metadata.tags.insert(key, value);
74 }
75
76 pub fn repo(&self) -> &MutableRepo {
77 &self.mut_repo
78 }
79
80 pub fn repo_mut(&mut self) -> &mut MutableRepo {
81 &mut self.mut_repo
82 }
83
84 pub fn merge_operation(&mut self, other_op: Operation) -> Result<(), RepoLoaderError> {
85 let ancestor_op = dag_walk::closest_common_node_ok(
86 self.parent_ops.iter().cloned().map(Ok),
87 [Ok(other_op.clone())],
88 |op: &Operation| op.id().clone(),
89 |op: &Operation| op.parents().collect_vec(),
90 )?
91 .unwrap();
92 let repo_loader = self.base_repo().loader();
93 let base_repo = repo_loader.load_at(&ancestor_op)?;
94 let other_repo = repo_loader.load_at(&other_op)?;
95 self.parent_ops.push(other_op);
96 let merged_repo = self.repo_mut();
97 merged_repo.merge(&base_repo, &other_repo)?;
98 Ok(())
99 }
100
101 pub fn set_is_snapshot(&mut self, is_snapshot: bool) {
102 self.op_metadata.is_snapshot = is_snapshot;
103 }
104
105 /// Writes the transaction to the operation store and publishes it.
106 pub fn commit(
107 self,
108 description: impl Into<String>,
109 ) -> Result<Arc<ReadonlyRepo>, OpHeadsStoreError> {
110 self.write(description).publish()
111 }
112
113 /// Writes the transaction to the operation store, but does not publish it.
114 /// That means that a repo can be loaded at the operation, but the
115 /// operation will not be seen when loading the repo at head.
116 pub fn write(mut self, description: impl Into<String>) -> UnpublishedOperation {
117 let mut_repo = self.mut_repo;
118 // TODO: Should we instead just do the rebasing here if necessary?
119 assert!(
120 !mut_repo.has_rewrites(),
121 "BUG: Descendants have not been rebased after the last rewrites."
122 );
123 let base_repo = mut_repo.base_repo().clone();
124 let (mut_index, view) = mut_repo.consume();
125
126 let view_id = base_repo.op_store().write_view(view.store_view()).unwrap();
127 self.op_metadata.description = description.into();
128 self.op_metadata.end_time = self.end_time.unwrap_or_else(Timestamp::now);
129 let parents = self.parent_ops.iter().map(|op| op.id().clone()).collect();
130 let store_operation = op_store::Operation {
131 view_id,
132 parents,
133 metadata: self.op_metadata,
134 };
135 let new_op_id = base_repo
136 .op_store()
137 .write_operation(&store_operation)
138 .unwrap();
139 let operation = Operation::new(base_repo.op_store().clone(), new_op_id, store_operation);
140
141 let index = base_repo
142 .index_store()
143 .write_index(mut_index, &operation)
144 .unwrap();
145 UnpublishedOperation::new(base_repo.loader(), operation, view, index)
146 }
147}
148
149pub fn create_op_metadata(
150 user_settings: &UserSettings,
151 description: String,
152 is_snapshot: bool,
153) -> OperationMetadata {
154 let start_time = user_settings
155 .operation_timestamp()
156 .unwrap_or_else(Timestamp::now);
157 let end_time = start_time;
158 let hostname = user_settings.operation_hostname().to_owned();
159 let username = user_settings.operation_username().to_owned();
160 OperationMetadata {
161 start_time,
162 end_time,
163 description,
164 hostname,
165 username,
166 is_snapshot,
167 tags: Default::default(),
168 }
169}
170
171/// An unpublished operation in the store.
172///
173/// An Operation which has been written to the operation store but not
174/// published. The repo can be loaded at an unpublished Operation, but the
175/// Operation will not be visible in the op log if the repo is loaded at head.
176///
177/// Either [`Self::publish`] or [`Self::leave_unpublished`] must be called to
178/// finish the operation.
179#[must_use = "Either publish() or leave_unpublished() must be called to finish the operation."]
180pub struct UnpublishedOperation {
181 op_heads_store: Arc<dyn OpHeadsStore>,
182 repo: Arc<ReadonlyRepo>,
183}
184
185impl UnpublishedOperation {
186 fn new(
187 repo_loader: &RepoLoader,
188 operation: Operation,
189 view: View,
190 index: Box<dyn ReadonlyIndex>,
191 ) -> Self {
192 UnpublishedOperation {
193 op_heads_store: repo_loader.op_heads_store().clone(),
194 repo: repo_loader.create_from(operation, view, index),
195 }
196 }
197
198 pub fn operation(&self) -> &Operation {
199 self.repo.operation()
200 }
201
202 pub fn publish(self) -> Result<Arc<ReadonlyRepo>, OpHeadsStoreError> {
203 let _lock = self.op_heads_store.lock()?;
204 self.op_heads_store
205 .update_op_heads(self.operation().parent_ids(), self.operation().id())?;
206 Ok(self.repo)
207 }
208
209 pub fn leave_unpublished(self) -> Arc<ReadonlyRepo> {
210 self.repo
211 }
212}