From 212b57c362a912d70ea271e2f383a714b4fb02d2 Mon Sep 17 00:00:00 2001 From: Caleb White Date: Thu, 13 Aug 2026 07:47:48 -0500 Subject: [PATCH] git colocation: support child workspaces `jj git colocation status` now correctly identifies child workspaces backed by Git worktrees (gitlink files) as colocated and includes the workspace name in its output. `jj git colocation enable` and `disable` now work on child workspaces. For child workspaces, `enable` creates a Git worktree and `disable` removes it, allowing colocation to be toggled after workspace creation. The `is_colocated_git_workspace` check now recognizes `.git` files (gitlinks) as indicating a colocated git worktree. --- CHANGELOG.md | 6 + cli/src/commands/git/colocation.rs | 208 +++++++++++++++-------------- cli/src/git_util.rs | 10 +- cli/tests/test_git_colocated.rs | 36 ++--- cli/tests/test_git_colocation.rs | 100 +++++++++++--- cli/tests/test_git_init.rs | 38 +++--- cli/tests/test_workspaces.rs | 8 ++ 7 files changed, 246 insertions(+), 160 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b74e2c3253..c2537701c8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,12 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). config is `true`. `jj workspace forget` removes the corresponding Git worktree when one exists. +* `jj git colocation status`/`enable`/`disable` now work on child + workspaces. `status` correctly reports colocation state and includes + the workspace name. `enable` creates a Git worktree and `disable` + removes it, allowing colocation to be toggled after workspace + creation. + ### Fixed bugs * The default pager flags now include `-K` (`--quit-on-intr`), so pressing diff --git a/cli/src/commands/git/colocation.rs b/cli/src/commands/git/colocation.rs index c3efc3dcebb..83fc529aa67 100644 --- a/cli/src/commands/git/colocation.rs +++ b/cli/src/commands/git/colocation.rs @@ -19,6 +19,7 @@ use itertools::Itertools as _; use jj_lib::commit::Commit; use jj_lib::file_util::IoResultExt as _; use jj_lib::git; +use jj_lib::git::GitSettings; use jj_lib::op_store::RefTarget; use jj_lib::repo::Repo as _; @@ -28,7 +29,9 @@ use crate::command_error::CommandError; use crate::command_error::user_error; use crate::command_error::user_error_with_message; use crate::commands::git::maybe_add_gitignore; +use crate::git_util::create_git_worktree; use crate::git_util::is_colocated_git_workspace; +use crate::git_util::remove_git_worktree; use crate::ui::Ui; /// Show the current colocation status @@ -71,24 +74,6 @@ pub async fn cmd_git_colocation( } } -/// Check that the repository supports colocation commands -/// which means that the repo is backed by Git and is a main workspace -fn workspace_supports_git_colocation_commands( - workspace_command: &WorkspaceCommandHelper, -) -> Result<(), CommandError> { - // Check if backend is Git (will show an error otherwise) - git::get_git_backend(workspace_command.repo().store())?; - - // Ensure that this is the main workspace - let repo_dir = workspace_command.workspace_root().join(".jj").join("repo"); - if repo_dir.is_file() { - return Err(user_error( - "This command cannot be used in a non-main Jujutsu workspace", - )); - } - Ok(()) -} - async fn cmd_git_colocation_status( ui: &mut Ui, command: &CommandHelper, @@ -96,19 +81,23 @@ async fn cmd_git_colocation_status( ) -> Result<(), CommandError> { let workspace_command = command.workspace_helper(ui).await?; - // Make sure that the workspace supports git colocation commands - workspace_supports_git_colocation_commands(&workspace_command)?; + git::get_git_backend(workspace_command.repo().store())?; let is_colocated = is_colocated_git_workspace(workspace_command.workspace()); let workspace_name = workspace_command.workspace_name(); let git_head = workspace_command.repo().view().git_head(workspace_name); if is_colocated { - writeln!(ui.stdout(), "Workspace is currently colocated with Git.")?; + writeln!( + ui.stdout(), + "Workspace '{}' is currently colocated with Git.", + workspace_name.as_symbol() + )?; } else { writeln!( ui.stdout(), - "Workspace is currently not colocated with Git." + "Workspace '{}' is currently not colocated with Git.", + workspace_name.as_symbol() )?; } @@ -159,72 +148,78 @@ async fn cmd_git_colocation_enable( _args: &GitColocationEnableArgs, ) -> Result<(), CommandError> { let workspace_command = command.workspace_helper(ui).await?; + let git_backend = git::get_git_backend(workspace_command.repo().store())?; - // Make sure that the workspace supports git colocation commands - workspace_supports_git_colocation_commands(&workspace_command)?; - - // Then ensure that the workspace is not already colocated before proceeding if is_colocated_git_workspace(workspace_command.workspace()) { writeln!(ui.status(), "Workspace is already colocated with Git.")?; return Ok(()); } - // And that it has a working copy (whose parent we'll use later to set the git - // HEAD) let wc_commit_id = workspace_command .get_wc_commit_id() .ok_or_else(|| user_error("This command requires a working copy"))? .clone(); - let workspace_root = workspace_command.workspace_root(); - let jj_repo_path = workspace_command.repo_path(); - let git_store_path = jj_repo_path.join("store").join("git"); - let git_target_path = jj_repo_path.join("store").join("git_target"); - let dot_git_path = workspace_root.join(".git"); - - // Move the git repository from .jj/repo/store/git to .git - std::fs::rename(&git_store_path, &dot_git_path).map_err(|err| match err.kind() { - ErrorKind::AlreadyExists | ErrorKind::DirectoryNotEmpty => { - user_error("A .git directory already exists in the workspace root. Cannot colocate.") - } - // An external Git repository (e.g. created by `jj git init - // --git-repo=`) isn't managed by jj and cannot be moved. - // workspace_supports_git_colocation_commands() already ensured that - // the backend is Git. - ErrorKind::NotFound => { - let git_backend = git::get_git_backend(workspace_command.repo().store()).unwrap(); - user_error(format!( + let is_child_workspace = workspace_command + .workspace_root() + .join(".jj") + .join("repo") + .is_file(); + + if is_child_workspace { + let main_workspace_root = git_backend + .git_workdir() + .ok_or_else(|| user_error("Cannot colocate: bare Git repository"))? + .to_owned(); + let git_settings = GitSettings::from_settings(workspace_command.settings())?; + let workspace_root = workspace_command.workspace_root().to_owned(); + + create_git_worktree(ui, &git_settings, &main_workspace_root, &workspace_root)?; + + let mut workspace_command = reload_workspace_helper(ui, command, workspace_command).await?; + let wc_commit = workspace_command + .repo() + .store() + .get_commit_async(&wc_commit_id) + .await?; + set_git_head_to_wc_parent(ui, &mut workspace_command, &wc_commit).await?; + } else { + let workspace_root = workspace_command.workspace_root(); + let jj_repo_path = workspace_command.repo_path(); + let git_store_path = jj_repo_path.join("store").join("git"); + let git_target_path = jj_repo_path.join("store").join("git_target"); + let dot_git_path = workspace_root.join(".git"); + + std::fs::rename(&git_store_path, &dot_git_path).map_err(|err| match err.kind() { + ErrorKind::AlreadyExists | ErrorKind::DirectoryNotEmpty => user_error( + "A .git directory already exists in the workspace root. Cannot colocate.", + ), + ErrorKind::NotFound => user_error(format!( "Cannot colocate a workspace backed by an external Git repository at {}", git_backend.git_repo_path().display() - )) - } - _ => user_error_with_message( - "Failed to move Git repository from .jj/repo/store/git to workspace root directory.", - err, - ), - })?; - - // Update the git_target file to point to the new location of the git repo - let git_target_content = "../../../.git"; - std::fs::write(&git_target_path, git_target_content).context(git_target_path)?; + )), + _ => user_error_with_message( + "Failed to move Git repository from .jj/repo/store/git to workspace root \ + directory.", + err, + ), + })?; - // Then we must make the Git repository non-bare - set_git_repo_bare(&dot_git_path, false)?; + let git_target_content = "../../../.git"; + std::fs::write(&git_target_path, git_target_content).context(git_target_path)?; - // Reload the workspace command helper to ensure it picks up the changes - let mut workspace_command = reload_workspace_helper(ui, command, workspace_command).await?; + set_git_repo_bare(&dot_git_path, false)?; - // Add a .jj/.gitignore file (if needed) to ensure that the colocated Git - // repository does not track Jujutsu's repository - maybe_add_gitignore(&workspace_command)?; + let mut workspace_command = reload_workspace_helper(ui, command, workspace_command).await?; + maybe_add_gitignore(&workspace_command)?; - // Finally, update git HEAD to point to the working-copy commit's parent - let wc_commit = workspace_command - .repo() - .store() - .get_commit_async(&wc_commit_id) - .await?; - set_git_head_to_wc_parent(ui, &mut workspace_command, &wc_commit).await?; + let wc_commit = workspace_command + .repo() + .store() + .get_commit_async(&wc_commit_id) + .await?; + set_git_head_to_wc_parent(ui, &mut workspace_command, &wc_commit).await?; + } writeln!( ui.status(), @@ -240,46 +235,57 @@ async fn cmd_git_colocation_disable( _args: &GitColocationDisableArgs, ) -> Result<(), CommandError> { let workspace_command = command.workspace_helper(ui).await?; + git::get_git_backend(workspace_command.repo().store())?; - // Make sure that the repository supports git colocation commands - workspace_supports_git_colocation_commands(&workspace_command)?; - - // Then ensure that the repo is colocated before proceeding if !is_colocated_git_workspace(workspace_command.workspace()) { writeln!(ui.status(), "Workspace is already not colocated with Git.")?; return Ok(()); } - let workspace_root = workspace_command.workspace_root(); - let dot_jj_path = workspace_root.join(".jj"); - let git_store_path = workspace_command.repo_path().join("store").join("git"); - let git_target_path = workspace_command - .repo_path() - .join("store") - .join("git_target"); - let dot_git_path = workspace_root.join(".git"); - let jj_gitignore_path = dot_jj_path.join(".gitignore"); - - // Move the Git repository from .git into .jj/repo/store/git - std::fs::rename(&dot_git_path, &git_store_path).map_err(|e| { - user_error_with_message("Failed to move Git repository to .jj/repo/store/git", e) - })?; - - // Make the Git repository bare - set_git_repo_bare(&git_store_path, true)?; + let is_child_workspace = workspace_command + .workspace_root() + .join(".jj") + .join("repo") + .is_file(); + + if is_child_workspace { + let git_backend = git::get_git_backend(workspace_command.repo().store())?; + let main_workspace_root = git_backend + .git_workdir() + .ok_or_else(|| user_error("Cannot disable colocation: bare Git repository"))? + .to_owned(); + let git_settings = GitSettings::from_settings(workspace_command.settings())?; + let workspace_root = workspace_command.workspace_root().to_owned(); + + remove_git_worktree(ui, &git_settings, &main_workspace_root, &workspace_root)?; + + let mut workspace_command = reload_workspace_helper(ui, command, workspace_command).await?; + remove_git_head(ui, &mut workspace_command).await?; + } else { + let workspace_root = workspace_command.workspace_root(); + let dot_jj_path = workspace_root.join(".jj"); + let git_store_path = workspace_command.repo_path().join("store").join("git"); + let git_target_path = workspace_command + .repo_path() + .join("store") + .join("git_target"); + let dot_git_path = workspace_root.join(".git"); + let jj_gitignore_path = dot_jj_path.join(".gitignore"); + + std::fs::rename(&dot_git_path, &git_store_path).map_err(|e| { + user_error_with_message("Failed to move Git repository to .jj/repo/store/git", e) + })?; - // Update the git_target file to point to the internal git store - let git_target_content = "git"; - std::fs::write(&git_target_path, git_target_content).context(&git_target_path)?; + set_git_repo_bare(&git_store_path, true)?; - // Remove the .jj/.gitignore file if it exists - std::fs::remove_file(&jj_gitignore_path).ok(); + let git_target_content = "git"; + std::fs::write(&git_target_path, git_target_content).context(&git_target_path)?; - // Reload the workspace command helper to ensure it picks up the changes - let mut workspace_command = reload_workspace_helper(ui, command, workspace_command).await?; + std::fs::remove_file(&jj_gitignore_path).ok(); - // And finally, remove the git HEAD reference - remove_git_head(ui, &mut workspace_command).await?; + let mut workspace_command = reload_workspace_helper(ui, command, workspace_command).await?; + remove_git_head(ui, &mut workspace_command).await?; + } writeln!( ui.status(), diff --git a/cli/src/git_util.rs b/cli/src/git_util.rs index cf043e31084..9870e63fbbb 100644 --- a/cli/src/git_util.rs +++ b/cli/src/git_util.rs @@ -73,9 +73,15 @@ pub fn is_colocated_git_workspace(workspace: &Workspace) -> bool { if git_workdir == workspace.workspace_root() { return true; } - // Colocated workspace should have ".git" directory, file, or symlink. Compare + let dot_git = workspace.workspace_root().join(".git"); + // A .git file (gitlink) indicates a git worktree, making this a colocated + // workspace. + if dot_git.is_file() { + return true; + } + // Colocated workspace should have ".git" directory or symlink. Compare // its parent as the git_workdir might be resolved from the real ".git" path. - let Ok(dot_git_path) = dunce::canonicalize(workspace.workspace_root().join(".git")) else { + let Ok(dot_git_path) = dunce::canonicalize(dot_git) else { return false; }; dunce::canonicalize(git_workdir).ok().as_deref() == dot_git_path.parent() diff --git a/cli/tests/test_git_colocated.rs b/cli/tests/test_git_colocated.rs index 4bde2e178a6..c3a39e4753d 100644 --- a/cli/tests/test_git_colocated.rs +++ b/cli/tests/test_git_colocated.rs @@ -60,7 +60,7 @@ fn test_git_colocated() -> TestResult { @"97358f54806c7cd005ed5ade68a779595efbae7e" ); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: 97358f54806c7cd005ed5ade68a779595efbae7e [EOF] "); @@ -79,7 +79,7 @@ fn test_git_colocated() -> TestResult { @"97358f54806c7cd005ed5ade68a779595efbae7e" ); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: 97358f54806c7cd005ed5ade68a779595efbae7e [EOF] "); @@ -99,7 +99,7 @@ fn test_git_colocated() -> TestResult { @"9dfe8c7005c8dff6078ecdfd953c6bfddc633c90" ); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: 9dfe8c7005c8dff6078ecdfd953c6bfddc633c90 [EOF] "); @@ -190,7 +190,7 @@ fn test_git_colocated_new_wc_commit_when_wc_immutable() { .run_jj(["bookmark", "create", "-r@", "main"]) .success(); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: eb7b8a1f02b8d0915290e1163a3526bfa4e417fa [EOF] "); @@ -231,7 +231,7 @@ fn test_git_colocated_new_wc_commit_when_wc_immutable() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: 1d3e40a35156c275f7a535fdaa50cb5882d500eb [EOF] "); @@ -258,7 +258,7 @@ fn test_git_colocated_update_stale_resets_git_head() { work_dir.write_file("file2", "b\n"); work_dir.run_jj(["status"]).success(); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: df69548750502d54d6f207b500d25da43ed6fe1e [EOF] "); @@ -287,7 +287,7 @@ fn test_git_colocated_update_stale_resets_git_head() { // Git HEAD should point to the amended "parent", not the stale one insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: 239acdab88e2b438ed43a99471a385d3832190ec [EOF] "); @@ -341,7 +341,7 @@ fn test_git_colocated_unborn_bookmark() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: (none) [EOF] "); @@ -369,7 +369,7 @@ fn test_git_colocated_unborn_bookmark() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: (none) [EOF] "); @@ -406,7 +406,7 @@ fn test_git_colocated_unborn_bookmark() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: ff5366846b039b25c6c4998fa74dca821c246243 [EOF] "); @@ -447,7 +447,7 @@ fn test_git_colocated_unborn_bookmark() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: (none) [EOF] "); @@ -481,7 +481,7 @@ fn test_git_colocated_unborn_bookmark() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: bb21bc2dce2af92973fdd6d42686d77bd16bc466 [EOF] "); @@ -882,7 +882,7 @@ fn test_git_colocated_checkout_non_empty_working_copy() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: 97358f54806c7cd005ed5ade68a779595efbae7e [EOF] "); @@ -1239,7 +1239,7 @@ fn test_git_colocated_undo_head_move() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: e8849ae12c709f2321908879bc724fdb2ab8a781 [EOF] "); @@ -1253,7 +1253,7 @@ fn test_git_colocated_undo_head_move() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: (none) [EOF] "); @@ -1269,7 +1269,7 @@ fn test_git_colocated_undo_head_move() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: 23e6e06a7471634da3567ef975fadf883082658f [EOF] "); @@ -1299,7 +1299,7 @@ fn test_git_colocated_undo_head_move() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: e8849ae12c709f2321908879bc724fdb2ab8a781 [EOF] "); @@ -1951,7 +1951,7 @@ fn test_git_colocated_operation_cleanup() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: cf3bb116ded416d9b202e71303f260e504c2eeb9 [EOF] "); diff --git a/cli/tests/test_git_colocation.rs b/cli/tests/test_git_colocation.rs index df1f67065da..ff1c6c3f69c 100644 --- a/cli/tests/test_git_colocation.rs +++ b/cli/tests/test_git_colocation.rs @@ -59,7 +59,7 @@ fn test_git_colocation_enable_success() -> TestResult { // And that there is no Git HEAD yet insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently not colocated with Git. + Workspace 'default' is currently not colocated with Git. Last imported/exported Git HEAD: (none) [EOF] "); @@ -90,7 +90,7 @@ fn test_git_colocation_enable_success() -> TestResult { // Verify that Git HEAD was set correctly insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: e8849ae12c709f2321908879bc724fdb2ab8a781 [EOF] "); @@ -128,7 +128,7 @@ fn test_git_colocation_enable_empty() { // Verify that Git HEAD was set correctly insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: (none) [EOF] "); @@ -209,7 +209,7 @@ fn test_git_colocation_enable_external_git_repo() { // The status hint shouldn't suggest enabling colocation let output = work_dir.run_jj(["git", "colocation", "status"]); insta::assert_snapshot!(output, @" - Workspace is currently not colocated with Git. + Workspace 'default' is currently not colocated with Git. Last imported/exported Git HEAD: (none) [EOF] ------- stderr ------- @@ -243,7 +243,7 @@ fn test_git_colocation_disable_success() { // Verify that Git HEAD is set insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: e8849ae12c709f2321908879bc724fdb2ab8a781 [EOF] "); @@ -275,7 +275,7 @@ fn test_git_colocation_disable_success() { // Verify that Git HEAD was removed correctly insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently not colocated with Git. + Workspace 'default' is currently not colocated with Git. Last imported/exported Git HEAD: (none) [EOF] "); @@ -301,7 +301,7 @@ fn test_git_colocation_disable_empty() { // Verify that Git HEAD is unset insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: (none) [EOF] "); @@ -356,7 +356,7 @@ fn test_git_colocation_status_non_colocated() { // Check status - should show non-colocated let output = work_dir.run_jj(["git", "colocation", "status"]); insta::assert_snapshot!(output, @" - Workspace is currently not colocated with Git. + Workspace 'default' is currently not colocated with Git. Last imported/exported Git HEAD: (none) [EOF] ------- stderr ------- @@ -378,7 +378,7 @@ fn test_git_colocation_status_colocated() { // Check status - should show colocated let output = work_dir.run_jj(["git", "colocation", "status"]); insta::assert_snapshot!(output, @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: (none) [EOF] ------- stderr ------- @@ -400,26 +400,86 @@ fn test_git_colocation_in_secondary_workspace() { let secondary_dir = test_env.work_dir("secondary"); let output = secondary_dir.run_jj(["git", "colocation", "status"]); - insta::assert_snapshot!(output, @" + insta::assert_snapshot!(output, @r#" + Workspace 'secondary' is currently not colocated with Git. + Last imported/exported Git HEAD: (none) + [EOF] ------- stderr ------- - Error: This command cannot be used in a non-main Jujutsu workspace + Hint: To enable colocation, run: `jj git colocation enable` [EOF] - [exit status: 1] - "); + "#); let output = secondary_dir.run_jj(["git", "colocation", "enable"]); - insta::assert_snapshot!(output, @" + insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: This command cannot be used in a non-main Jujutsu workspace + Error: Cannot colocate: bare Git repository [EOF] [exit status: 1] - "); + "#); let output = secondary_dir.run_jj(["git", "colocation", "disable"]); - insta::assert_snapshot!(output, @" + insta::assert_snapshot!(output, @r#" ------- stderr ------- - Error: This command cannot be used in a non-main Jujutsu workspace + Workspace is already not colocated with Git. [EOF] - [exit status: 1] - "); + "#); +} + +#[test] +fn test_git_colocation_enable_disable_child_workspace() { + let test_env = TestEnvironment::default(); + test_env.add_config("git.colocate = true"); + test_env + .run_jj_in(".", ["git", "init", "--colocate", "main"]) + .success(); + let main_dir = test_env.work_dir("main"); + + main_dir.write_file("file", "contents"); + main_dir.run_jj(["commit", "-m", "initial"]).success(); + + main_dir + .run_jj(["workspace", "add", "--no-colocate", "../secondary"]) + .success(); + let secondary_dir = test_env.work_dir("secondary"); + + assert!(!test_env.env_root().join("secondary/.git").exists()); + + let output = secondary_dir.run_jj(["git", "colocation", "status", "--quiet"]); + insta::assert_snapshot!(output, @r#" + Workspace 'secondary' is currently not colocated with Git. + Last imported/exported Git HEAD: (none) + [EOF] + "#); + + let output = secondary_dir.run_jj(["git", "colocation", "enable"]); + insta::assert_snapshot!(output.normalize_backslash(), @r#" + ------- stderr ------- + Created Git worktree for the new workspace. + Workspace successfully converted into a colocated Jujutsu/Git workspace. + [EOF] + "#); + assert!(test_env.env_root().join("secondary/.git").is_file()); + + let output = secondary_dir.run_jj(["git", "colocation", "status", "--quiet"]); + insta::assert_snapshot!(output, @r#" + Workspace 'secondary' is currently colocated with Git. + Last imported/exported Git HEAD: 7b22a8cbe888adcb4d5ff6dd46a38049e870c6ab + [EOF] + "#); + + let output = secondary_dir.run_jj(["git", "colocation", "disable"]); + insta::assert_snapshot!(output.normalize_backslash(), @r#" + ------- stderr ------- + Removed Git worktree for "$TEST_ENV/secondary". + Workspace successfully converted into a non-colocated Jujutsu/Git workspace. + [EOF] + "#); + assert!(!test_env.env_root().join("secondary/.git").exists()); + + let output = secondary_dir.run_jj(["git", "colocation", "status", "--quiet"]); + insta::assert_snapshot!(output, @r#" + Workspace 'secondary' is currently not colocated with Git. + Last imported/exported Git HEAD: (none) + [EOF] + "#); } diff --git a/cli/tests/test_git_init.rs b/cli/tests/test_git_init.rs index f24ff9b1e09..83f3b9a35ae 100644 --- a/cli/tests/test_git_init.rs +++ b/cli/tests/test_git_init.rs @@ -221,7 +221,7 @@ fn test_git_init_external(bare: bool) { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently not colocated with Git. + Workspace 'default' is currently not colocated with Git. Last imported/exported Git HEAD: e80a42cccd069007c7a2bb427ac7f1d10b408633 [EOF] "); @@ -478,7 +478,7 @@ fn test_git_init_colocated_via_git_repo_path() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: e80a42cccd069007c7a2bb427ac7f1d10b408633 [EOF] "); @@ -493,7 +493,7 @@ fn test_git_init_colocated_via_git_repo_path() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: f3fe58bc88ccfb820b930a21297d8e48bf76ac2a [EOF] "); @@ -527,7 +527,7 @@ fn test_git_init_colocated_via_git_repo_path_gitlink() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&jj_work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: e80a42cccd069007c7a2bb427ac7f1d10b408633 [EOF] "); @@ -542,7 +542,7 @@ fn test_git_init_colocated_via_git_repo_path_gitlink() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&jj_work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: f3fe58bc88ccfb820b930a21297d8e48bf76ac2a [EOF] "); @@ -575,7 +575,7 @@ fn test_git_init_colocated_via_git_repo_path_symlink_directory() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&jj_work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: e80a42cccd069007c7a2bb427ac7f1d10b408633 [EOF] "); @@ -590,7 +590,7 @@ fn test_git_init_colocated_via_git_repo_path_symlink_directory() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&jj_work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: f3fe58bc88ccfb820b930a21297d8e48bf76ac2a [EOF] "); @@ -629,7 +629,7 @@ fn test_git_init_colocated_via_git_repo_path_symlink_directory_without_bare_conf [EOF] "); insta::assert_snapshot!(get_colocation_status(&jj_work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: e80a42cccd069007c7a2bb427ac7f1d10b408633 [EOF] "); @@ -644,7 +644,7 @@ fn test_git_init_colocated_via_git_repo_path_symlink_directory_without_bare_conf [EOF] "); insta::assert_snapshot!(get_colocation_status(&jj_work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: f3fe58bc88ccfb820b930a21297d8e48bf76ac2a [EOF] "); @@ -685,7 +685,7 @@ fn test_git_init_colocated_via_git_repo_path_symlink_gitlink() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&jj_work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: e80a42cccd069007c7a2bb427ac7f1d10b408633 [EOF] "); @@ -700,7 +700,7 @@ fn test_git_init_colocated_via_git_repo_path_symlink_gitlink() -> TestResult { [EOF] "); insta::assert_snapshot!(get_colocation_status(&jj_work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: f3fe58bc88ccfb820b930a21297d8e48bf76ac2a [EOF] "); @@ -927,7 +927,7 @@ fn test_git_init_external_but_git_dir_exists() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently not colocated with Git. + Workspace 'default' is currently not colocated with Git. Last imported/exported Git HEAD: (none) [EOF] "); @@ -956,7 +956,7 @@ fn test_git_init_colocated_via_flag_git_dir_exists() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: e80a42cccd069007c7a2bb427ac7f1d10b408633 [EOF] "); @@ -971,7 +971,7 @@ fn test_git_init_colocated_via_flag_git_dir_exists() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: f3fe58bc88ccfb820b930a21297d8e48bf76ac2a [EOF] "); @@ -1001,7 +1001,7 @@ fn test_git_init_colocated_via_config_git_dir_exists() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: e80a42cccd069007c7a2bb427ac7f1d10b408633 [EOF] "); @@ -1016,7 +1016,7 @@ fn test_git_init_colocated_via_config_git_dir_exists() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: f3fe58bc88ccfb820b930a21297d8e48bf76ac2a [EOF] "); @@ -1079,7 +1079,7 @@ fn test_git_init_colocated_via_flag_overrides_false_config() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: e80a42cccd069007c7a2bb427ac7f1d10b408633 [EOF] "); @@ -1103,7 +1103,7 @@ fn test_git_init_colocated_via_flag_git_dir_not_exists() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: (none) [EOF] "); @@ -1121,7 +1121,7 @@ fn test_git_init_colocated_via_flag_git_dir_not_exists() { [EOF] "); insta::assert_snapshot!(get_colocation_status(&work_dir), @" - Workspace is currently colocated with Git. + Workspace 'default' is currently colocated with Git. Last imported/exported Git HEAD: (none) [EOF] "); diff --git a/cli/tests/test_workspaces.rs b/cli/tests/test_workspaces.rs index 39505e7580c..cad4abfad59 100644 --- a/cli/tests/test_workspaces.rs +++ b/cli/tests/test_workspaces.rs @@ -2166,6 +2166,14 @@ fn test_workspaces_add_colocated_default_creates_worktree() { [EOF] "#); assert!(test_env.env_root().join("secondary/.git").is_file()); + + let secondary_dir = test_env.work_dir("secondary"); + let output = secondary_dir.run_jj(["git", "colocation", "status", "--quiet"]); + insta::assert_snapshot!(output, @r#" + Workspace 'secondary' is currently colocated with Git. + Last imported/exported Git HEAD: 7b22a8cbe888adcb4d5ff6dd46a38049e870c6ab + [EOF] + "#); } #[test]