diff --git a/CHANGELOG.md b/CHANGELOG.md index 59d42d5c9..39ebedad3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed relative path to the current directory in output to be `.` instead of empty string. +* When adding a new workspace, the parent of the current workspace's current + checkout will be checked out. That was always the intent, but the root commit + was accidentally checked out instead. + ## [0.4.0] - 2022-04-02 ### Breaking changes diff --git a/src/commands.rs b/src/commands.rs index 9f7e813c7..6bda20f72 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -2707,6 +2707,8 @@ fn cmd_status( ui.write("Working copy : ")?; ui.write_commit_summary(repo.as_repo_ref(), &workspace_id, checkout_commit)?; ui.write("\n")?; + } else { + ui.write("No working copy\n")?; } let mut conflicted_local_branches = vec![]; @@ -4318,7 +4320,7 @@ fn cmd_workspace_add( writeln!( ui, "Created workspace in \"{}\"", - destination_path.display() + ui::relative_path(old_workspace_command.workspace_root(), &destination_path).display() )?; let mut new_workspace_command = WorkspaceCommandHelper::for_loaded_repo( @@ -4335,7 +4337,7 @@ fn cmd_workspace_add( let new_checkout_commit = if let Some(old_checkout_id) = new_workspace_command .repo() .view() - .get_checkout(&new_workspace_command.workspace_id()) + .get_checkout(&old_workspace_command.workspace_id()) { new_workspace_command .repo() diff --git a/tests/test_workspaces.rs b/tests/test_workspaces.rs new file mode 100644 index 000000000..a83c5e3c8 --- /dev/null +++ b/tests/test_workspaces.rs @@ -0,0 +1,186 @@ +// Copyright 2022 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 std::path::Path; + +use itertools::Itertools; + +use crate::common::TestEnvironment; + +pub mod common; + +/// Test adding a second workspace +#[test] +fn test_workspaces_add_second_workspace() { + let test_env = TestEnvironment::default(); + test_env.jj_cmd_success(test_env.env_root(), &["init", "--git", "main"]); + let main_path = test_env.env_root().join("main"); + let secondary_path = test_env.env_root().join("secondary"); + + std::fs::write(main_path.join("file"), "contents").unwrap(); + test_env.jj_cmd_success(&main_path, &["close", "-m", "initial"]); + + let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]); + insta::assert_snapshot!(stdout, @"default: 988d8c1dca7e +"); + + let stdout = test_env.jj_cmd_success( + &main_path, + &["workspace", "add", "--name", "second", "../secondary"], + ); + insta::assert_snapshot!(stdout.replace('\\', "/"), @r###" + Created workspace in "../secondary" + Working copy now at: 8ac248e0c8d2 + Added 1 files, modified 0 files, removed 0 files + "###); + + // Can see the checkout in each workspace in the log output. The "@" node in the + // graph indicates the current workspace's checkout. + insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###" + o 8ac248e0c8d2d1865fe3679296e329c0137b1a31 second@ + | @ 988d8c1dca7e0944210ccc33584a6a42cd2962d4 default@ + |/ + o 2062e7d6f1f46b4fe1453040d691931e77a88f7c + o 0000000000000000000000000000000000000000 + "###); + insta::assert_snapshot!(get_log_output(&test_env, &secondary_path), @r###" + @ 8ac248e0c8d2d1865fe3679296e329c0137b1a31 second@ + | o 988d8c1dca7e0944210ccc33584a6a42cd2962d4 default@ + |/ + o 2062e7d6f1f46b4fe1453040d691931e77a88f7c + o 0000000000000000000000000000000000000000 + "###); + + // Both workspaces show up when we list them + let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]); + insta::assert_snapshot!(stdout, @r###" + default: 988d8c1dca7e + second: 8ac248e0c8d2 + "###); +} + +/// Test making changes to the working copy in a workspace as it gets rewritten +/// from another workspace +#[test] +fn test_workspaces_conflicting_edits() { + let test_env = TestEnvironment::default(); + test_env.jj_cmd_success(test_env.env_root(), &["init", "--git", "main"]); + let main_path = test_env.env_root().join("main"); + let secondary_path = test_env.env_root().join("secondary"); + + std::fs::write(main_path.join("file"), "contents\n").unwrap(); + test_env.jj_cmd_success(&main_path, &["close", "-m", "initial"]); + + test_env.jj_cmd_success(&main_path, &["workspace", "add", "../secondary"]); + + insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###" + o 6bafff1a880f313aebb6d357c79b7aa4befa0af8 secondary@ + | @ c8f1217f93a0bc570a8bbfe055980f27062339ef default@ + |/ + o 5af56dcc2cc27bb234e5574b5a3ebc5f22081462 + o 0000000000000000000000000000000000000000 + "###); + + // Make changes in both working copies + std::fs::write(main_path.join("file"), "changed in main\n").unwrap(); + std::fs::write(secondary_path.join("file"), "changed in second\n").unwrap(); + // Squash the changes from the main workspace in the initial commit (before + // running any command in the secondary workspace + let stdout = test_env.jj_cmd_success(&main_path, &["squash"]); + insta::assert_snapshot!(stdout, @r###" + Rebased 1 descendant commits + Working copy now at: 86bef7fee095 + "###); + + // The secondary workspace's checkout was updated + insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###" + @ 86bef7fee095bb5626d853c222764fc7c9fb88ac default@ + | o 8d8269a323a01a287236c4fd5f64dc9737febb5b secondary@ + |/ + o 52601f748bf6cb00ad5389922f530f20a7ecffaa + o 0000000000000000000000000000000000000000 + "###); + let stdout = get_log_output(&test_env, &secondary_path); + // It was detected that the working copy is now stale + // TODO: Since there was an uncommitted change in the working copy, it should + // have been committed first (causing divergence) + assert!(stdout.starts_with("The working copy is stale")); + insta::assert_snapshot!(stdout.lines().skip(1).join("\n"), @r###" + o 86bef7fee095bb5626d853c222764fc7c9fb88ac default@ + | @ 8d8269a323a01a287236c4fd5f64dc9737febb5b secondary@ + |/ + o 52601f748bf6cb00ad5389922f530f20a7ecffaa + o 0000000000000000000000000000000000000000 + "###); +} + +/// Test forgetting workspaces +#[test] +fn test_workspaces_forget() { + let test_env = TestEnvironment::default(); + test_env.jj_cmd_success(test_env.env_root(), &["init", "--git", "main"]); + let main_path = test_env.env_root().join("main"); + + std::fs::write(main_path.join("file"), "contents").unwrap(); + test_env.jj_cmd_success(&main_path, &["close", "-m", "initial"]); + + test_env.jj_cmd_success(&main_path, &["workspace", "add", "../secondary"]); + let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "forget"]); + insta::assert_snapshot!(stdout, @""); + + // When listing workspaces, only the secondary workspace shows up + let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]); + insta::assert_snapshot!(stdout, @"secondary: 39a6d6c6f295 +"); + + // `jj status` tells us that there's no working copy here + let stdout = test_env.jj_cmd_success(&main_path, &["st"]); + insta::assert_snapshot!(stdout, @"No working copy +"); + + // The old checkout doesn't get an "@" in the log output + // TODO: We should abandon the empty working copy commit + // TODO: It seems useful to still have the "secondary@" marker here even though + // there's only one workspace. We should show it when the command is not run + // from that workspace. + insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###" + o 39a6d6c6f29557f886ded65d50063da4321ab2a8 + | o 988d8c1dca7e0944210ccc33584a6a42cd2962d4 + |/ + o 2062e7d6f1f46b4fe1453040d691931e77a88f7c + o 0000000000000000000000000000000000000000 + "###); + + // Revision "@" cannot be used + let stderr = test_env.jj_cmd_failure(&main_path, &["log", "-r", "@"]); + insta::assert_snapshot!(stderr, @r###"Error: Revision "@" doesn't exist +"###); + + // Try to add back the workspace + // TODO: We should make this just add it back instead of failing + let stderr = test_env.jj_cmd_failure(&main_path, &["workspace", "add", "."]); + insta::assert_snapshot!(stderr, @"Error: Workspace already exists +"); + + // Forget the secondary workspace + let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "forget", "secondary"]); + insta::assert_snapshot!(stdout, @""); + // No workspaces left + let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]); + insta::assert_snapshot!(stdout, @""); +} + +fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> String { + test_env.jj_cmd_success(cwd, &["log", "-T", r#"commit_id " " checkouts"#]) +}