cli: add jj edit for editing a commit in the wokring copy

This commit is contained in:
Martin von Zweigbergk 2022-06-27 17:36:13 -07:00 committed by Martin von Zweigbergk
parent 6952b4f91e
commit 42b2937d5e
4 changed files with 101 additions and 3 deletions

View File

@ -34,7 +34,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* The [`$NO_COLOR` environment variable](https://no-color.org/) no longer * The [`$NO_COLOR` environment variable](https://no-color.org/) no longer
overrides the `ui.color` configuration if explicitly set. overrides the `ui.color` configuration if explicitly set.
* `jj edit` has been renamed to `jj touchup`. * `jj edit` has been renamed to `jj touchup`, and `jj edit` is now a new command
with different behavior. The new `jj edit` lets you edit a commit in the
working copy, even if the specified commit is closed.
* `jj git push` no longer aborts if you attempt to push an open commit (but it * `jj git push` no longer aborts if you attempt to push an open commit (but it
now aborts if a commit does not have a description). now aborts if a commit does not have a description).

View File

@ -597,7 +597,6 @@ impl MutableRepo {
} }
pub fn edit(&mut self, workspace_id: WorkspaceId, commit: &Commit) { pub fn edit(&mut self, workspace_id: WorkspaceId, commit: &Commit) {
assert!(commit.is_open(), "commit to edit is closed");
self.leave_commit(&workspace_id); self.leave_commit(&workspace_id);
self.set_checkout(workspace_id, commit.id().clone()); self.set_checkout(workspace_id, commit.id().clone());
} }
@ -606,7 +605,6 @@ impl MutableRepo {
let maybe_current_checkout_id = self.view.borrow().get_checkout(workspace_id).cloned(); let maybe_current_checkout_id = self.view.borrow().get_checkout(workspace_id).cloned();
if let Some(current_checkout_id) = maybe_current_checkout_id { if let Some(current_checkout_id) = maybe_current_checkout_id {
let current_checkout = self.store().get_commit(&current_checkout_id).unwrap(); let current_checkout = self.store().get_commit(&current_checkout_id).unwrap();
assert!(current_checkout.is_open(), "current checkout is closed");
if current_checkout.is_empty() if current_checkout.is_empty()
&& current_checkout.description().is_empty() && current_checkout.description().is_empty()
&& self.view().heads().contains(current_checkout.id()) && self.view().heads().contains(current_checkout.id())

View File

@ -1127,6 +1127,7 @@ enum Commands {
Open(OpenArgs), Open(OpenArgs),
Duplicate(DuplicateArgs), Duplicate(DuplicateArgs),
Abandon(AbandonArgs), Abandon(AbandonArgs),
Edit(EditArgs),
New(NewArgs), New(NewArgs),
Move(MoveArgs), Move(MoveArgs),
Squash(SquashArgs), Squash(SquashArgs),
@ -1401,6 +1402,16 @@ struct AbandonArgs {
revisions: Vec<String>, revisions: Vec<String>,
} }
/// Edit a commit in the working copy
///
/// Puts the contents of a commit in the working copy for editing. Any changes
/// you make in the working copy will update (amend) the commit.
#[derive(clap::Args, Clone, Debug)]
struct EditArgs {
/// The commit to edit
revision: String,
}
/// Create a new, empty change and check it out /// Create a new, empty change and check it out
/// ///
/// This may be useful if you want to make some changes you're unsure of on top /// This may be useful if you want to make some changes you're unsure of on top
@ -3439,6 +3450,22 @@ fn cmd_abandon(
Ok(()) Ok(())
} }
fn cmd_edit(ui: &mut Ui, command: &CommandHelper, args: &EditArgs) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let new_commit = workspace_command.resolve_single_rev(ui, &args.revision)?;
let workspace_id = workspace_command.workspace_id();
if workspace_command.repo().view().get_checkout(&workspace_id) == Some(new_commit.id()) {
ui.write("Already editing that commit\n")?;
} else {
workspace_command.commit_working_copy(ui)?;
let mut tx =
workspace_command.start_transaction(&format!("edit commit {}", new_commit.id().hex()));
tx.mut_repo().edit(workspace_id, &new_commit);
workspace_command.finish_transaction(ui, tx)?;
}
Ok(())
}
fn cmd_new(ui: &mut Ui, command: &CommandHelper, args: &NewArgs) -> Result<(), CommandError> { fn cmd_new(ui: &mut Ui, command: &CommandHelper, args: &NewArgs) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let parent = workspace_command.resolve_single_rev(ui, &args.revision)?; let parent = workspace_command.resolve_single_rev(ui, &args.revision)?;
@ -5294,6 +5321,7 @@ where
Commands::Open(sub_args) => cmd_open(ui, &command_helper, sub_args), Commands::Open(sub_args) => cmd_open(ui, &command_helper, sub_args),
Commands::Duplicate(sub_args) => cmd_duplicate(ui, &command_helper, sub_args), Commands::Duplicate(sub_args) => cmd_duplicate(ui, &command_helper, sub_args),
Commands::Abandon(sub_args) => cmd_abandon(ui, &command_helper, sub_args), Commands::Abandon(sub_args) => cmd_abandon(ui, &command_helper, sub_args),
Commands::Edit(sub_args) => cmd_edit(ui, &command_helper, sub_args),
Commands::New(sub_args) => cmd_new(ui, &command_helper, sub_args), Commands::New(sub_args) => cmd_new(ui, &command_helper, sub_args),
Commands::Move(sub_args) => cmd_move(ui, &command_helper, sub_args), Commands::Move(sub_args) => cmd_move(ui, &command_helper, sub_args),
Commands::Squash(sub_args) => cmd_squash(ui, &command_helper, sub_args), Commands::Squash(sub_args) => cmd_squash(ui, &command_helper, sub_args),

View File

@ -0,0 +1,70 @@
// 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 crate::common::TestEnvironment;
pub mod common;
#[test]
fn test_edit() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
std::fs::write(repo_path.join("file1"), "0").unwrap();
test_env.jj_cmd_success(&repo_path, &["close", "-m", "first"]);
test_env.jj_cmd_success(&repo_path, &["describe", "-m", "second"]);
std::fs::write(repo_path.join("file1"), "1").unwrap();
// Errors out without argument
test_env.jj_cmd_cli_error(&repo_path, &["edit"]);
// Can edit a closed commit
let stdout = test_env.jj_cmd_success(&repo_path, &["edit", "@-"]);
insta::assert_snapshot!(stdout, @r###"
Working copy now at: 5c9d6c787f29 first
Added 0 files, modified 1 files, removed 0 files
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
o 37ed5225d0fd open second
@ 5c9d6c787f29 closed first
o 000000000000 closed (no description set)
"###);
insta::assert_snapshot!(read_file(&repo_path.join("file1")), @"0");
// Changes in the working copy are amended into the commit
std::fs::write(repo_path.join("file2"), "0").unwrap();
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
Rebased 1 descendant commits onto updated working copy
o 57e61f6b2ce1 open second
@ f1b9706b17d0 closed first
o 000000000000 closed (no description set)
"###);
}
fn read_file(path: &Path) -> String {
String::from_utf8(std::fs::read(path).unwrap()).unwrap()
}
fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> String {
test_env.jj_cmd_success(
cwd,
&[
"log",
"-T",
r#"commit_id.short() " " if(open, "open", "closed") " " description"#,
],
)
}