cli: add --author argument for commit and describe

This commit is contained in:
Mateusz Mikuła 2024-09-14 16:51:52 +02:00 committed by Mateusz Mikuła
parent b8f8827914
commit 2e9049b188
7 changed files with 193 additions and 31 deletions

View File

@ -64,6 +64,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
* CommitId / ChangeId template types now support `.normal_hex()`.
* `jj commit` and `jj describe` now accept `--author` option allowing to quickly change
author of given commit.
### Fixed bugs
* `jj git fetch -b <remote-git-branch-name>` will now warn if the branch(es)

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use jj_lib::backend::Signature;
use jj_lib::object_id::ObjectId;
use jj_lib::repo::Repo;
use tracing::instrument;
@ -22,6 +23,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::text_util::parse_author;
use crate::ui::Ui;
/// Update the description and create a new change on top.
@ -50,6 +52,16 @@ pub(crate) struct CommitArgs {
/// $ JJ_USER='Foo Bar' JJ_EMAIL=foo@bar.com 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 for non-discardable commits.
#[arg(
long,
conflicts_with = "reset_author",
value_parser = parse_author
)]
author: Option<(String, String)>,
}
#[instrument(skip_all)]
@ -106,6 +118,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 = Signature {
name,
email,
timestamp: commit_builder.author().timestamp.clone(),
};
commit_builder.set_author(new_author);
}
let description = if !args.message_paragraphs.is_empty() {
join_message_paragraphs(&args.message_paragraphs)

View File

@ -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;
@ -30,6 +31,7 @@ use crate::description_util::edit_description;
use crate::description_util::edit_multiple_descriptions;
use crate::description_util::join_message_paragraphs;
use crate::description_util::ParsedBulkEditMessage;
use crate::text_util::parse_author;
use crate::ui::Ui;
/// Update the change description or other metadata
@ -72,6 +74,16 @@ pub(crate) struct DescribeArgs {
/// $ JJ_USER='Foo Bar' JJ_EMAIL=foo@bar.com 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 for non-discardable commits.
#[arg(
long,
conflicts_with = "reset_author",
value_parser = parse_author
)]
author: Option<(String, String)>,
}
#[instrument(skip_all)]
@ -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))
})
@ -194,13 +214,14 @@ pub(crate) fn cmd_describe(
// `transform_descendants` below unnecessarily.
let commit_descriptions: HashMap<_, _> = commit_descriptions
.into_iter()
.filter_map(|(commit, new_description)| {
if *new_description == *commit.description() && !args.reset_author {
None
} else {
Some((commit.id(), new_description))
}
.filter(|(commit, new_description)| {
new_description != commit.description()
|| args.reset_author
|| args.author.as_ref().is_some_and(|(name, email)| {
name != &commit.author().name || email != &commit.author().email
})
})
.map(|(commit, new_description)| (commit.id(), new_description))
.collect();
let mut num_described = 0;
@ -225,6 +246,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;

View File

@ -261,6 +261,12 @@ pub fn write_wrapped(
})
}
pub fn parse_author(author: &str) -> Result<(String, String), &'static str> {
let re = regex::Regex::new(r"(?<name>.*?)\s*<(?<email>.+)>$").unwrap();
let captures = re.captures(author).ok_or("Invalid author string")?;
Ok((captures["name"].to_string(), captures["email"].to_string()))
}
#[cfg(test)]
mod tests {
use std::io::Write as _;
@ -632,4 +638,33 @@ mod tests {
"foo\n",
);
}
#[test]
fn test_parse_author() {
let expected_name = "Example";
let expected_email = "example@example.com";
let parsed = parse_author(&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 = "example@example.com";
let parsed = parse_author(&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 = "example@example.com";
let parsed = parse_author(&format!("<{expected_email}>")).unwrap();
assert_eq!(("".to_string(), expected_email.to_string()), parsed);
}
}

View File

@ -447,6 +447,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' JJ_EMAIL=foo@bar.com jj commit --reset-author
* `--author <AUTHOR>` — Set author to the provided string
This changes author name and email while retaining author timestamp for non-discardable commits.
@ -600,6 +603,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' JJ_EMAIL=foo@bar.com jj describe --reset-author
* `--author <AUTHOR>` — Set author to the provided string
This changes author name and email while retaining author timestamp for non-discardable commits.

View File

@ -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"]);
@ -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",
"Another User <another.user@example.com>",
"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.user@example.com> (2001-02-03 08:05:09)
JJ: Author: Another User <another.user@example.com> (2001-02-03 08:05:08)
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.
"###);
"#);
// 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:10)
JJ: Committer: Test User <test.user@example.com> (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]

View File

@ -525,6 +525,52 @@ fn test_describe_author() {
~
"###);
// Change the author for the latest commit (the committer is always reset)
test_env.jj_cmd_ok(
&repo_path,
&[
"describe",
"--no-edit",
"--author",
"Super Seeder <super.seeder@example.com>",
],
);
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 test.user@example.com 2001-02-03 04:05:09.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:09.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:08.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:08.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:07.000 +07:00
Test User test.user@example.com 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",
"Super Seeder <super.seeder@example.com>",
],
);
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:14.000 +07:00
Super Seeder super.seeder@example.com 2001-02-03 04:05:14.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:14.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:14.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:14.000 +07:00
Super Seeder super.seeder@example.com 2001-02-03 04:05:14.000 +07:00
Test User test.user@example.com 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,
@ -537,17 +583,17 @@ fn test_describe_author() {
"--reset-author",
],
);
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
Test User test.user@example.com 2001-02-03 04:05:09.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:09.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:08.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:08.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:07.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:07.000 +07:00
insta::assert_snapshot!(get_signatures(), @r#"
@ Ove Ridder ove.ridder@example.com 2001-02-03 04:05:16.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:16.000 +07:00
Super Seeder super.seeder@example.com 2001-02-03 04:05:14.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:14.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:14.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:14.000 +07:00
Super Seeder super.seeder@example.com 2001-02-03 04:05:14.000 +07:00
Test User test.user@example.com 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(
@ -563,17 +609,17 @@ fn test_describe_author() {
"--reset-author",
],
);
insta::assert_snapshot!(get_signatures(), @r###"
@ Ove Ridder ove.ridder@example.com 2001-02-03 04:05:14.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:14.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:14.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:14.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:08.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:14.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:14.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:14.000 +07:00
insta::assert_snapshot!(get_signatures(), @r#"
@ Ove Ridder ove.ridder@example.com 2001-02-03 04:05:18.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:18.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:18.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:18.000 +07:00
Test User test.user@example.com 2001-02-03 04:05:14.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:18.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:18.000 +07:00
Ove Ridder ove.ridder@example.com 2001-02-03 04:05:18.000 +07:00
~
"###);
"#);
}
#[test]