forked from mirrors/jj
parent
5bd7588738
commit
412ef36259
11 changed files with 254 additions and 1 deletions
|
@ -50,6 +50,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
(inherit from parent; default), `full` (full working copy), or `empty` (the
|
||||
empty working copy).
|
||||
|
||||
* New command `jj workspace rename` that can rename the current workspace.
|
||||
|
||||
* `jj op log` gained an option to include operation diffs.
|
||||
|
||||
* `jj git clone` now accepts a `--remote <REMOTE NAME>` option, which
|
||||
|
|
|
@ -250,6 +250,10 @@ impl LockedWorkingCopy for LockedConflictsWorkingCopy {
|
|||
self.inner.check_out(commit)
|
||||
}
|
||||
|
||||
fn rename_workspace(&mut self, new_workspace_id: WorkspaceId) {
|
||||
self.inner.rename_workspace(new_workspace_id);
|
||||
}
|
||||
|
||||
fn reset(&mut self, commit: &Commit) -> Result<(), ResetError> {
|
||||
self.inner.reset(commit)
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ use jj_lib::revset::RevsetParseErrorKind;
|
|||
use jj_lib::revset::RevsetResolutionError;
|
||||
use jj_lib::signing::SignInitError;
|
||||
use jj_lib::str_util::StringPatternParseError;
|
||||
use jj_lib::view::RenameWorkspaceError;
|
||||
use jj_lib::working_copy::ResetError;
|
||||
use jj_lib::working_copy::SnapshotError;
|
||||
use jj_lib::working_copy::WorkingCopyStateError;
|
||||
|
@ -264,6 +265,12 @@ impl From<CheckOutCommitError> for CommandError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<RenameWorkspaceError> for CommandError {
|
||||
fn from(err: RenameWorkspaceError) -> Self {
|
||||
user_error_with_message("Failed to rename a workspace", err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BackendError> for CommandError {
|
||||
fn from(err: BackendError) -> Self {
|
||||
match &err {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
mod add;
|
||||
mod forget;
|
||||
mod list;
|
||||
mod rename;
|
||||
mod root;
|
||||
mod update_stale;
|
||||
|
||||
|
@ -27,6 +28,8 @@ use self::forget::cmd_workspace_forget;
|
|||
use self::forget::WorkspaceForgetArgs;
|
||||
use self::list::cmd_workspace_list;
|
||||
use self::list::WorkspaceListArgs;
|
||||
use self::rename::cmd_workspace_rename;
|
||||
use self::rename::WorkspaceRenameArgs;
|
||||
use self::root::cmd_workspace_root;
|
||||
use self::root::WorkspaceRootArgs;
|
||||
use self::update_stale::cmd_workspace_update_stale;
|
||||
|
@ -51,6 +54,7 @@ pub(crate) enum WorkspaceCommand {
|
|||
Add(WorkspaceAddArgs),
|
||||
Forget(WorkspaceForgetArgs),
|
||||
List(WorkspaceListArgs),
|
||||
Rename(WorkspaceRenameArgs),
|
||||
Root(WorkspaceRootArgs),
|
||||
UpdateStale(WorkspaceUpdateStaleArgs),
|
||||
}
|
||||
|
@ -65,6 +69,7 @@ pub(crate) fn cmd_workspace(
|
|||
WorkspaceCommand::Add(args) => cmd_workspace_add(ui, command, args),
|
||||
WorkspaceCommand::Forget(args) => cmd_workspace_forget(ui, command, args),
|
||||
WorkspaceCommand::List(args) => cmd_workspace_list(ui, command, args),
|
||||
WorkspaceCommand::Rename(args) => cmd_workspace_rename(ui, command, args),
|
||||
WorkspaceCommand::Root(args) => cmd_workspace_root(ui, command, args),
|
||||
WorkspaceCommand::UpdateStale(args) => cmd_workspace_update_stale(ui, command, args),
|
||||
}
|
||||
|
|
78
cli/src/commands/workspace/rename.rs
Normal file
78
cli/src/commands/workspace/rename.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2020 The Jujutsu Authors
|
||||
//
|
||||
// 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::op_store::WorkspaceId;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::cli_util::CommandHelper;
|
||||
use crate::command_error::user_error;
|
||||
use crate::command_error::CommandError;
|
||||
use crate::ui::Ui;
|
||||
|
||||
/// Renames the current workspace
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub struct WorkspaceRenameArgs {
|
||||
/// The name of the workspace to update to.
|
||||
new_workspace_name: String,
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn cmd_workspace_rename(
|
||||
ui: &mut Ui,
|
||||
command: &CommandHelper,
|
||||
args: &WorkspaceRenameArgs,
|
||||
) -> Result<(), CommandError> {
|
||||
if args.new_workspace_name.is_empty() {
|
||||
return Err(user_error("New workspace name cannot be empty"));
|
||||
}
|
||||
|
||||
let mut workspace_command = command.workspace_helper(ui)?;
|
||||
|
||||
let old_workspace_id = workspace_command.working_copy().workspace_id().clone();
|
||||
let new_workspace_id = WorkspaceId::new(args.new_workspace_name.clone());
|
||||
if new_workspace_id == old_workspace_id {
|
||||
writeln!(ui.status(), "Nothing changed.")?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if workspace_command
|
||||
.repo()
|
||||
.view()
|
||||
.get_wc_commit_id(&old_workspace_id)
|
||||
.is_none()
|
||||
{
|
||||
return Err(user_error(format!(
|
||||
"The current workspace '{}' is not tracked in the repo.",
|
||||
old_workspace_id.as_str()
|
||||
)));
|
||||
}
|
||||
|
||||
let mut tx = workspace_command.start_transaction().into_inner();
|
||||
let (mut locked_ws, _wc_commit) = workspace_command.start_working_copy_mutation()?;
|
||||
|
||||
locked_ws
|
||||
.locked_wc()
|
||||
.rename_workspace(new_workspace_id.clone());
|
||||
|
||||
tx.repo_mut()
|
||||
.rename_workspace(&old_workspace_id, new_workspace_id)?;
|
||||
let repo = tx.commit(format!(
|
||||
"Renamed workspace '{}' to '{}'",
|
||||
old_workspace_id.as_str(),
|
||||
args.new_workspace_name
|
||||
));
|
||||
locked_ws.finish(repo.op_id().clone())?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -98,6 +98,7 @@ This document contains the help content for the `jj` command-line program.
|
|||
* [`jj workspace add`↴](#jj-workspace-add)
|
||||
* [`jj workspace forget`↴](#jj-workspace-forget)
|
||||
* [`jj workspace list`↴](#jj-workspace-list)
|
||||
* [`jj workspace rename`↴](#jj-workspace-rename)
|
||||
* [`jj workspace root`↴](#jj-workspace-root)
|
||||
* [`jj workspace update-stale`↴](#jj-workspace-update-stale)
|
||||
|
||||
|
@ -2144,6 +2145,7 @@ Each workspace also has own sparse patterns.
|
|||
* `add` — Add a workspace
|
||||
* `forget` — Stop tracking a workspace's working-copy commit in the repo
|
||||
* `list` — List workspaces
|
||||
* `rename` — Renames the current workspace
|
||||
* `root` — Show the current workspace root directory
|
||||
* `update-stale` — Update a workspace that has become stale
|
||||
|
||||
|
@ -2208,6 +2210,18 @@ List workspaces
|
|||
|
||||
|
||||
|
||||
## `jj workspace rename`
|
||||
|
||||
Renames the current workspace
|
||||
|
||||
**Usage:** `jj workspace rename <NEW_WORKSPACE_NAME>`
|
||||
|
||||
###### **Arguments:**
|
||||
|
||||
* `<NEW_WORKSPACE_NAME>` — The name of the workspace to update to
|
||||
|
||||
|
||||
|
||||
## `jj workspace root`
|
||||
|
||||
Show the current workspace root directory
|
||||
|
|
|
@ -1078,6 +1078,93 @@ fn test_debug_snapshot() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workspaces_rename_nothing_changed() {
|
||||
let test_env = TestEnvironment::default();
|
||||
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "main"]);
|
||||
let main_path = test_env.env_root().join("main");
|
||||
let (stdout, stderr) = test_env.jj_cmd_ok(&main_path, &["workspace", "rename", "default"]);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Nothing changed.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workspaces_rename_new_workspace_name_already_used() {
|
||||
let test_env = TestEnvironment::default();
|
||||
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "main"]);
|
||||
let main_path = test_env.env_root().join("main");
|
||||
test_env.jj_cmd_ok(
|
||||
&main_path,
|
||||
&["workspace", "add", "--name", "second", "../secondary"],
|
||||
);
|
||||
let stderr = test_env.jj_cmd_failure(&main_path, &["workspace", "rename", "second"]);
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Error: Failed to rename a workspace
|
||||
Caused by: Workspace second already exists
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workspaces_rename_forgotten_workspace() {
|
||||
let test_env = TestEnvironment::default();
|
||||
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "main"]);
|
||||
let main_path = test_env.env_root().join("main");
|
||||
test_env.jj_cmd_ok(
|
||||
&main_path,
|
||||
&["workspace", "add", "--name", "second", "../secondary"],
|
||||
);
|
||||
test_env.jj_cmd_ok(&main_path, &["workspace", "forget", "second"]);
|
||||
let secondary_path = test_env.env_root().join("secondary");
|
||||
let stderr = test_env.jj_cmd_failure(&secondary_path, &["workspace", "rename", "third"]);
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Error: The current workspace 'second' is not tracked in the repo.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workspaces_rename_workspace() {
|
||||
let test_env = TestEnvironment::default();
|
||||
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "main"]);
|
||||
let main_path = test_env.env_root().join("main");
|
||||
test_env.jj_cmd_ok(
|
||||
&main_path,
|
||||
&["workspace", "add", "--name", "second", "../secondary"],
|
||||
);
|
||||
let secondary_path = test_env.env_root().join("secondary");
|
||||
|
||||
// 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: qpvuntsm 230dd059 (empty) (no description set)
|
||||
second: uuqppmxq 57d63245 (empty) (no description set)
|
||||
"###);
|
||||
|
||||
let stdout = test_env.jj_cmd_success(&secondary_path, &["workspace", "rename", "third"]);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
|
||||
let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]);
|
||||
insta::assert_snapshot!(stdout, @r###"
|
||||
default: qpvuntsm 230dd059 (empty) (no description set)
|
||||
third: uuqppmxq 57d63245 (empty) (no description set)
|
||||
"###);
|
||||
|
||||
// Can see the working-copy commit in each workspace in the log output.
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
|
||||
○ 57d63245a308 third@
|
||||
│ @ 230dd059e1b0 default@
|
||||
├─╯
|
||||
◆ 000000000000
|
||||
"###);
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &secondary_path), @r###"
|
||||
@ 57d63245a308 third@
|
||||
│ ○ 230dd059e1b0 default@
|
||||
├─╯
|
||||
◆ 000000000000
|
||||
"###);
|
||||
}
|
||||
|
||||
fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> String {
|
||||
let template = r#"
|
||||
separate(" ",
|
||||
|
|
|
@ -1626,6 +1626,7 @@ impl WorkingCopy for LocalWorkingCopy {
|
|||
old_operation_id,
|
||||
old_tree_id,
|
||||
tree_state_dirty: false,
|
||||
new_workspace_id: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -1825,6 +1826,7 @@ pub struct LockedLocalWorkingCopy {
|
|||
old_operation_id: OperationId,
|
||||
old_tree_id: MergedTreeId,
|
||||
tree_state_dirty: bool,
|
||||
new_workspace_id: Option<WorkspaceId>,
|
||||
}
|
||||
|
||||
impl LockedWorkingCopy for LockedLocalWorkingCopy {
|
||||
|
@ -1872,6 +1874,10 @@ impl LockedWorkingCopy for LockedLocalWorkingCopy {
|
|||
Ok(stats)
|
||||
}
|
||||
|
||||
fn rename_workspace(&mut self, new_workspace_id: WorkspaceId) {
|
||||
self.new_workspace_id = Some(new_workspace_id);
|
||||
}
|
||||
|
||||
fn reset(&mut self, commit: &Commit) -> Result<(), ResetError> {
|
||||
let new_tree = commit.tree()?;
|
||||
self.wc
|
||||
|
@ -1937,7 +1943,10 @@ impl LockedWorkingCopy for LockedLocalWorkingCopy {
|
|||
err: Box::new(err),
|
||||
})?;
|
||||
}
|
||||
if self.old_operation_id != operation_id {
|
||||
if self.old_operation_id != operation_id || self.new_workspace_id.is_some() {
|
||||
if let Some(new_workspace_id) = self.new_workspace_id {
|
||||
self.wc.checkout_state_mut().workspace_id = new_workspace_id;
|
||||
}
|
||||
self.wc.checkout_state_mut().operation_id = operation_id;
|
||||
self.wc.save();
|
||||
}
|
||||
|
|
|
@ -92,6 +92,7 @@ use crate::simple_op_store::SimpleOpStore;
|
|||
use crate::store::Store;
|
||||
use crate::submodule_store::SubmoduleStore;
|
||||
use crate::transaction::Transaction;
|
||||
use crate::view::RenameWorkspaceError;
|
||||
use crate::view::View;
|
||||
|
||||
pub trait Repo {
|
||||
|
@ -1353,6 +1354,15 @@ impl MutableRepo {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rename_workspace(
|
||||
&mut self,
|
||||
old_workspace_id: &WorkspaceId,
|
||||
new_workspace_id: WorkspaceId,
|
||||
) -> Result<(), RenameWorkspaceError> {
|
||||
self.view_mut()
|
||||
.rename_workspace(old_workspace_id, new_workspace_id)
|
||||
}
|
||||
|
||||
pub fn check_out(
|
||||
&mut self,
|
||||
workspace_id: WorkspaceId,
|
||||
|
|
|
@ -19,6 +19,7 @@ use std::collections::HashMap;
|
|||
use std::collections::HashSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::backend::CommitId;
|
||||
use crate::op_store;
|
||||
|
@ -95,6 +96,29 @@ impl View {
|
|||
self.data.wc_commit_ids.remove(workspace_id);
|
||||
}
|
||||
|
||||
pub fn rename_workspace(
|
||||
&mut self,
|
||||
old_workspace_id: &WorkspaceId,
|
||||
new_workspace_id: WorkspaceId,
|
||||
) -> Result<(), RenameWorkspaceError> {
|
||||
if self.data.wc_commit_ids.contains_key(&new_workspace_id) {
|
||||
return Err(RenameWorkspaceError::WorkspaceAlreadyExists {
|
||||
workspace_id: new_workspace_id.as_str().to_owned(),
|
||||
});
|
||||
}
|
||||
let wc_commit_id = self
|
||||
.data
|
||||
.wc_commit_ids
|
||||
.remove(old_workspace_id)
|
||||
.ok_or_else(|| RenameWorkspaceError::WorkspaceDoesNotExist {
|
||||
workspace_id: old_workspace_id.as_str().to_owned(),
|
||||
})?;
|
||||
self.data
|
||||
.wc_commit_ids
|
||||
.insert(new_workspace_id, wc_commit_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_head(&mut self, head_id: &CommitId) {
|
||||
self.data.head_ids.insert(head_id.clone());
|
||||
}
|
||||
|
@ -369,3 +393,13 @@ impl View {
|
|||
&mut self.data
|
||||
}
|
||||
}
|
||||
|
||||
/// Error from attempts to rename a workspace
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RenameWorkspaceError {
|
||||
#[error("Workspace {workspace_id} not found")]
|
||||
WorkspaceDoesNotExist { workspace_id: String },
|
||||
|
||||
#[error("Workspace {workspace_id} already exists")]
|
||||
WorkspaceAlreadyExists { workspace_id: String },
|
||||
}
|
||||
|
|
|
@ -107,6 +107,9 @@ pub trait LockedWorkingCopy {
|
|||
/// Check out the specified commit in the working copy.
|
||||
fn check_out(&mut self, commit: &Commit) -> Result<CheckoutStats, CheckoutError>;
|
||||
|
||||
/// Update the workspace name.
|
||||
fn rename_workspace(&mut self, new_workspace_name: WorkspaceId);
|
||||
|
||||
/// Update to another commit without touching the files in the working copy.
|
||||
fn reset(&mut self, commit: &Commit) -> Result<(), ResetError>;
|
||||
|
||||
|
|
Loading…
Reference in a new issue