just playing with tangled
at globpattern 212 lines 7.5 kB view raw
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}