ok/jj
1
0
Fork 0
forked from mirrors/jj

cli: add instructions for all diff-editing (aka interactive) commands

Now when you do e.g. `jj split`, you'll get a `JJ-INSTRUCTIONS` file
as part of the diff you're editing.
This commit is contained in:
Martin von Zweigbergk 2021-05-15 22:16:07 -07:00
parent e29cef7918
commit 66460477b7
2 changed files with 93 additions and 6 deletions

View file

@ -749,6 +749,11 @@ fn get_app<'a, 'b>() -> App<'a, 'b> {
.subcommand(debug_command)
}
fn short_commit_description(commit: &Commit) -> String {
let first_line = commit.description().split('\n').next().unwrap();
format!("{} ({})", &commit.id().hex()[0..12], first_line)
}
fn cmd_init(
ui: &mut Ui,
_command: &CommandHelper,
@ -1517,7 +1522,22 @@ fn cmd_squash(
let mut_repo = tx.mut_repo();
let new_parent_tree_id;
if sub_matches.is_present("interactive") {
new_parent_tree_id = crate::diff_edit::edit_diff(&parent.tree(), &commit.tree())?;
let instructions = format!(
"You are moving changes from: {}\n\
into its parent: {}\n\n\
The left side of the diff shows the contents of the parent commit. The\n\
right side initially shows the contents of the commit you're moving\n\
changes from.\n\n\
Adjust the right side until the diff shows the changes you want to move\n\
to the destination. If you don't make any changes, then all the changes\n\
from the source will be moved into the parent.\n",
short_commit_description(&commit),
short_commit_description(&parent)
);
new_parent_tree_id =
crate::diff_edit::edit_diff(&parent.tree(), &commit.tree(), &instructions)?;
if &new_parent_tree_id == parent.tree().id() {
return Err(CommandError::UserError(String::from("No changes selected")));
}
@ -1565,7 +1585,21 @@ fn cmd_unsquash(
let parent_base_tree = merge_commit_trees(repo.as_repo_ref(), &parent.parents());
let new_parent_tree_id;
if sub_matches.is_present("interactive") {
new_parent_tree_id = crate::diff_edit::edit_diff(&parent_base_tree, &parent.tree())?;
let instructions = format!(
"You are moving changes from: {}\n\
into its child: {}\n\n\
The diff initially shows the parent commit's changes.\n\n\
Adjust the right side until it shows the contents you want to keep in\n\
the parent commit. The changes you edited out will be moved into the\n\
child commit. If you don't make any changes, then the operation will be\n\
aborted.\n",
short_commit_description(&parent),
short_commit_description(&commit)
);
new_parent_tree_id =
crate::diff_edit::edit_diff(&parent_base_tree, &parent.tree(), &instructions)?;
if &new_parent_tree_id == parent_base_tree.id() {
return Err(CommandError::UserError(String::from("No changes selected")));
}
@ -1624,7 +1658,24 @@ fn cmd_restore(
"restore with --interactive and path is not yet supported".to_string(),
));
}
tree_id = crate::diff_edit::edit_diff(&source_commit.tree(), &destination_commit.tree())?;
let instructions = format!(
"You are restoring state from: {}\n\
into: {}\n\n\
The left side of the diff shows the contents of the commit you're\n\
restoring from. The right side initially shows the contents of the\n\
commit you're restoring into.\n\n\
Adjust the right side until it has the changes you wanted from the left\n\
side. If you don't make any changes, then the operation will be aborted.\n",
short_commit_description(&source_commit),
short_commit_description(&destination_commit)
);
tree_id = crate::diff_edit::edit_diff(
&source_commit.tree(),
&destination_commit.tree(),
&instructions,
)?;
} else if sub_matches.is_present("paths") {
let paths = sub_matches.values_of("paths").unwrap();
let mut tree_builder = repo
@ -1674,7 +1725,16 @@ fn cmd_edit(
let commit = repo_command.resolve_revision_arg(sub_matches)?;
let repo = repo_command.repo();
let base_tree = merge_commit_trees(repo.as_repo_ref(), &commit.parents());
let tree_id = crate::diff_edit::edit_diff(&base_tree, &commit.tree())?;
let instructions = format!(
"You are editing changes in: {}\n\n\
The diff initially shows the commit's changes.\n\n\
Adjust the right side until it shows the contents you want. If you\n\
don't make any changes, then the operation will be aborted.\n",
short_commit_description(&commit)
);
let tree_id = crate::diff_edit::edit_diff(&base_tree, &commit.tree(), &instructions)?;
if &tree_id == commit.tree().id() {
ui.write("Nothing changed.\n")?;
} else {
@ -1700,7 +1760,17 @@ fn cmd_split(
let commit = repo_command.resolve_revision_arg(sub_matches)?;
let repo = repo_command.repo();
let base_tree = merge_commit_trees(repo.as_repo_ref(), &commit.parents());
let tree_id = crate::diff_edit::edit_diff(&base_tree, &commit.tree())?;
let instructions = format!(
"You are splitting a commit in two: {}\n\n\
The diff initially shows the changes in the commit you're splitting.\n\n\
Adjust the right side until it shows the contents you want for the first\n\
commit. The remainder will be in the second commit. If you don't make\n\
any changes, then the operation will be aborted.\n",
short_commit_description(&commit)
);
let tree_id = crate::diff_edit::edit_diff(&base_tree, &commit.tree(), &instructions)?;
if &tree_id == commit.tree().id() {
ui.write("Nothing changed.\n")?;
} else {

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::Arc;
@ -92,7 +94,11 @@ fn set_readonly_recursively(path: &Path) {
std::fs::set_permissions(path, perms).unwrap();
}
pub fn edit_diff(left_tree: &Tree, right_tree: &Tree) -> Result<TreeId, DiffEditError> {
pub fn edit_diff(
left_tree: &Tree,
right_tree: &Tree,
instructions: &str,
) -> Result<TreeId, DiffEditError> {
// First create partial Trees of only the subset of the left and right trees
// that affect files changed between them.
let store = left_tree.store();
@ -131,6 +137,14 @@ pub fn edit_diff(left_tree: &Tree, right_tree: &Tree) -> Result<TreeId, DiffEdit
right_state_dir,
right_partial_tree_id,
)?;
let instructions_path = right_wc_dir.join("JJ-INSTRUCTIONS");
// In the unlikely event that the file already exists, then the user will simply
// not get any instructions.
let add_instructions = !instructions.is_empty() && !instructions_path.exists();
if add_instructions {
let mut file = File::create(&instructions_path).unwrap();
file.write_all(instructions.as_bytes()).unwrap();
}
// Start a diff editor on the two directories.
let exit_status = Command::new("meld")
@ -141,6 +155,9 @@ pub fn edit_diff(left_tree: &Tree, right_tree: &Tree) -> Result<TreeId, DiffEdit
if !exit_status.success() {
return Err(DiffEditError::DifftoolAborted);
}
if add_instructions {
std::fs::remove_file(instructions_path).ok();
}
// Create a Tree based on the initial right tree, applying the changes made to
// that directory by the diff editor.