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

cli: port description template to templater

This implements a building block of "signed-off-by line" #1399 and "commit
--verbose" #1946. We'll probably need an easy way to customize the diff part,
but I'm not sure if it can be as simple as a template alias function. User
might want to embed diffs without "JJ: " prefixes?

Perhaps, we can deprecate "ui.default-description", but it's not addressed in
this patch. It could be replaced with "default_description" template alias,
but we might want to configure default per command. Suppose we add a default
"backout_description" template, it would have to be rendered against the
source commit, not the newly-created backout commit.

The template key is named as "draft_commit_description" because it is the
template to generate an editor template. "templates.commit_description_template"
sounds a bit odd.

There's one minor behavior change: the default description is now terminated
by "\n".

Closes #1354
This commit is contained in:
Yuya Nishihara 2024-07-20 17:46:50 +09:00
parent 5a19eb6331
commit d6e97883df
11 changed files with 131 additions and 41 deletions

View file

@ -82,6 +82,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
* New config setting `git.private-commits` to prevent commits from being pushed.
* [The default commit description template](docs/config.md#default-description)
can now be configured by `templates.draft_commit_description`.
### Fixed bugs
* `jj diff --git` no longer shows the contents of binary files.

View file

@ -1638,6 +1638,10 @@ impl WorkspaceCommandTransaction<'_> {
self.helper
}
pub fn settings(&self) -> &UserSettings {
&self.helper.settings
}
pub fn base_repo(&self) -> &Arc<ReadonlyRepo> {
self.tx.base_repo()
}

View file

@ -111,7 +111,7 @@ new working-copy commit.
commit_builder.set_description(command.settings().default_description());
}
let temp_commit = commit_builder.write_hidden()?;
let template = description_template(ui, tx.base_workspace_helper(), "", &temp_commit)?;
let template = description_template(&tx, "", &temp_commit)?;
edit_description(tx.base_repo(), &template, command.settings())?
};
commit_builder.set_description(description);

View file

@ -90,7 +90,7 @@ pub(crate) fn cmd_describe(
commit_builder.set_description(command.settings().default_description());
}
let temp_commit = commit_builder.write_hidden()?;
let template = description_template(ui, tx.base_workspace_helper(), "", &temp_commit)?;
let template = description_template(&tx, "", &temp_commit)?;
edit_description(tx.base_repo(), &template, command.settings())?
};
commit_builder.set_description(description);

View file

@ -133,8 +133,7 @@ the operation will be aborted.
}
let temp_commit = commit_builder.write_hidden()?;
let template = description_template(
ui,
tx.base_workspace_helper(),
&tx,
"Enter a description for the first commit.",
&temp_commit,
)?;
@ -176,8 +175,7 @@ the operation will be aborted.
} else {
let temp_commit = commit_builder.write_hidden()?;
let template = description_template(
ui,
tx.base_workspace_helper(),
&tx,
"Enter a description for the second commit.",
&temp_commit,
)?;

View file

@ -21,6 +21,18 @@ if(overridden,
) ++ "\n"
'''
# TODO: Provide hook point for diff customization (#1946)? We might want a
# syntax to comment out full text diffs without using the "JJ: " prefix.
draft_commit_description = '''
concat(
description,
surround(
"\nJJ: This commit contains the following changes:\n", "",
indent("JJ: ", diff.summary()),
),
)
'''
log = 'builtin_log_compact'
op_log = 'builtin_op_log_compact'
show = 'builtin_log_detailed'

View file

@ -1,15 +1,15 @@
use std::io::Write as _;
use bstr::ByteVec as _;
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::{edit_temp_file, WorkspaceCommandHelper};
use crate::cli_util::{edit_temp_file, WorkspaceCommandTransaction};
use crate::command_error::CommandError;
use crate::diff_util::DiffFormat;
use crate::formatter::PlainTextFormatter;
use crate::text_util;
use crate::ui::Ui;
pub fn edit_description(
repo: &ReadonlyRepo,
@ -89,37 +89,30 @@ pub fn join_message_paragraphs(paragraphs: &[String]) -> String {
.join("\n")
}
/// Renders commit description template, which will be edited by user.
pub fn description_template(
ui: &Ui,
workspace_command: &WorkspaceCommandHelper,
tx: &WorkspaceCommandTransaction,
intro: &str,
commit: &Commit,
) -> Result<String, CommandError> {
let mut diff_summary_bytes = Vec::new();
let diff_renderer = workspace_command.diff_renderer(vec![DiffFormat::Summary]);
diff_renderer.show_patch(
ui,
&mut PlainTextFormatter::new(&mut diff_summary_bytes),
commit,
&EverythingMatcher,
)?;
let mut template_chunks = Vec::new();
if !intro.is_empty() {
template_chunks.push(format!("JJ: {intro}\n"));
}
template_chunks.push(commit.description().to_owned());
if !diff_summary_bytes.is_empty() {
template_chunks.push("\n".to_owned());
template_chunks.push(diff_summary_to_description(&diff_summary_bytes));
}
Ok(template_chunks.concat())
}
// TODO: Should "ui.default-description" be deprecated?
// We might want default description templates per command instead. For
// example, "backout_description" template will be rendered against the
// commit to be backed out, and the generated description could be set
// without spawning editor.
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: ")
// Named as "draft" because the output can contain "JJ: " comment lines.
let template_key = "templates.draft_commit_description";
let template_text = tx.settings().config().get_string(template_key)?;
let template = tx.parse_commit_template(&template_text)?;
let mut output = Vec::new();
if !intro.is_empty() {
writeln!(output, "JJ: {intro}").unwrap();
}
template
.format(commit, &mut PlainTextFormatter::new(&mut output))
.expect("write() to vec backed formatter should never fail");
// Template output is usually UTF-8, but it can contain file content.
Ok(output.into_string_lossy())
}

View file

@ -174,6 +174,7 @@ fn test_commit_with_default_description() {
TESTED=TODO
JJ: This commit contains the following changes:
JJ: A file1
JJ: A file2
@ -182,6 +183,67 @@ fn test_commit_with_default_description() {
"###);
}
#[test]
fn test_commit_with_description_template() {
let mut test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
test_env.add_config(
r#"
[templates]
draft_commit_description = '''
concat(
description,
"\n",
indent(
"JJ: ",
concat(
"Author: " ++ format_detailed_signature(author) ++ "\n",
"Committer: " ++ format_detailed_signature(committer) ++ "\n",
"\n",
diff.stat(76),
),
),
)
'''
"#,
);
let workspace_path = test_env.env_root().join("repo");
let edit_script = test_env.set_up_fake_editor();
std::fs::write(edit_script, ["dump editor"].join("\0")).unwrap();
std::fs::write(workspace_path.join("file1"), "foo\n").unwrap();
std::fs::write(workspace_path.join("file2"), "bar\n").unwrap();
// Only file1 should be included in the diff
test_env.jj_cmd_ok(&workspace_path, &["commit", "file1"]);
insta::assert_snapshot!(
std::fs::read_to_string(test_env.env_root().join("editor")).unwrap(), @r###"
JJ: Author: Test User <test.user@example.com> (2001-02-03 08:05:08)
JJ: Committer: Test User <test.user@example.com> (2001-02-03 08:05:08)
JJ: file1 | 1 +
JJ: 1 file changed, 1 insertion(+), 0 deletions(-)
JJ: Lines starting with "JJ: " (like this one) will be removed.
"###);
// Timestamp after the reset should be available to the template
test_env.jj_cmd_ok(&workspace_path, &["commit", "--reset-author"]);
insta::assert_snapshot!(
std::fs::read_to_string(test_env.env_root().join("editor")).unwrap(), @r###"
JJ: Author: Test User <test.user@example.com> (2001-02-03 08:05:09)
JJ: Committer: Test User <test.user@example.com> (2001-02-03 08:05:09)
JJ: file2 | 1 +
JJ: 1 file changed, 1 insertion(+), 0 deletions(-)
JJ: Lines starting with "JJ: " (like this one) will be removed.
"###);
}
#[test]
fn test_commit_without_working_copy() {
let test_env = TestEnvironment::default();

View file

@ -271,6 +271,7 @@ fn test_describe_default_description() {
TESTED=TODO
JJ: This commit contains the following changes:
JJ: A file1
JJ: A file2

View file

@ -248,6 +248,7 @@ fn test_split_with_default_description() {
TESTED=TODO
JJ: This commit contains the following changes:
JJ: A file1
@ -366,6 +367,7 @@ fn test_split_siblings_no_descendants() {
TESTED=TODO
JJ: This commit contains the following changes:
JJ: A file1

View file

@ -158,9 +158,24 @@ ui.default-command = ["log", "--reversed"]
### Default description
The value of the `ui.default-description` setting will be used to prepopulate
the editor when describing changes with an empty description. This could be a
useful reminder to fill in things like BUG=, TESTED= etc.
The editor content of a commit description can be populated by the
`draft_commit_description` template.
```toml
[templates]
draft_commit_description = '''
concat(
description,
surround(
"\nJJ: This commit contains the following changes:\n", "",
indent("JJ: ", diff.stat(72)),
),
)
'''
```
The value of the `ui.default-description` setting can also be used in order to
fill in things like BUG=, TESTED= etc.
```toml
ui.default-description = "\n\nTESTED=TODO"