Skip to content

Commit

Permalink
cli: add --author argument for commit and describe
Browse files Browse the repository at this point in the history
  • Loading branch information
mati865 committed Sep 15, 2024
1 parent 6e72b1c commit a4316ea
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
allows to set a name for the remote instead of using the default
`origin`.

* `jj commit` and `jj describe` now accept `--author` option allowing to quickly change
author of given commit.

### Fixed bugs

* Fixed panic when parsing invalid conflict markers of a particular form.
Expand Down
15 changes: 15 additions & 0 deletions cli/src/commands/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::command_error::CommandError;
use crate::description_util::description_template;
use crate::description_util::edit_description;
use crate::description_util::join_message_paragraphs;
use crate::description_util::parse_user_name_email;
use crate::ui::Ui;

/// Update the description and create a new change on top.
Expand Down Expand Up @@ -50,6 +51,11 @@ pub(crate) struct CommitArgs {
/// $ JJ_USER='Foo Bar' [email protected] jj commit --reset-author
#[arg(long)]
reset_author: bool,
/// Set author to the provided string
///
/// This changes author name and email.
#[arg(long, conflicts_with = "reset_author")]
author: Option<String>,
}

#[instrument(skip_all)]
Expand Down Expand Up @@ -106,6 +112,15 @@ new working-copy commit.
if args.reset_author {
commit_builder.set_author(commit_builder.committer().clone());
}
if let Some(author) = &args.author {
let (name, email) = parse_user_name_email(author)?;
let new_author = jj_lib::backend::Signature {
name,
email,
timestamp: commit_builder.committer().timestamp.clone(),
};
commit_builder.set_author(new_author);
}

let description = if !args.message_paragraphs.is_empty() {
join_message_paragraphs(&args.message_paragraphs)
Expand Down
34 changes: 33 additions & 1 deletion cli/src/commands/describe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::io;
use std::io::Read;

use itertools::Itertools;
use jj_lib::backend::Signature;
use jj_lib::commit::CommitIteratorExt;
use jj_lib::object_id::ObjectId;
use tracing::instrument;
Expand All @@ -29,6 +30,7 @@ use crate::description_util::description_template;
use crate::description_util::edit_description;
use crate::description_util::edit_multiple_descriptions;
use crate::description_util::join_message_paragraphs;
use crate::description_util::parse_user_name_email;
use crate::description_util::ParsedBulkEditMessage;
use crate::ui::Ui;

Expand Down Expand Up @@ -72,6 +74,11 @@ pub(crate) struct DescribeArgs {
/// $ JJ_USER='Foo Bar' [email protected] jj describe --reset-author
#[arg(long)]
reset_author: bool,
/// Set author to the provided string
///
/// This changes author name and email while retaining timestamp.
#[arg(long, conflicts_with = "reset_author")]
author: Option<String>,
}

#[instrument(skip_all)]
Expand Down Expand Up @@ -112,6 +119,12 @@ pub(crate) fn cmd_describe(
None
};

let author_arg = args
.author
.as_deref()
.map(parse_user_name_email)
.transpose()?;

let commit_descriptions: Vec<(_, _)> = if args.no_edit || shared_description.is_some() {
commits
.iter()
Expand Down Expand Up @@ -139,6 +152,14 @@ pub(crate) fn cmd_describe(
let new_author = commit_builder.committer().clone();
commit_builder.set_author(new_author);
}
if let Some((name, email)) = author_arg.clone() {
let new_author = Signature {
name,
email,
timestamp: commit.author().timestamp.clone(),
};
commit_builder.set_author(new_author);
}
let temp_commit = commit_builder.write_hidden()?;
Ok((commit.id(), temp_commit))
})
Expand Down Expand Up @@ -195,7 +216,10 @@ pub(crate) fn cmd_describe(
let commit_descriptions: HashMap<_, _> = commit_descriptions
.into_iter()
.filter_map(|(commit, new_description)| {
if *new_description == *commit.description() && !args.reset_author {
if *new_description == *commit.description()
&& !args.reset_author
&& author_arg.is_none()
{
None
} else {
Some((commit.id(), new_description))
Expand All @@ -218,13 +242,21 @@ pub(crate) fn cmd_describe(
.collect_vec(),
|rewriter| {
let old_commit_id = rewriter.old_commit().id().clone();
let new_author = author_arg.clone().map(|(name, email)| Signature {
name,
email,
timestamp: rewriter.old_commit().author().timestamp.clone(),
});
let mut commit_builder = rewriter.rebase(command.settings())?;
if let Some(description) = commit_descriptions.get(&old_commit_id) {
commit_builder = commit_builder.set_description(description);
if args.reset_author {
let new_author = commit_builder.committer().clone();
commit_builder = commit_builder.set_author(new_author);
}
if let Some(new_author) = new_author {
commit_builder = commit_builder.set_author(new_author);
}
num_described += 1;
} else {
num_rebased += 1;
Expand Down
39 changes: 39 additions & 0 deletions cli/src/description_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::cli_util::edit_temp_file;
use crate::cli_util::short_commit_hash;
use crate::cli_util::WorkspaceCommandHelper;
use crate::cli_util::WorkspaceCommandTransaction;
use crate::command_error::cli_error;
use crate::command_error::CommandError;
use crate::formatter::PlainTextFormatter;
use crate::text_util;
Expand Down Expand Up @@ -251,13 +252,22 @@ pub fn description_template(
Ok(output.into_string_lossy())
}

pub fn parse_user_name_email(author: &str) -> Result<(String, String), CommandError> {
let re = regex::Regex::new(r"(?<name>.*?)\s*<(?<email>.+)>").unwrap();
let captures = re
.captures(author)
.ok_or_else(|| cli_error("Invalid author string"))?;
Ok((captures["name"].to_string(), captures["email"].to_string()))
}

#[cfg(test)]
mod tests {
use indexmap::indexmap;
use indoc::indoc;
use maplit::hashmap;

use super::parse_bulk_edit_message;
use super::parse_user_name_email;
use crate::description_util::ParseBulkEditMessageError;

#[test]
Expand Down Expand Up @@ -410,4 +420,33 @@ mod tests {
assert!(result.duplicates.is_empty());
assert!(result.unexpected.is_empty());
}

#[test]
fn test_parse_author() {
let expected_name = "Example";
let expected_email = "[email protected]";
let parsed = parse_user_name_email(&format!("{expected_name} <{expected_email}>")).unwrap();
assert_eq!(
(expected_name.to_string(), expected_email.to_string()),
parsed
);
}

#[test]
fn test_parse_author_with_utf8() {
let expected_name = "Ąćęłńóśżź";
let expected_email = "[email protected]";
let parsed = parse_user_name_email(&format!("{expected_name} <{expected_email}>")).unwrap();
assert_eq!(
(expected_name.to_string(), expected_email.to_string()),
parsed
);
}

#[test]
fn test_parse_author_without_name() {
let expected_email = "[email protected]";
let parsed = parse_user_name_email(&format!("<{expected_email}>")).unwrap();
assert_eq!(("".to_string(), expected_email.to_string()), parsed);
}
}
6 changes: 6 additions & 0 deletions cli/tests/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,9 @@ Update the description and create a new change on top
You can use it in combination with the JJ_USER and JJ_EMAIL environment variables to set a different author:
$ JJ_USER='Foo Bar' [email protected] jj commit --reset-author
* `--author <AUTHOR>` — Set author to the provided string
This changes author name and email.
Expand Down Expand Up @@ -599,6 +602,9 @@ Starts an editor to let you edit the description of changes. The editor will be
You can use it in combination with the JJ_USER and JJ_EMAIL environment variables to set a different author:
$ JJ_USER='Foo Bar' [email protected] jj describe --reset-author
* `--author <AUTHOR>` — Set author to the provided string
This changes author name and email while retaining timestamp.
Expand Down
33 changes: 28 additions & 5 deletions cli/tests/test_commit_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ fn test_commit_with_description_template() {

std::fs::write(workspace_path.join("file1"), "foo\n").unwrap();
std::fs::write(workspace_path.join("file2"), "bar\n").unwrap();
std::fs::write(workspace_path.join("file3"), "foobar\n").unwrap();

// Only file1 should be included in the diff
test_env.jj_cmd_ok(&workspace_path, &["commit", "file1"]);
Expand All @@ -230,19 +231,41 @@ fn test_commit_with_description_template() {
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"]);
// Only file2 with modified author should be included in the diff
test_env.jj_cmd_ok(
&workspace_path,
&[
"commit",
"--author",
r#""Another User <[email protected]>""#,
"file2",
],
);
insta::assert_snapshot!(
std::fs::read_to_string(test_env.env_root().join("editor")).unwrap(), @r###"
std::fs::read_to_string(test_env.env_root().join("editor")).unwrap(), @r#"
JJ: Author: Test User <test[email protected]> (2001-02-03 08:05:09)
JJ: Author: "Another User <another[email protected]> (2001-02-03 08:05:09)
JJ: Committer: Test User <[email protected]> (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.
"###);
"#);

// 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 <[email protected]> (2001-02-03 08:05:10)
JJ: Committer: Test User <[email protected]> (2001-02-03 08:05:10)
JJ: file3 | 1 +
JJ: 1 file changed, 1 insertion(+), 0 deletions(-)
JJ: Lines starting with "JJ: " (like this one) will be removed.
"#);
}

#[test]
Expand Down
84 changes: 65 additions & 19 deletions cli/tests/test_describe_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,29 +525,75 @@ fn test_describe_author() {
~
"###);

// Reset the author for the latest commit (the committer is always reset)
// Change the author for the latest commit (the committer is always reset)
test_env.jj_cmd_ok(
&repo_path,
&[
"describe",
"--config-toml",
r#"user.name = "Ove Ridder"
user.email = "[email protected]""#,
"--no-edit",
"--reset-author",
"--author",
r#""Super Seeder <[email protected]>""#,
],
);
insta::assert_snapshot!(get_signatures(), @r###"
@ Ove Ridder ove.ridder@example.com 2001-02-03 04:05:12.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:12.000 +07:00
insta::assert_snapshot!(get_signatures(), @r#"
@ "Super Seeder super.seeder@example.com 2001-02-03 04:05:10.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:12.000 +07:00
○ Test User [email protected] 2001-02-03 04:05:09.000 +07:00
│ Test User [email protected] 2001-02-03 04:05:09.000 +07:00
○ Test User [email protected] 2001-02-03 04:05:08.000 +07:00
│ Test User [email protected] 2001-02-03 04:05:08.000 +07:00
○ Test User [email protected] 2001-02-03 04:05:07.000 +07:00
│ Test User [email protected] 2001-02-03 04:05:07.000 +07:00
~
"###);
"#);

// Change the author for multiple commits (the committer is always reset)
test_env.jj_cmd_ok(
&repo_path,
&[
"describe",
"@---",
"@-",
"--no-edit",
"--author",
r#""Super Seeder <[email protected]>""#,
],
);
insta::assert_snapshot!(get_signatures(), @r#"
@ "Super Seeder [email protected] 2001-02-03 04:05:10.000 +07:00
│ Test User [email protected] 2001-02-03 04:05:14.000 +07:00
○ "Super Seeder [email protected] 2001-02-03 04:05:09.000 +07:00
│ Test User [email protected] 2001-02-03 04:05:14.000 +07:00
○ Test User [email protected] 2001-02-03 04:05:14.000 +07:00
│ Test User [email protected] 2001-02-03 04:05:14.000 +07:00
○ "Super Seeder [email protected] 2001-02-03 04:05:07.000 +07:00
│ Test User [email protected] 2001-02-03 04:05:14.000 +07:00
~
"#);

// Reset the author for the latest commit (the committer is always reset)
test_env.jj_cmd_ok(
&repo_path,
&[
"describe",
"--config-toml",
r#"user.name = "Ove Ridder"
user.email = "[email protected]""#,
"--no-edit",
"--reset-author",
],
);
insta::assert_snapshot!(get_signatures(), @r#"
@ Ove Ridder [email protected] 2001-02-03 04:05:16.000 +07:00
│ Ove Ridder [email protected] 2001-02-03 04:05:16.000 +07:00
○ "Super Seeder [email protected] 2001-02-03 04:05:09.000 +07:00
│ Test User [email protected] 2001-02-03 04:05:14.000 +07:00
○ Test User [email protected] 2001-02-03 04:05:14.000 +07:00
│ Test User [email protected] 2001-02-03 04:05:14.000 +07:00
○ "Super Seeder [email protected] 2001-02-03 04:05:07.000 +07:00
│ Test User [email protected] 2001-02-03 04:05:14.000 +07:00
~
"#);

// Reset the author for multiple commits (the committer is always reset)
test_env.jj_cmd_ok(
Expand All @@ -563,17 +609,17 @@ fn test_describe_author() {
"--reset-author",
],
);
insta::assert_snapshot!(get_signatures(), @r###"
@ Ove Ridder [email protected] 2001-02-03 04:05:14.000 +07:00
│ Ove Ridder [email protected] 2001-02-03 04:05:14.000 +07:00
○ Ove Ridder [email protected] 2001-02-03 04:05:14.000 +07:00
│ Ove Ridder [email protected] 2001-02-03 04:05:14.000 +07:00
○ Test User [email protected] 2001-02-03 04:05:08.000 +07:00
│ Ove Ridder [email protected] 2001-02-03 04:05:14.000 +07:00
○ Ove Ridder [email protected] 2001-02-03 04:05:14.000 +07:00
│ Ove Ridder [email protected] 2001-02-03 04:05:14.000 +07:00
insta::assert_snapshot!(get_signatures(), @r#"
@ Ove Ridder [email protected] 2001-02-03 04:05:18.000 +07:00
│ Ove Ridder [email protected] 2001-02-03 04:05:18.000 +07:00
○ Ove Ridder [email protected] 2001-02-03 04:05:18.000 +07:00
│ Ove Ridder [email protected] 2001-02-03 04:05:18.000 +07:00
○ Test User [email protected] 2001-02-03 04:05:14.000 +07:00
│ Ove Ridder [email protected] 2001-02-03 04:05:18.000 +07:00
○ Ove Ridder [email protected] 2001-02-03 04:05:18.000 +07:00
│ Ove Ridder [email protected] 2001-02-03 04:05:18.000 +07:00
~
"###);
"#);
}

#[test]
Expand Down

0 comments on commit a4316ea

Please sign in to comment.