From 602b44258e784383a7dabaa24f70ea7df495f258 Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Fri, 3 Nov 2023 21:30:08 +0900 Subject: [PATCH] workspace: add function that initializes colocated git repository One less git2 API use in CLI. The function name GitBackend::init_colocated() is a bit odd, but we need to specify the work-tree path, not the ".git" repo path. So we can't eliminate the notion of the working copy path anyway. --- cli/src/commands/git.rs | 3 +-- lib/src/git_backend.rs | 23 +++++++++++++++++++++++ lib/src/workspace.rs | 26 ++++++++++++++++++++++++++ lib/tests/test_init.rs | 25 +++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/cli/src/commands/git.rs b/cli/src/commands/git.rs index 3576ee657..8ac695926 100644 --- a/cli/src/commands/git.rs +++ b/cli/src/commands/git.rs @@ -519,8 +519,7 @@ fn do_git_clone( wc_path: &Path, ) -> Result<(WorkspaceCommandHelper, GitFetchStats), CommandError> { let (workspace, repo) = if colocate { - let git_repo = git2::Repository::init(wc_path)?; - Workspace::init_external_git(command.settings(), wc_path, git_repo.path())? + Workspace::init_colocated_git(command.settings(), wc_path)? } else { Workspace::init_internal_git(command.settings(), wc_path)? }; diff --git a/lib/src/git_backend.rs b/lib/src/git_backend.rs index 984d0d847..7c4c3792e 100644 --- a/lib/src/git_backend.rs +++ b/lib/src/git_backend.rs @@ -136,6 +136,29 @@ impl GitBackend { Self::init_with_repo(store_path, git_repo_path, git_repo) } + /// Initializes backend by creating a new Git repo at the specified + /// workspace path. The workspace directory must exist. + pub fn init_colocated( + store_path: &Path, + workspace_root: &Path, + ) -> Result> { + let canonical_workspace_root = { + let path = store_path.join(workspace_root); + path.canonicalize() + .context(&path) + .map_err(GitBackendInitError::Path)? + }; + let git_repo = gix::ThreadSafeRepository::init( + canonical_workspace_root, + gix::create::Kind::WithWorktree, + gix::create::Options::default(), + ) + .map_err(GitBackendInitError::InitRepository)?; + let git_repo_path = workspace_root.join(".git"); + Self::init_with_repo(store_path, &git_repo_path, git_repo) + } + + /// Initializes backend with an existing Git repo at the specified path. pub fn init_external( store_path: &Path, git_repo_path: &Path, diff --git a/lib/src/workspace.rs b/lib/src/workspace.rs index 46c3a4018..84b3b034d 100644 --- a/lib/src/workspace.rs +++ b/lib/src/workspace.rs @@ -160,6 +160,32 @@ impl Workspace { Self::init_with_backend(user_settings, workspace_root, backend_initializer) } + /// Initializes a workspace with a new Git backend and Git repo that shares + /// the same working copy. + pub fn init_colocated_git( + user_settings: &UserSettings, + workspace_root: &Path, + ) -> Result<(Self, Arc), WorkspaceInitError> { + let backend_initializer = { + let workspace_root = workspace_root.to_owned(); + move |store_path: &Path| -> Result, BackendInitError> { + // TODO: Clean up path normalization. store_path is canonicalized by + // ReadonlyRepo::init(). workspace_root will be canonicalized by + // Workspace::new(), but it's not yet here. + let store_relative_workspace_root = + if let Ok(workspace_root) = workspace_root.canonicalize() { + file_util::relative_path(store_path, &workspace_root) + } else { + workspace_root.to_owned() + }; + let backend = + GitBackend::init_colocated(store_path, &store_relative_workspace_root)?; + Ok(Box::new(backend)) + } + }; + Self::init_with_backend(user_settings, workspace_root, &backend_initializer) + } + /// Initializes a workspace with an existing Git repo at the specified path. pub fn init_external_git( user_settings: &UserSettings, diff --git a/lib/tests/test_init.rs b/lib/tests/test_init.rs index 7e1bc0656..eb6f86b42 100644 --- a/lib/tests/test_init.rs +++ b/lib/tests/test_init.rs @@ -75,6 +75,31 @@ fn test_init_internal_git() { write_random_commit(tx.mut_repo(), &settings); } +#[test] +fn test_init_colocated_git() { + let settings = testutils::user_settings(); + let temp_dir = testutils::new_temp_dir(); + let (canonical, uncanonical) = canonicalize(temp_dir.path()); + let (workspace, repo) = Workspace::init_colocated_git(&settings, &uncanonical).unwrap(); + let git_backend = repo + .store() + .backend_impl() + .downcast_ref::() + .unwrap(); + assert_eq!(repo.repo_path(), &canonical.join(".jj").join("repo")); + assert_eq!(workspace.workspace_root(), &canonical); + assert_eq!(git_backend.git_repo_path(), canonical.join(".git")); + assert_eq!(git_backend.git_workdir(), Some(canonical.as_ref())); + assert_eq!( + std::fs::read_to_string(repo.repo_path().join("store").join("git_target")).unwrap(), + "../../../.git" + ); + + // Just test that we can write a commit to the store + let mut tx = repo.start_transaction(&settings, "test"); + write_random_commit(tx.mut_repo(), &settings); +} + #[test] fn test_init_external_git() { let settings = testutils::user_settings();