mirror of
https://github.com/martinvonz/jj.git
synced 2024-10-04 09:28:53 +03:00
cli: add --author
argument for commit
and describe
This commit is contained in:
parent
b8f8827914
commit
2e9049b188
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
||||
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user