// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use crate::commit::Commit; use crate::commit_builder::CommitBuilder; use crate::conflicts; use crate::op_store; use crate::operation::Operation; use crate::repo::{MutableRepo, ReadonlyRepo, Repo}; use crate::settings::UserSettings; use crate::store; use crate::store::{CommitId, Timestamp}; use crate::store_wrapper::StoreWrapper; use std::ops::Deref; use std::sync::Arc; pub struct Transaction<'r> { repo: Option>>, description: String, start_time: Timestamp, closed: bool, } impl<'r> Transaction<'r> { pub fn new(mut_repo: Arc>, description: &str) -> Transaction<'r> { Transaction { repo: Some(mut_repo), description: description.to_owned(), start_time: Timestamp::now(), closed: false, } } pub fn base_repo(&self) -> &'r ReadonlyRepo { self.repo.as_ref().unwrap().base_repo() } pub fn store(&self) -> &Arc { self.repo.as_ref().unwrap().store() } pub fn as_repo<'a: 'r>(&'a self) -> &(impl Repo + 'a) { self.repo.as_ref().unwrap().deref() } pub fn as_repo_mut(&mut self) -> &mut MutableRepo<'r> { Arc::get_mut(self.repo.as_mut().unwrap()).unwrap() } pub fn write_commit(&mut self, commit: store::Commit) -> Commit { let commit = self.store().write_commit(commit); self.add_head(&commit); commit } pub fn check_out(&mut self, settings: &UserSettings, commit: &Commit) -> Commit { let current_checkout_id = self.as_repo().view().checkout().clone(); let current_checkout = self.store().get_commit(¤t_checkout_id).unwrap(); assert!(current_checkout.is_open(), "current checkout is closed"); if current_checkout.is_empty() && !(current_checkout.is_pruned() || self.as_repo().evolution().is_obsolete(¤t_checkout_id)) { // Prune the checkout we're leaving if it's empty. // TODO: Also prune it if the only changes are conflicts that got materialized. CommitBuilder::for_rewrite_from(settings, self.store(), ¤t_checkout) .set_pruned(true) .write_to_transaction(self); } let store = self.store(); // Create a new tree with any conflicts resolved. let mut tree_builder = store.tree_builder(commit.tree().id().clone()); for (path, conflict_id) in commit.tree().conflicts() { let conflict = store.read_conflict(&conflict_id).unwrap(); let materialized_value = conflicts::conflict_to_materialized_value(store, &path, &conflict); tree_builder.set(path, materialized_value); } let tree_id = tree_builder.write_tree(); let open_commit; if !commit.is_open() || &tree_id != commit.tree().id() { // If the commit is closed, or if it had conflicts, create a new open commit on // top open_commit = CommitBuilder::for_open_commit( settings, self.store(), commit.id().clone(), tree_id, ) .write_to_transaction(self); } else { // Otherwise the commit was open and didn't have any conflicts, so just use // that commit as is. open_commit = commit.clone(); } let id = open_commit.id().clone(); let mut_repo = Arc::get_mut(self.repo.as_mut().unwrap()).unwrap(); mut_repo.view_mut().set_checkout(id); open_commit } pub fn set_checkout(&mut self, id: CommitId) { let mut_repo = Arc::get_mut(self.repo.as_mut().unwrap()).unwrap(); mut_repo.view_mut().set_checkout(id); } pub fn add_head(&mut self, head: &Commit) { let mut_repo = Arc::get_mut(self.repo.as_mut().unwrap()).unwrap(); // TODO: Also add other ancestors that were not already known. For example, when // the user creates a new repo backed by a git repo and they then check // out some commit, we'll need add all ancestors of that commit. This // code probably needs to be restructured a bit. Perhaps it should be // higher-level code that makes sure unknown ancestors are also added. mut_repo.view_mut().add_head(head); mut_repo.evolution_mut().add_commit(head); } pub fn remove_head(&mut self, head: &Commit) { let mut_repo = Arc::get_mut(self.repo.as_mut().unwrap()).unwrap(); mut_repo.view_mut().remove_head(head); mut_repo.evolution_mut().invalidate(); } pub fn add_public_head(&mut self, head: &Commit) { let mut_repo = Arc::get_mut(self.repo.as_mut().unwrap()).unwrap(); mut_repo.view_mut().add_public_head(head); mut_repo.evolution_mut().add_commit(head); } pub fn remove_public_head(&mut self, head: &Commit) { let mut_repo = Arc::get_mut(self.repo.as_mut().unwrap()).unwrap(); mut_repo.view_mut().remove_public_head(head); mut_repo.evolution_mut().invalidate(); } pub fn insert_git_ref(&mut self, name: String, commit_id: CommitId) { let mut_repo = Arc::get_mut(self.repo.as_mut().unwrap()).unwrap(); mut_repo.view_mut().insert_git_ref(name, commit_id); } pub fn remove_git_ref(&mut self, name: &str) { let mut_repo = Arc::get_mut(self.repo.as_mut().unwrap()).unwrap(); mut_repo.view_mut().remove_git_ref(name); } pub fn set_view(&mut self, data: op_store::View) { let mut_repo = Arc::get_mut(self.repo.as_mut().unwrap()).unwrap(); mut_repo.view_mut().set_view(data); mut_repo.evolution_mut().invalidate(); } pub fn commit(mut self) -> Operation { let mut_repo = Arc::try_unwrap(self.repo.take().unwrap()).ok().unwrap(); let view = mut_repo.take_view(); let operation = view.save(self.description.clone(), self.start_time.clone()); self.closed = true; operation } pub fn discard(mut self) { self.closed = true; } } impl<'r> Drop for Transaction<'r> { fn drop(&mut self) { if !std::thread::panicking() { assert!(self.closed, "Transaction was dropped without being closed."); } } }