Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: add --author argument for commit and describe #4466

Merged
merged 1 commit into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

* Fixed panic when parsing invalid conflict markers of a particular form.
Expand Down
20 changes: 20 additions & 0 deletions cli/src/commands/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -50,6 +52,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 for non-discardable commits.
#[arg(
long,
conflicts_with = "reset_author",
value_parser = parse_author
)]
author: Option<(String, String)>,
}

#[instrument(skip_all)]
Expand Down Expand Up @@ -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)
Expand Down
41 changes: 35 additions & 6 deletions 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 @@ -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
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 for non-discardable commits.
#[arg(
long,
conflicts_with = "reset_author",
value_parser = parse_author
)]
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 @@ -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;
Expand All @@ -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;
Expand Down
35 changes: 35 additions & 0 deletions cli/src/text_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _;
Expand Down Expand Up @@ -632,4 +638,33 @@ mod tests {
"foo\n",
);
}

#[test]
fn test_parse_author() {
let expected_name = "Example";
let expected_email = "[email protected]";
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 = "[email protected]";
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 = "[email protected]";
let parsed = parse_author(&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 @@ -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' [email protected] 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.



Expand Down Expand Up @@ -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' [email protected] 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.



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",
"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:08)
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",
"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",
"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
Loading