repo: add support for loading at given operation without loading head op first

The only way to load the repo at a current operation (as with
`--at-op`) is currently to first load it at the head operation and
then call `reload()` on the repo. This patch makes it so we can load
the repo directly at the requested operation.
This commit is contained in:
Martin von Zweigbergk 2021-01-04 09:40:46 -08:00
parent df53871daf
commit 0a4ef1030f
4 changed files with 88 additions and 12 deletions

View file

@ -233,7 +233,14 @@ impl ReadonlyRepo {
user_settings: &UserSettings,
wc_path: PathBuf,
) -> Result<Arc<ReadonlyRepo>, RepoLoadError> {
RepoLoader::init(user_settings, wc_path)?.load()
ReadonlyRepo::loader(user_settings, wc_path)?.load_at_head()
}
pub fn loader(
user_settings: &UserSettings,
wc_path: PathBuf,
) -> Result<RepoLoader, RepoLoadError> {
RepoLoader::init(user_settings, wc_path)
}
pub fn as_repo_ref(&self) -> RepoRef {
@ -381,12 +388,26 @@ impl RepoLoader {
&self.op_store
}
pub fn load(self) -> Result<Arc<ReadonlyRepo>, RepoLoadError> {
pub fn load_at_head(self) -> Result<Arc<ReadonlyRepo>, RepoLoadError> {
let view = ReadonlyView::load(
self.store.clone(),
self.op_store.clone(),
self.repo_path.join("view"),
);
self._finish_load(view)
}
pub fn load_at(self, op: &Operation) -> Result<Arc<ReadonlyRepo>, RepoLoadError> {
let view = ReadonlyView::load_at(
self.store.clone(),
self.op_store.clone(),
self.repo_path.join("view"),
op,
);
self._finish_load(view)
}
fn _finish_load(self, view: ReadonlyView) -> Result<Arc<ReadonlyRepo>, RepoLoadError> {
let working_copy = WorkingCopy::load(
self.store.clone(),
self.wc_path.clone(),

View file

@ -403,6 +403,21 @@ impl ReadonlyView {
}
}
pub fn load_at(
store: Arc<StoreWrapper>,
op_store: Arc<dyn OpStore>,
path: PathBuf,
operation: &Operation,
) -> Self {
ReadonlyView {
store,
path,
op_store,
op_id: operation.id().clone(),
data: operation.view().take_store_view(),
}
}
pub fn reload(&mut self) -> OperationId {
let op_heads_dir = self.path.join("op_heads");
let (op_id, _operation, view) =

View file

@ -14,6 +14,8 @@
use jujube_lib::repo::{ReadonlyRepo, RepoLoadError};
use jujube_lib::testutils;
use std::sync::Arc;
use test_case::test_case;
#[test]
fn test_load_bad_path() {
@ -24,3 +26,31 @@ fn test_load_bad_path() {
let result = ReadonlyRepo::load(&settings, wc_path.clone());
assert_eq!(result.err(), Some(RepoLoadError::NoRepoHere(wc_path)));
}
#[test_case(false ; "local store")]
#[test_case(true ; "git store")]
fn test_load_at_operation(use_git: bool) {
let settings = testutils::user_settings();
let (_temp_dir, mut repo) = testutils::init_repo(&settings, use_git);
let mut tx = repo.start_transaction("add commit");
let commit = testutils::create_random_commit(&settings, &repo).write_to_transaction(&mut tx);
let op = tx.commit();
Arc::get_mut(&mut repo).unwrap().reload();
let mut tx = repo.start_transaction("remove commit");
tx.remove_head(&commit);
tx.commit();
// If we load the repo at head, we should not see the commit since it was
// removed
let loader = ReadonlyRepo::loader(&settings, repo.working_copy_path().clone()).unwrap();
let head_repo = loader.load_at_head().unwrap();
assert!(!head_repo.view().heads().contains(commit.id()));
// If we load the repo at the previous operation, we should see the commit since
// it has not been removed yet
let loader = ReadonlyRepo::loader(&settings, repo.working_copy_path().clone()).unwrap();
let old_repo = loader.load_at(&op).unwrap();
assert!(old_repo.view().heads().contains(commit.id()));
}

View file

@ -40,7 +40,7 @@ use jujube_lib::evolution::EvolveListener;
use jujube_lib::files;
use jujube_lib::files::DiffLine;
use jujube_lib::git;
use jujube_lib::op_store::{OpStoreError, OperationId};
use jujube_lib::op_store::{OpStore, OpStoreError, OperationId};
use jujube_lib::repo::{ReadonlyRepo, RepoLoadError};
use jujube_lib::repo_path::RepoPath;
use jujube_lib::rewrite::{back_out_commit, merge_commit_trees, rebase_commit};
@ -92,14 +92,15 @@ impl From<RepoLoadError> for CommandError {
}
fn get_repo(ui: &Ui, matches: &ArgMatches) -> Result<Arc<ReadonlyRepo>, CommandError> {
let repo_path_str = matches.value_of("repository").unwrap();
let repo_path = ui.cwd().join(repo_path_str);
let mut repo = ReadonlyRepo::load(ui.settings(), repo_path)?;
let wc_path_str = matches.value_of("repository").unwrap();
let wc_path = ui.cwd().join(wc_path_str);
let loader = ReadonlyRepo::loader(ui.settings(), wc_path)?;
if let Some(op_str) = matches.value_of("at_op") {
let op = resolve_single_op(&repo, op_str)?;
Arc::get_mut(&mut repo).unwrap().reload_at(&op);
let op = resolve_single_op_from_store(loader.op_store(), op_str)?;
Ok(loader.load_at(&op)?)
} else {
Ok(loader.load_at_head()?)
}
Ok(repo)
}
fn resolve_commit_id_prefix(
@ -205,10 +206,19 @@ fn resolve_single_op(repo: &ReadonlyRepo, op_str: &str) -> Result<Operation, Com
let view = repo.view();
if op_str == "@" {
Ok(view.as_view_ref().base_op_head())
} else if let Ok(binary_op_id) = hex::decode(op_str) {
} else {
resolve_single_op_from_store(&repo.view().op_store(), op_str)
}
}
fn resolve_single_op_from_store(
op_store: &Arc<dyn OpStore>,
op_str: &str,
) -> Result<Operation, CommandError> {
if let Ok(binary_op_id) = hex::decode(op_str) {
let op_id = OperationId(binary_op_id);
match view.as_view_ref().get_operation(&op_id) {
Ok(operation) => Ok(operation),
match op_store.read_operation(&op_id) {
Ok(operation) => Ok(Operation::new(op_store.clone(), op_id, operation)),
Err(OpStoreError::NotFound) => Err(CommandError::UserError(format!(
"Operation id not found: {}",
op_str