init: add support for creating new repo backed by bare git repo in .jj/git/

It's annoying to have to have the Git repo and Jujube repo in separate
directories. This commit adds `jj init --git`, which creates a new
Jujube repo with an empty, bare git repo in `.jj/git/`. Hopefully the
`jj git` subcommands will eventually provide enough functionality for
working with the Git repo that the user won't have to use Git commands
directly. If they still do, they can run them from inside `.jj/git/`,
or create a new worktree based on that bare repo.

The implementation is quite straight-forward. One thing to note is
that I made `.jj/store` support relative paths to the Git repo. That's
mostly so the Jujube repo can be moved around freely.
This commit is contained in:
Martin von Zweigbergk 2020-12-28 00:26:37 -08:00
parent e82197d981
commit a8a9f7dedd
6 changed files with 116 additions and 7 deletions

View file

@ -91,7 +91,22 @@ impl ReadonlyRepo {
ReadonlyRepo::init(settings, repo_path, wc_path, store)
}
pub fn init_git(
/// Initializes a repo with a new Git store in .jj/git/ (bare Git repo)
pub fn init_internal_git(settings: &UserSettings, wc_path: PathBuf) -> Arc<ReadonlyRepo> {
let repo_path = wc_path.join(".jj");
fs::create_dir(repo_path.clone()).unwrap();
let git_store_path = repo_path.join("git");
git2::Repository::init_bare(&git_store_path).unwrap();
let store_path = repo_path.join("store");
let git_store_path = fs::canonicalize(git_store_path).unwrap();
let mut store_file = File::create(store_path).unwrap();
store_file.write_all(b"git: git").unwrap();
let store = Box::new(GitStore::load(git_store_path));
ReadonlyRepo::init(settings, repo_path, wc_path, store)
}
/// Initializes a repo with an existing Git store at the specified path
pub fn init_external_git(
settings: &UserSettings,
wc_path: PathBuf,
git_store_path: PathBuf,
@ -102,7 +117,7 @@ impl ReadonlyRepo {
let git_store_path = fs::canonicalize(git_store_path).unwrap();
let mut store_file = File::create(store_path).unwrap();
store_file
.write_all((String::from("git: ") + git_store_path.to_str().unwrap()).as_bytes())
.write_all(format!("git: {}", git_store_path.to_str().unwrap()).as_bytes())
.unwrap();
let store = Box::new(GitStore::load(git_store_path));
ReadonlyRepo::init(settings, repo_path, wc_path, store)
@ -183,7 +198,8 @@ impl ReadonlyRepo {
let contents = String::from_utf8(buf).unwrap();
assert!(contents.starts_with("git: "));
let git_store_path_str = contents[5..].to_string();
let git_store_path = PathBuf::from(git_store_path_str);
let git_store_path =
fs::canonicalize(repo_path.join(PathBuf::from(git_store_path_str))).unwrap();
store = Box::new(GitStore::load(git_store_path));
}
let store = StoreWrapper::new(store);

View file

@ -44,7 +44,7 @@ pub fn init_repo(settings: &UserSettings, use_git: bool) -> (TempDir, Arc<Readon
let repo = if use_git {
let git_path = temp_dir.path().join("git-repo");
git2::Repository::init(&git_path).unwrap();
ReadonlyRepo::init_git(&settings, wc_path, git_path)
ReadonlyRepo::init_external_git(&settings, wc_path, git_path)
} else {
ReadonlyRepo::init_local(&settings, wc_path)
};

View file

@ -62,7 +62,7 @@ fn set_up_push_repos(settings: &UserSettings, temp_dir: &TempDir) -> PushTestSet
create_repo_clone(&source_repo_dir, &clone_repo_dir);
std::fs::create_dir(&jj_repo_dir).unwrap();
let mut jj_repo =
ReadonlyRepo::init_git(&settings, jj_repo_dir.clone(), clone_repo_dir.clone());
ReadonlyRepo::init_external_git(&settings, jj_repo_dir.clone(), clone_repo_dir.clone());
let new_commit = testutils::create_random_commit(&settings, &jj_repo)
.set_parents(vec![initial_commit_id.clone()])
.write_to_new_transaction(&jj_repo, "test");

67
lib/tests/test_init.rs Normal file
View file

@ -0,0 +1,67 @@
// 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 jj_lib::repo::ReadonlyRepo;
use jj_lib::testutils;
#[test]
fn test_init_local() {
let settings = testutils::user_settings();
let temp_dir = tempfile::tempdir().unwrap();
let wc_path = temp_dir.path().to_owned();
let repo = ReadonlyRepo::init_local(&settings, wc_path.clone());
assert!(repo.store().git_repo().is_none());
assert_eq!(repo.working_copy_path(), &wc_path);
assert_eq!(repo.repo_path(), &wc_path.join(".jj"));
// Just test that we write a commit to the store
let mut tx = repo.start_transaction("test");
testutils::create_random_commit(&settings, &repo).write_to_transaction(&mut tx);
tx.discard();
}
#[test]
fn test_init_internal_git() {
let settings = testutils::user_settings();
let temp_dir = tempfile::tempdir().unwrap();
let wc_path = temp_dir.path().to_owned();
let repo = ReadonlyRepo::init_internal_git(&settings, wc_path.clone());
assert!(repo.store().git_repo().is_some());
assert_eq!(repo.working_copy_path(), &wc_path);
assert_eq!(repo.repo_path(), &wc_path.join(".jj"));
// Just test that we write a commit to the store
let mut tx = repo.start_transaction("test");
testutils::create_random_commit(&settings, &repo).write_to_transaction(&mut tx);
tx.discard();
}
#[test]
fn test_init_external_git() {
let settings = testutils::user_settings();
let temp_dir = tempfile::tempdir().unwrap();
let git_repo_path = temp_dir.path().join("git");
git2::Repository::init(&git_repo_path).unwrap();
let wc_path = temp_dir.path().join("jj");
std::fs::create_dir(&wc_path).unwrap();
let repo = ReadonlyRepo::init_external_git(&settings, wc_path.clone(), git_repo_path);
assert!(repo.store().git_repo().is_some());
assert_eq!(repo.working_copy_path(), &wc_path);
assert_eq!(repo.repo_path(), &wc_path.join(".jj"));
// Just test that we write a commit to the store
let mut tx = repo.start_transaction("test");
testutils::create_random_commit(&settings, &repo).write_to_transaction(&mut tx);
tx.discard();
}

View file

@ -270,6 +270,7 @@ fn get_app<'a, 'b>() -> App<'a, 'b> {
let init_command = SubCommand::with_name("init")
.about("initialize a repo")
.arg(Arg::with_name("destination").index(1).default_value("."))
.arg(Arg::with_name("git").long("git"))
.arg(
Arg::with_name("git-store")
.long("git-store")
@ -541,6 +542,11 @@ fn cmd_init(
_matches: &ArgMatches,
sub_matches: &ArgMatches,
) -> Result<(), CommandError> {
if sub_matches.is_present("git") && sub_matches.is_present("git-store") {
return Err(CommandError::UserError(String::from(
"--git cannot be used with --git-store",
)));
}
let wc_path_str = sub_matches.value_of("destination").unwrap();
let wc_path = ui.cwd().join(wc_path_str);
if wc_path.exists() {
@ -552,7 +558,9 @@ fn cmd_init(
let repo;
if let Some(git_store_str) = sub_matches.value_of("git-store") {
let git_store_path = ui.cwd().join(git_store_str);
repo = ReadonlyRepo::init_git(ui.settings(), wc_path, git_store_path);
repo = ReadonlyRepo::init_external_git(ui.settings(), wc_path, git_store_path);
} else if sub_matches.is_present("git") {
repo = ReadonlyRepo::init_internal_git(ui.settings(), wc_path);
} else {
repo = ReadonlyRepo::init_local(ui.settings(), wc_path);
}

View file

@ -15,7 +15,25 @@
use jj::testutils;
#[test]
fn test_init_git() {
fn test_init_git_internal() {
let temp_dir = tempfile::tempdir().unwrap();
let output = testutils::CommandRunner::new(temp_dir.path()).run(vec!["init", "repo", "--git"]);
assert_eq!(output.status, 0);
let repo_path = temp_dir.path().join("repo");
assert!(repo_path.is_dir());
assert!(repo_path.join(".jj").is_dir());
assert!(repo_path.join(".jj").join("git").is_dir());
let store_file_contents = std::fs::read_to_string(repo_path.join(".jj").join("store")).unwrap();
assert_eq!(store_file_contents, "git: git");
assert_eq!(
output.stdout_string(),
format!("Initialized repo in \"{}\"\n", repo_path.to_str().unwrap())
);
}
#[test]
fn test_init_git_external() {
let temp_dir = tempfile::tempdir().unwrap();
let git_repo_path = temp_dir.path().join("git-repo");
git2::Repository::init(git_repo_path.clone()).unwrap();