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 17, 2024
1 parent ac605d2 commit 981b856
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

* `jj squash`: the `-k` flag can be used as a shorthand for `--keep-emptied`.

* `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
45 changes: 45 additions & 0 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3298,3 +3298,48 @@ fn format_template_aliases_hint(template_aliases: &TemplateAliasesMap) -> String
);
hint
}

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

#[cfg(test)]
mod test {
use super::parse_author_arg;

#[test]
fn test_parse_author() {
let expected_name = "Example";
let expected_email = "[email protected]";
let parsed = parse_author_arg(&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_author_arg(&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_author_arg(&format!("<{expected_email}>")).unwrap();
assert_eq!(("".to_string(), expected_email.to_string()), parsed);
}
}
19 changes: 19 additions & 0 deletions cli/src/commands/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use jj_lib::object_id::ObjectId;
use jj_lib::repo::Repo;
use tracing::instrument;

use crate::cli_util::parse_author_arg;
use crate::cli_util::CommandHelper;
use crate::command_error::user_error;
use crate::command_error::CommandError;
Expand Down Expand Up @@ -50,6 +51,16 @@ 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 while retaining author
/// timestamp if the description is filled.
#[arg(
long,
conflicts_with = "reset_author",
value_parser = parse_author_arg
)]
author: Option<(String, String)>,
}

#[instrument(skip_all)]
Expand Down Expand Up @@ -106,6 +117,14 @@ new working-copy commit.
if args.reset_author {
commit_builder.set_author(commit_builder.committer().clone());
}
if let Some((name, email)) = args.author.clone() {
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
33 changes: 32 additions & 1 deletion cli/src/commands/describe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ 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;

use crate::cli_util::parse_author_arg;
use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg;
use crate::command_error::user_error;
Expand Down Expand Up @@ -72,6 +74,16 @@ 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 author
/// timestamp if the description is filled.
#[arg(
long,
conflicts_with = "reset_author",
value_parser = parse_author_arg
)]
author: Option<(String, String)>,
}

#[instrument(skip_all)]
Expand Down Expand Up @@ -139,6 +151,14 @@ pub(crate) fn cmd_describe(
let new_author = commit_builder.committer().clone();
commit_builder.set_author(new_author);
}
if let Some((name, email)) = args.author.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 +215,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
&& args.author.is_none()
{
None
} else {
Some((commit.id(), new_description))
Expand Down Expand Up @@ -225,6 +248,14 @@ pub(crate) fn cmd_describe(
let new_author = commit_builder.committer().clone();
commit_builder = commit_builder.set_author(new_author);
}
if let Some((name, email)) = args.author.clone() {
let new_author = Signature {
name,
email,
timestamp: commit_builder.author().timestamp.clone(),
};
commit_builder = commit_builder.set_author(new_author);
}
num_described += 1;
} else {
num_rebased += 1;
Expand Down
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 while retaining author timestamp if the description is filled.
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 author timestamp if the description is filled.
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:12.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:12.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: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
│ Test User [email protected] 2001-02-03 04:05:14.000 +07:00
○ "Super Seeder [email protected] 2001-02-03 04:05:14.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: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
│ Test User [email protected] 2001-02-03 04:05:14.000 +07:00
○ "Super Seeder [email protected] 2001-02-03 04:05:14.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 981b856

Please sign in to comment.