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 22, 2024
1 parent b8f8827 commit 2e9049b
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 30 deletions.
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

* `jj git fetch -b <remote-git-branch-name>` will now warn if the branch(es)
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

0 comments on commit 2e9049b

Please sign in to comment.