jj/lib/src/transaction.rs
Martin von Zweigbergk 4db3d8d3a6 view: add tracking of "public" heads (copying Mercurial's phase concept)
Mercurial's "phase" concept is important for evolution, and it's also
useful for filtering out uninteresting commits from log
output. Commits are typically marked "public" when they are pushed to
a remote. The CLI prevents public commits from being rewritten. Public
commits cannot be obsolete (even if they have a successor, they won't
be considered obsolete like non-public commits would).

This commits just makes space for tracking the public heads in the
View.
2021-01-16 11:48:35 -08:00

249 lines
9.1 KiB
Rust

// 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::evolution::{Evolution, MutableEvolution, ReadonlyEvolution};
use crate::op_store;
use crate::operation::Operation;
use crate::repo::{ReadonlyRepo, Repo};
use crate::settings::UserSettings;
use crate::store;
use crate::store::{CommitId, Timestamp};
use crate::store_wrapper::StoreWrapper;
use crate::view::{MutableView, ReadonlyView, View};
use std::ops::Deref;
use std::sync::Arc;
pub struct Transaction<'r> {
repo: Option<Arc<MutableRepo<'r>>>,
description: String,
start_time: Timestamp,
closed: bool,
}
pub struct MutableRepo<'r> {
repo: &'r ReadonlyRepo,
view: Option<MutableView>,
evolution: Option<MutableEvolution<'static, 'static>>,
}
impl<'r> Transaction<'r> {
pub fn new(
repo: &'r ReadonlyRepo,
view: &ReadonlyView,
evolution: &ReadonlyEvolution<'r>,
description: &str,
) -> Transaction<'r> {
let mut_view = view.start_modification();
let internal = Arc::new(MutableRepo {
repo,
view: Some(mut_view),
evolution: None,
});
let repo_ref: &MutableRepo = internal.as_ref();
let static_lifetime_repo: &'static MutableRepo = unsafe { std::mem::transmute(repo_ref) };
let mut tx = Transaction {
repo: Some(internal),
description: description.to_owned(),
start_time: Timestamp::now(),
closed: false,
};
let mut_evolution: MutableEvolution<'_, '_> =
evolution.start_modification(static_lifetime_repo);
let static_lifetime_mut_evolution: MutableEvolution<'static, 'static> =
unsafe { std::mem::transmute(mut_evolution) };
Arc::get_mut(tx.repo.as_mut().unwrap()).unwrap().evolution =
Some(static_lifetime_mut_evolution);
tx
}
pub fn base_repo(&self) -> &'r ReadonlyRepo {
self.repo.as_ref().unwrap().repo
}
pub fn store(&self) -> &Arc<StoreWrapper> {
self.repo.as_ref().unwrap().repo.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
.repo
.as_ref()
.unwrap()
.repo
.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(&current_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(&current_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(), &current_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() {
// If the commit is closed, 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 if &tree_id != commit.tree().id() {
// If the commit is open but had conflicts, create a successor with the
// conflicts materialized.
open_commit = CommitBuilder::for_rewrite_from(settings, self.store(), commit)
.set_tree(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.as_mut().unwrap().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.as_mut().unwrap().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.as_mut().unwrap().add_head(head);
mut_repo.evolution.as_mut().unwrap().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.as_mut().unwrap().remove_head(head);
mut_repo.evolution.as_mut().unwrap().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.as_mut().unwrap().add_public_head(head);
mut_repo.evolution.as_mut().unwrap().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.as_mut().unwrap().remove_public_head(head);
mut_repo.evolution.as_mut().unwrap().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
.as_mut()
.unwrap()
.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.as_mut().unwrap().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.as_mut().unwrap().set_view(data);
mut_repo.evolution.as_mut().unwrap().invalidate();
}
pub fn commit(mut self) -> Operation {
let mut_repo = Arc::get_mut(self.repo.as_mut().unwrap()).unwrap();
mut_repo.evolution = None;
let mut internal = Arc::try_unwrap(self.repo.take().unwrap()).ok().unwrap();
let view = internal.view.take().unwrap();
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.");
}
}
}
impl<'r> Repo for MutableRepo<'r> {
fn store(&self) -> &Arc<StoreWrapper> {
self.repo.store()
}
fn view(&self) -> &dyn View {
self.view.as_ref().unwrap()
}
fn evolution(&self) -> &dyn Evolution {
self.evolution.as_ref().unwrap()
}
}
impl<'r> MutableRepo<'r> {
pub fn evolution_mut<'m>(&'m mut self) -> &'m mut MutableEvolution<'r, 'm> {
let evolution: &mut MutableEvolution<'static, 'static> = self.evolution.as_mut().unwrap();
let evolution: &mut MutableEvolution<'r, 'm> = unsafe { std::mem::transmute(evolution) };
evolution
}
}