mirror of
https://github.com/martinvonz/jj.git
synced 2025-02-03 18:32:09 +00:00
cli: move description utils to description_util.rs
This commit is contained in:
parent
f1898a31b5
commit
afc2382833
9 changed files with 126 additions and 121 deletions
|
@ -17,8 +17,8 @@ use jj_lib::repo::Repo;
|
||||||
use jj_lib::rewrite::merge_commit_trees;
|
use jj_lib::rewrite::merge_commit_trees;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::{description_template_for_commit, edit_description};
|
|
||||||
use crate::cli_util::{join_message_paragraphs, user_error, CommandError, CommandHelper};
|
use crate::cli_util::{join_message_paragraphs, user_error, CommandError, CommandHelper};
|
||||||
|
use crate::description_util::{description_template_for_commit, edit_description};
|
||||||
use crate::ui::Ui;
|
use crate::ui::Ui;
|
||||||
|
|
||||||
/// Update the description and create a new change on top.
|
/// Update the description and create a new change on top.
|
||||||
|
|
|
@ -17,8 +17,8 @@ use std::io::{self, Read, Write};
|
||||||
use jj_lib::backend::ObjectId;
|
use jj_lib::backend::ObjectId;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::{description_template_for_commit, edit_description};
|
|
||||||
use crate::cli_util::{join_message_paragraphs, CommandError, CommandHelper, RevisionArg};
|
use crate::cli_util::{join_message_paragraphs, CommandError, CommandHelper, RevisionArg};
|
||||||
|
use crate::description_util::{description_template_for_commit, edit_description};
|
||||||
use crate::ui::Ui;
|
use crate::ui::Ui;
|
||||||
|
|
||||||
/// Update the change description or other metadata
|
/// Update the change description or other metadata
|
||||||
|
|
|
@ -55,24 +55,14 @@ mod util;
|
||||||
mod version;
|
mod version;
|
||||||
mod workspace;
|
mod workspace;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::io::Write;
|
|
||||||
use std::{fmt, fs, io};
|
|
||||||
|
|
||||||
use clap::{Command, CommandFactory, FromArgMatches, Subcommand};
|
use clap::{Command, CommandFactory, FromArgMatches, Subcommand};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use jj_lib::commit::Commit;
|
|
||||||
use jj_lib::matchers::EverythingMatcher;
|
|
||||||
use jj_lib::repo::ReadonlyRepo;
|
|
||||||
use jj_lib::settings::UserSettings;
|
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::cli_util::{
|
use crate::cli_util::{Args, CommandError, CommandHelper};
|
||||||
run_ui_editor, user_error, Args, CommandError, CommandHelper, WorkspaceCommandHelper,
|
|
||||||
};
|
|
||||||
use crate::diff_util::{self, DiffFormat};
|
|
||||||
use crate::formatter::PlainTextFormatter;
|
|
||||||
use crate::text_util;
|
|
||||||
use crate::ui::Ui;
|
use crate::ui::Ui;
|
||||||
|
|
||||||
#[derive(clap::Parser, Clone, Debug)]
|
#[derive(clap::Parser, Clone, Debug)]
|
||||||
|
@ -145,109 +135,6 @@ enum Commands {
|
||||||
Workspace(workspace::WorkspaceCommands),
|
Workspace(workspace::WorkspaceCommands),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edit_description(
|
|
||||||
repo: &ReadonlyRepo,
|
|
||||||
description: &str,
|
|
||||||
settings: &UserSettings,
|
|
||||||
) -> Result<String, CommandError> {
|
|
||||||
let description_file_path = (|| -> Result<_, io::Error> {
|
|
||||||
let mut file = tempfile::Builder::new()
|
|
||||||
.prefix("editor-")
|
|
||||||
.suffix(".jjdescription")
|
|
||||||
.tempfile_in(repo.repo_path())?;
|
|
||||||
file.write_all(description.as_bytes())?;
|
|
||||||
file.write_all(b"\nJJ: Lines starting with \"JJ: \" (like this one) will be removed.\n")?;
|
|
||||||
let (_, path) = file.keep().map_err(|e| e.error)?;
|
|
||||||
Ok(path)
|
|
||||||
})()
|
|
||||||
.map_err(|e| {
|
|
||||||
user_error(format!(
|
|
||||||
r#"Failed to create description file in "{path}": {e}"#,
|
|
||||||
path = repo.repo_path().display()
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
run_ui_editor(settings, &description_file_path)?;
|
|
||||||
|
|
||||||
let description = fs::read_to_string(&description_file_path).map_err(|e| {
|
|
||||||
user_error(format!(
|
|
||||||
r#"Failed to read description file "{path}": {e}"#,
|
|
||||||
path = description_file_path.display()
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
// Delete the file only if everything went well.
|
|
||||||
// TODO: Tell the user the name of the file we left behind.
|
|
||||||
std::fs::remove_file(description_file_path).ok();
|
|
||||||
// Normalize line ending, remove leading and trailing blank lines.
|
|
||||||
let description = description
|
|
||||||
.lines()
|
|
||||||
.filter(|line| !line.starts_with("JJ: "))
|
|
||||||
.join("\n");
|
|
||||||
Ok(text_util::complete_newline(description.trim_matches('\n')))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn combine_messages(
|
|
||||||
repo: &ReadonlyRepo,
|
|
||||||
source: &Commit,
|
|
||||||
destination: &Commit,
|
|
||||||
settings: &UserSettings,
|
|
||||||
abandon_source: bool,
|
|
||||||
) -> Result<String, CommandError> {
|
|
||||||
let description = if abandon_source {
|
|
||||||
if source.description().is_empty() {
|
|
||||||
destination.description().to_string()
|
|
||||||
} else if destination.description().is_empty() {
|
|
||||||
source.description().to_string()
|
|
||||||
} else {
|
|
||||||
let combined = "JJ: Enter a description for the combined commit.\n".to_string()
|
|
||||||
+ "JJ: Description from the destination commit:\n"
|
|
||||||
+ destination.description()
|
|
||||||
+ "\nJJ: Description from the source commit:\n"
|
|
||||||
+ source.description();
|
|
||||||
edit_description(repo, &combined, settings)?
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
destination.description().to_string()
|
|
||||||
};
|
|
||||||
Ok(description)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description_template_for_commit(
|
|
||||||
ui: &Ui,
|
|
||||||
settings: &UserSettings,
|
|
||||||
workspace_command: &WorkspaceCommandHelper,
|
|
||||||
commit: &Commit,
|
|
||||||
) -> Result<String, CommandError> {
|
|
||||||
let mut diff_summary_bytes = Vec::new();
|
|
||||||
diff_util::show_patch(
|
|
||||||
ui,
|
|
||||||
&mut PlainTextFormatter::new(&mut diff_summary_bytes),
|
|
||||||
workspace_command,
|
|
||||||
commit,
|
|
||||||
&EverythingMatcher,
|
|
||||||
&[DiffFormat::Summary],
|
|
||||||
)?;
|
|
||||||
let description = if commit.description().is_empty() {
|
|
||||||
settings.default_description()
|
|
||||||
} else {
|
|
||||||
commit.description().to_owned()
|
|
||||||
};
|
|
||||||
if diff_summary_bytes.is_empty() {
|
|
||||||
Ok(description)
|
|
||||||
} else {
|
|
||||||
Ok(description + "\n" + &diff_summary_to_description(&diff_summary_bytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn diff_summary_to_description(bytes: &[u8]) -> String {
|
|
||||||
let text = std::str::from_utf8(bytes).expect(
|
|
||||||
"Summary diffs and repo paths must always be valid UTF8.",
|
|
||||||
// Double-check this assumption for diffs that include file content.
|
|
||||||
);
|
|
||||||
"JJ: This commit contains the following changes:\n".to_owned()
|
|
||||||
+ &textwrap::indent(text, "JJ: ")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_branch_term(branch_names: &[impl fmt::Display]) -> String {
|
fn make_branch_term(branch_names: &[impl fmt::Display]) -> String {
|
||||||
match branch_names {
|
match branch_names {
|
||||||
[branch_name] => format!("branch {}", branch_name),
|
[branch_name] => format!("branch {}", branch_name),
|
||||||
|
|
|
@ -18,8 +18,8 @@ use jj_lib::repo::Repo;
|
||||||
use jj_lib::rewrite::merge_commit_trees;
|
use jj_lib::rewrite::merge_commit_trees;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::combine_messages;
|
|
||||||
use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg};
|
use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg};
|
||||||
|
use crate::description_util::combine_messages;
|
||||||
use crate::ui::Ui;
|
use crate::ui::Ui;
|
||||||
|
|
||||||
/// Move changes from one revision into another
|
/// Move changes from one revision into another
|
||||||
|
|
|
@ -22,8 +22,8 @@ use jj_lib::settings::UserSettings;
|
||||||
use maplit::{hashmap, hashset};
|
use maplit::{hashmap, hashset};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::{diff_summary_to_description, edit_description};
|
|
||||||
use crate::cli_util::{CommandError, CommandHelper, RevisionArg, WorkspaceCommandHelper};
|
use crate::cli_util::{CommandError, CommandHelper, RevisionArg, WorkspaceCommandHelper};
|
||||||
|
use crate::description_util::{diff_summary_to_description, edit_description};
|
||||||
use crate::diff_util::{self, DiffFormat};
|
use crate::diff_util::{self, DiffFormat};
|
||||||
use crate::formatter::PlainTextFormatter;
|
use crate::formatter::PlainTextFormatter;
|
||||||
use crate::ui::Ui;
|
use crate::ui::Ui;
|
||||||
|
|
|
@ -17,8 +17,8 @@ use jj_lib::backend::ObjectId;
|
||||||
use jj_lib::revset;
|
use jj_lib::revset;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::combine_messages;
|
|
||||||
use crate::cli_util::{self, user_error, CommandError, CommandHelper, RevisionArg};
|
use crate::cli_util::{self, user_error, CommandError, CommandHelper, RevisionArg};
|
||||||
|
use crate::description_util::combine_messages;
|
||||||
use crate::ui::Ui;
|
use crate::ui::Ui;
|
||||||
|
|
||||||
/// Move changes from a revision into its parent
|
/// Move changes from a revision into its parent
|
||||||
|
|
|
@ -17,8 +17,8 @@ use jj_lib::matchers::EverythingMatcher;
|
||||||
use jj_lib::rewrite::merge_commit_trees;
|
use jj_lib::rewrite::merge_commit_trees;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::combine_messages;
|
|
||||||
use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg};
|
use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg};
|
||||||
|
use crate::description_util::combine_messages;
|
||||||
use crate::ui::Ui;
|
use crate::ui::Ui;
|
||||||
|
|
||||||
/// Move changes from a revision's parent into the revision
|
/// Move changes from a revision's parent into the revision
|
||||||
|
|
117
cli/src/description_util.rs
Normal file
117
cli/src/description_util.rs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
use std::io::Write;
|
||||||
|
use std::{fs, io};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
use jj_lib::commit::Commit;
|
||||||
|
use jj_lib::matchers::EverythingMatcher;
|
||||||
|
use jj_lib::repo::ReadonlyRepo;
|
||||||
|
use jj_lib::settings::UserSettings;
|
||||||
|
|
||||||
|
use crate::cli_util::{run_ui_editor, user_error, CommandError, WorkspaceCommandHelper};
|
||||||
|
use crate::diff_util::{self, DiffFormat};
|
||||||
|
use crate::formatter::PlainTextFormatter;
|
||||||
|
use crate::text_util;
|
||||||
|
use crate::ui::Ui;
|
||||||
|
|
||||||
|
pub fn edit_description(
|
||||||
|
repo: &ReadonlyRepo,
|
||||||
|
description: &str,
|
||||||
|
settings: &UserSettings,
|
||||||
|
) -> Result<String, CommandError> {
|
||||||
|
let description_file_path = (|| -> Result<_, io::Error> {
|
||||||
|
let mut file = tempfile::Builder::new()
|
||||||
|
.prefix("editor-")
|
||||||
|
.suffix(".jjdescription")
|
||||||
|
.tempfile_in(repo.repo_path())?;
|
||||||
|
file.write_all(description.as_bytes())?;
|
||||||
|
file.write_all(b"\nJJ: Lines starting with \"JJ: \" (like this one) will be removed.\n")?;
|
||||||
|
let (_, path) = file.keep().map_err(|e| e.error)?;
|
||||||
|
Ok(path)
|
||||||
|
})()
|
||||||
|
.map_err(|e| {
|
||||||
|
user_error(format!(
|
||||||
|
r#"Failed to create description file in "{path}": {e}"#,
|
||||||
|
path = repo.repo_path().display()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
run_ui_editor(settings, &description_file_path)?;
|
||||||
|
|
||||||
|
let description = fs::read_to_string(&description_file_path).map_err(|e| {
|
||||||
|
user_error(format!(
|
||||||
|
r#"Failed to read description file "{path}": {e}"#,
|
||||||
|
path = description_file_path.display()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
// Delete the file only if everything went well.
|
||||||
|
// TODO: Tell the user the name of the file we left behind.
|
||||||
|
std::fs::remove_file(description_file_path).ok();
|
||||||
|
// Normalize line ending, remove leading and trailing blank lines.
|
||||||
|
let description = description
|
||||||
|
.lines()
|
||||||
|
.filter(|line| !line.starts_with("JJ: "))
|
||||||
|
.join("\n");
|
||||||
|
Ok(text_util::complete_newline(description.trim_matches('\n')))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn combine_messages(
|
||||||
|
repo: &ReadonlyRepo,
|
||||||
|
source: &Commit,
|
||||||
|
destination: &Commit,
|
||||||
|
settings: &UserSettings,
|
||||||
|
abandon_source: bool,
|
||||||
|
) -> Result<String, CommandError> {
|
||||||
|
let description = if abandon_source {
|
||||||
|
if source.description().is_empty() {
|
||||||
|
destination.description().to_string()
|
||||||
|
} else if destination.description().is_empty() {
|
||||||
|
source.description().to_string()
|
||||||
|
} else {
|
||||||
|
let combined = "JJ: Enter a description for the combined commit.\n".to_string()
|
||||||
|
+ "JJ: Description from the destination commit:\n"
|
||||||
|
+ destination.description()
|
||||||
|
+ "\nJJ: Description from the source commit:\n"
|
||||||
|
+ source.description();
|
||||||
|
edit_description(repo, &combined, settings)?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
destination.description().to_string()
|
||||||
|
};
|
||||||
|
Ok(description)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description_template_for_commit(
|
||||||
|
ui: &Ui,
|
||||||
|
settings: &UserSettings,
|
||||||
|
workspace_command: &WorkspaceCommandHelper,
|
||||||
|
commit: &Commit,
|
||||||
|
) -> Result<String, CommandError> {
|
||||||
|
let mut diff_summary_bytes = Vec::new();
|
||||||
|
diff_util::show_patch(
|
||||||
|
ui,
|
||||||
|
&mut PlainTextFormatter::new(&mut diff_summary_bytes),
|
||||||
|
workspace_command,
|
||||||
|
commit,
|
||||||
|
&EverythingMatcher,
|
||||||
|
&[DiffFormat::Summary],
|
||||||
|
)?;
|
||||||
|
let description = if commit.description().is_empty() {
|
||||||
|
settings.default_description()
|
||||||
|
} else {
|
||||||
|
commit.description().to_owned()
|
||||||
|
};
|
||||||
|
if diff_summary_bytes.is_empty() {
|
||||||
|
Ok(description)
|
||||||
|
} else {
|
||||||
|
Ok(description + "\n" + &diff_summary_to_description(&diff_summary_bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn diff_summary_to_description(bytes: &[u8]) -> String {
|
||||||
|
let text = std::str::from_utf8(bytes).expect(
|
||||||
|
"Summary diffs and repo paths must always be valid UTF8.",
|
||||||
|
// Double-check this assumption for diffs that include file content.
|
||||||
|
);
|
||||||
|
"JJ: This commit contains the following changes:\n".to_owned()
|
||||||
|
+ &textwrap::indent(text, "JJ: ")
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ pub mod cli_util;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod commit_templater;
|
pub mod commit_templater;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod description_util;
|
||||||
pub mod diff_util;
|
pub mod diff_util;
|
||||||
pub mod formatter;
|
pub mod formatter;
|
||||||
pub mod graphlog;
|
pub mod graphlog;
|
||||||
|
|
Loading…
Reference in a new issue