mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-26 22:10:52 +00:00
repo: return error when attempting to load repo where there is none
This commits makes it so that running commands outside a repo results in an error message instead of a panic. We still don't look for a `.jj/` directory in ancestors of the current directory.
This commit is contained in:
parent
86b2c6b464
commit
7494a03081
8 changed files with 64 additions and 19 deletions
|
@ -81,6 +81,12 @@ impl Debug for ReadonlyRepo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum RepoLoadError {
|
||||
#[error("There is no Jujube repo in {0}")]
|
||||
NoRepoHere(PathBuf),
|
||||
}
|
||||
|
||||
impl ReadonlyRepo {
|
||||
pub fn init_local(settings: &UserSettings, wc_path: PathBuf) -> Arc<ReadonlyRepo> {
|
||||
let repo_path = wc_path.join(".jj");
|
||||
|
@ -185,8 +191,15 @@ impl ReadonlyRepo {
|
|||
repo
|
||||
}
|
||||
|
||||
pub fn load(user_settings: &UserSettings, wc_path: PathBuf) -> Arc<ReadonlyRepo> {
|
||||
pub fn load(
|
||||
user_settings: &UserSettings,
|
||||
wc_path: PathBuf,
|
||||
) -> Result<Arc<ReadonlyRepo>, RepoLoadError> {
|
||||
let repo_path = wc_path.join(".jj");
|
||||
// TODO: Check if ancestor directory has a .jj/
|
||||
if !repo_path.is_dir() {
|
||||
return Err(RepoLoadError::NoRepoHere(wc_path));
|
||||
}
|
||||
let store_path = repo_path.join("store");
|
||||
let store: Box<dyn Store>;
|
||||
if store_path.is_dir() {
|
||||
|
@ -225,7 +238,7 @@ impl ReadonlyRepo {
|
|||
let static_lifetime_repo: &'static ReadonlyRepo = unsafe { std::mem::transmute(repo_ref) };
|
||||
let evolution = ReadonlyEvolution::new(static_lifetime_repo);
|
||||
ReadonlyRepo::init_cycles(&mut repo, evolution);
|
||||
repo
|
||||
Ok(repo)
|
||||
}
|
||||
|
||||
fn init_cycles(mut repo: &mut Arc<ReadonlyRepo>, evolution: ReadonlyEvolution<'static>) {
|
||||
|
|
|
@ -106,7 +106,7 @@ fn test_bad_locking_children(use_git: bool) {
|
|||
// Simulate a write of a commit that happens on one machine
|
||||
let machine1_path = TempDir::new().unwrap().into_path();
|
||||
copy_directory(repo.working_copy_path(), &machine1_path);
|
||||
let machine1_repo = ReadonlyRepo::load(&settings, machine1_path);
|
||||
let machine1_repo = ReadonlyRepo::load(&settings, machine1_path).unwrap();
|
||||
let child1 = testutils::create_random_commit(&settings, &machine1_repo)
|
||||
.set_parents(vec![initial.id().clone()])
|
||||
.write_to_new_transaction(&machine1_repo, "test");
|
||||
|
@ -114,7 +114,7 @@ fn test_bad_locking_children(use_git: bool) {
|
|||
// Simulate a write of a commit that happens on another machine
|
||||
let machine2_path = TempDir::new().unwrap().into_path();
|
||||
copy_directory(repo.working_copy_path(), &machine2_path);
|
||||
let machine2_repo = ReadonlyRepo::load(&settings, machine2_path);
|
||||
let machine2_repo = ReadonlyRepo::load(&settings, machine2_path).unwrap();
|
||||
let child2 = testutils::create_random_commit(&settings, &machine2_repo)
|
||||
.set_parents(vec![initial.id().clone()])
|
||||
.write_to_new_transaction(&machine2_repo, "test");
|
||||
|
@ -128,7 +128,7 @@ fn test_bad_locking_children(use_git: bool) {
|
|||
machine2_repo.working_copy_path(),
|
||||
&merged_path,
|
||||
);
|
||||
let merged_repo = ReadonlyRepo::load(&settings, merged_path);
|
||||
let merged_repo = ReadonlyRepo::load(&settings, merged_path).unwrap();
|
||||
let heads: HashSet<_> = merged_repo.view().heads().cloned().collect();
|
||||
assert!(heads.contains(child1.id()));
|
||||
assert!(heads.contains(child2.id()));
|
||||
|
@ -170,10 +170,10 @@ fn test_bad_locking_interrupted(use_git: bool) {
|
|||
|
||||
copy_directory(&backup_path, &op_heads_dir);
|
||||
// Reload the repo and check that only the new head is present.
|
||||
let reloaded_repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone());
|
||||
let reloaded_repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone()).unwrap();
|
||||
assert_eq!(reloaded_repo.view().base_op_head_id(), &op_head_id);
|
||||
// Reload once more to make sure that the view/op_heads/ directory was updated
|
||||
// correctly.
|
||||
let reloaded_repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone());
|
||||
let reloaded_repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone()).unwrap();
|
||||
assert_eq!(reloaded_repo.view().base_op_head_id(), &op_head_id);
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ fn test_commit_parallel_instances(use_git: bool) {
|
|||
let mut threads = vec![];
|
||||
for _ in 0..100 {
|
||||
let settings = settings.clone();
|
||||
let repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone());
|
||||
let repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone()).unwrap();
|
||||
let handle = thread::spawn(move || {
|
||||
testutils::create_random_commit(&settings, &repo)
|
||||
.write_to_new_transaction(&repo, "test");
|
||||
|
@ -93,7 +93,7 @@ fn test_commit_parallel_instances(use_git: bool) {
|
|||
}
|
||||
// One commit per thread plus the commit from the initial checkout on top of the
|
||||
// root commit
|
||||
let repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone());
|
||||
let repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone()).unwrap();
|
||||
assert_eq!(repo.view().heads().count(), 101);
|
||||
|
||||
// One operation for initializing the repo (containing the root id and the
|
||||
|
|
|
@ -144,7 +144,7 @@ fn test_fetch_success() {
|
|||
let new_git_commit = empty_git_commit(&git_repo, "refs/heads/main", &[&initial_git_commit]);
|
||||
|
||||
// The new commit is not visible before git::fetch().
|
||||
let jj_repo = ReadonlyRepo::load(&settings, jj_repo_dir.clone());
|
||||
let jj_repo = ReadonlyRepo::load(&settings, jj_repo_dir.clone()).unwrap();
|
||||
let heads: HashSet<_> = jj_repo.view().heads().cloned().collect();
|
||||
assert!(!heads.contains(&commit_id(&new_git_commit)));
|
||||
|
||||
|
|
|
@ -275,7 +275,7 @@ fn test_index_commits_previous_operations(use_git: bool) {
|
|||
std::fs::remove_dir_all(&index_operations_dir).unwrap();
|
||||
std::fs::create_dir(&index_operations_dir).unwrap();
|
||||
|
||||
let repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone());
|
||||
let repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone()).unwrap();
|
||||
let index = repo.index();
|
||||
let index = index.as_composite();
|
||||
// There should be the root commit and the working copy commit, plus
|
||||
|
@ -323,7 +323,7 @@ fn test_index_commits_incremental(use_git: bool) {
|
|||
let commit_c = child_commit(&settings, &repo, &commit_b).write_to_transaction(&mut tx);
|
||||
tx.commit();
|
||||
|
||||
let repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone());
|
||||
let repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone()).unwrap();
|
||||
let index = repo.index();
|
||||
let index = index.as_composite();
|
||||
// There should be the root commit and the working copy commit, plus
|
||||
|
@ -370,7 +370,7 @@ fn test_index_commits_incremental_empty_transaction(use_git: bool) {
|
|||
|
||||
repo.start_transaction("test").commit();
|
||||
|
||||
let repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone());
|
||||
let repo = ReadonlyRepo::load(&settings, repo.working_copy_path().clone()).unwrap();
|
||||
let index = repo.index();
|
||||
let index = index.as_composite();
|
||||
// There should be the root commit and the working copy commit, plus
|
||||
|
|
26
lib/tests/test_load_repo.rs
Normal file
26
lib/tests/test_load_repo.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2021 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 jujube_lib::repo::{ReadonlyRepo, RepoLoadError};
|
||||
use jujube_lib::testutils;
|
||||
|
||||
#[test]
|
||||
fn test_load_bad_path() {
|
||||
let settings = testutils::user_settings();
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let wc_path = temp_dir.path().to_owned();
|
||||
// We haven't created a repo in the wc_path, so it should fail to load.
|
||||
let result = ReadonlyRepo::load(&settings, wc_path.clone());
|
||||
assert_eq!(result.err(), Some(RepoLoadError::NoRepoHere(wc_path)));
|
||||
}
|
|
@ -47,7 +47,7 @@ fn test_concurrent_checkout(use_git: bool) {
|
|||
wc1.check_out(commit1).unwrap();
|
||||
|
||||
// Check out commit2 from another process (simulated by another repo instance)
|
||||
let repo2 = ReadonlyRepo::load(&settings, repo1.working_copy_path().clone());
|
||||
let repo2 = ReadonlyRepo::load(&settings, repo1.working_copy_path().clone()).unwrap();
|
||||
repo2
|
||||
.working_copy_locked()
|
||||
.check_out(commit2.clone())
|
||||
|
@ -60,7 +60,7 @@ fn test_concurrent_checkout(use_git: bool) {
|
|||
);
|
||||
|
||||
// Check that the commit2 is still checked out on disk.
|
||||
let repo3 = ReadonlyRepo::load(&settings, repo1.working_copy_path().clone());
|
||||
let repo3 = ReadonlyRepo::load(&settings, repo1.working_copy_path().clone()).unwrap();
|
||||
assert_eq!(
|
||||
repo3.working_copy_locked().current_tree_id(),
|
||||
commit2.tree().id().clone()
|
||||
|
@ -80,7 +80,7 @@ fn test_concurrent_commit(use_git: bool) {
|
|||
let commit1 = wc1.current_commit();
|
||||
|
||||
// Commit from another process (simulated by another repo instance)
|
||||
let mut repo2 = ReadonlyRepo::load(&settings, repo1.working_copy_path().clone());
|
||||
let mut repo2 = ReadonlyRepo::load(&settings, repo1.working_copy_path().clone()).unwrap();
|
||||
testutils::write_working_copy_file(&repo2, &FileRepoPath::from("file2"), "contents2");
|
||||
let owned_wc2 = repo2.working_copy().clone();
|
||||
let wc2 = owned_wc2.lock().unwrap();
|
||||
|
@ -132,7 +132,7 @@ fn test_checkout_parallel(use_git: bool) {
|
|||
let settings = settings.clone();
|
||||
let working_copy_path = repo.working_copy_path().clone();
|
||||
let handle = thread::spawn(move || {
|
||||
let mut repo = ReadonlyRepo::load(&settings, working_copy_path);
|
||||
let mut repo = ReadonlyRepo::load(&settings, working_copy_path).unwrap();
|
||||
let owned_wc = repo.working_copy().clone();
|
||||
let wc = owned_wc.lock().unwrap();
|
||||
let commit = repo.store().get_commit(&commit_id).unwrap();
|
||||
|
|
|
@ -41,7 +41,7 @@ use jujube_lib::files;
|
|||
use jujube_lib::files::DiffLine;
|
||||
use jujube_lib::git;
|
||||
use jujube_lib::op_store::{OpStoreError, OperationId};
|
||||
use jujube_lib::repo::{ReadonlyRepo, Repo};
|
||||
use jujube_lib::repo::{ReadonlyRepo, Repo, RepoLoadError};
|
||||
use jujube_lib::repo_path::RepoPath;
|
||||
use jujube_lib::rewrite::{back_out_commit, merge_commit_trees, rebase_commit};
|
||||
use jujube_lib::settings::UserSettings;
|
||||
|
@ -82,10 +82,16 @@ impl From<git2::Error> for CommandError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<RepoLoadError> for CommandError {
|
||||
fn from(err: RepoLoadError) -> Self {
|
||||
CommandError::UserError(format!("Failed to load repo: {}", err))
|
||||
}
|
||||
}
|
||||
|
||||
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 mut repo = ReadonlyRepo::load(ui.settings(), repo_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);
|
||||
|
|
Loading…
Reference in a new issue