mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-18 18:27:38 +00:00
cli: add jj edit
for editing a commit in the wokring copy
This commit is contained in:
parent
6952b4f91e
commit
42b2937d5e
4 changed files with 101 additions and 3 deletions
|
@ -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).
|
||||||
|
|
|
@ -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(¤t_checkout_id).unwrap();
|
let current_checkout = self.store().get_commit(¤t_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())
|
||||||
|
|
|
@ -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),
|
||||||
|
|
70
tests/test_edit_command.rs
Normal file
70
tests/test_edit_command.rs
Normal 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"#,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in a new issue