2 Commits

Author SHA1 Message Date
phundrak 88c77f8ac6 docs(README): update the README to reflect new features
Run checks and build archives / coverage-and-sonar (push) Successful in 6m59s
Run checks and build archives / build (windows-x86_64) (push) Successful in 7m38s
Run checks and build archives / build (linux-aarch64) (push) Successful in 7m45s
Run checks and build archives / build (linux-x86_64) (push) Successful in 9m1s
2026-06-14 15:13:21 +02:00
phundrak 5aa382a4c9 feat: implement --new flag
The new `--new` or `-n` flag allows to create a new revision after the
single revision being described.

Running `jj-cz --new` is the equivalent of running `jj-cz --new @`,
describing the current revision and creating a new revision after it.

Passing several revisions to `jj-cz` with the `--new` flag will result
in an error.
2026-06-14 15:13:21 +02:00
11 changed files with 20 additions and 361 deletions
+7 -10
View File
@@ -1,10 +1,6 @@
--- # jj-cz: Conventional Commits for Jujutsu
include_toc: true
gitea: none
---
<h1 align="center">Bakit</h1>
<h1 align="center">jj-cz: Conventional Commits for Jujutsu</h1>
<div align="center"> <div align="center">
<strong> <strong>
An interactive CLI tool that guides Jujutsu users through creating <a href="https://www.conventionalcommits.org/" rel="noopener">conventional commit</a> messages. An interactive CLI tool that guides Jujutsu users through creating <a href="https://www.conventionalcommits.org/" rel="noopener">conventional commit</a> messages.
@@ -38,8 +34,9 @@ gitea: none
## Features ## Features
- Interactive prompts for type, scope, breaking changes, ticket references, and description - Interactive prompts for type, scope, breaking changes, and description
- All 11 commit types with descriptions (feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert) - All 11 commit types with descriptions (feat, fix, docs, style,
refactor, perf, test, build, ci, chore, revert)
- Optional scope with validation - Optional scope with validation
- 72-character first-line limit enforcement - 72-character first-line limit enforcement
- Preview before applying - Preview before applying
@@ -71,8 +68,8 @@ revision, you can use the `-n` or `--new` flag.
```sh ```sh
jj-cz -n # equivalent of `jj-cz && jj new` jj-cz -n # equivalent of `jj-cz && jj new`
jj-cz xs -n # equivalent of `jj-cz xs && jj new xs` jj-cz xs -n # equivalent of `jj-cz xs && jj new`
jj-cz -n xs # equivalent of `jj-cz xs && jj new xs` jj-cz -n xs # equivalent of `jj-cz xs && jj new`
``` ```
You cannot, however, call `jj-cz` on multiple revisions with the `--new` flag active. You cannot, however, call `jj-cz` on multiple revisions with the `--new` flag active.
-7
View File
@@ -38,12 +38,6 @@ command = [
] ]
need_stdout = true need_stdout = true
[jobs.coverage]
command = [
"cargo", "tarpaulin", "--config", ".tarpaulin.local.toml", "--features", "test-utils"
]
need_stdout = true
[jobs.doc] [jobs.doc]
command = ["cargo", "doc", "--color", "always", "--no-deps"] command = ["cargo", "doc", "--color", "always", "--no-deps"]
need_stdout = false need_stdout = false
@@ -88,4 +82,3 @@ allow_warnings = true
[keybindings] [keybindings]
# alt-m = "job:my-job" # alt-m = "job:my-job"
c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target
v = "job:coverage"
+1 -4
View File
@@ -9,10 +9,7 @@ pkgs.stdenv.mkDerivation rec {
nativeBuildInputs = [pkgs.zip]; nativeBuildInputs = [pkgs.zip];
buildPhase = '' buildPhase = ''
mkdir -p $out/dist mkdir -p $out/dist
# zip -j $out/dist/${name}.zip ${bin}/bin/jj-cz* ${src}/README.md ${src}/LICENSE.* zip -j $out/dist/${name}.zip ${bin}/bin/jj-cz* ${src}/README.md ${src}/LICENSE.*
cp ${bin}/bin/jj-cz* $out/dist/
cp ${src}/README.md $out/dist/
cp ${src}/LICENSE.* $out/dist/
''; '';
installPhase = ""; installPhase = "";
dontConfigure = true; dontConfigure = true;
+2 -4
View File
@@ -4,12 +4,10 @@ pub trait Footer {
fn as_footer(&self) -> String { fn as_footer(&self) -> String {
let default = format!("{}: {}", self.prefix(), self.note()); let default = format!("{}: {}", self.prefix(), self.note());
let mut footer = if default.chars().count() > 72 { if default.chars().count() > 72 {
textwrap::wrap(&default, 71).join("\n ") textwrap::wrap(&default, 71).join("\n ")
} else { } else {
default default
}; }
footer.push('\n');
footer
} }
} }
+2 -27
View File
@@ -1,4 +1,4 @@
use super::{Body, BreakingChange, CommitType, Description, Footer, References, Scope}; use super::{Body, BreakingChange, CommitType, Description, Scope};
use thiserror::Error; use thiserror::Error;
/// Errors that can occur when creating a ConventionalCommit /// Errors that can occur when creating a ConventionalCommit
@@ -23,7 +23,6 @@ pub struct ConventionalCommit {
description: Description, description: Description,
breaking_change: BreakingChange, breaking_change: BreakingChange,
body: Body, body: Body,
references: References,
} }
impl ConventionalCommit { impl ConventionalCommit {
@@ -45,7 +44,6 @@ impl ConventionalCommit {
description: Description, description: Description,
breaking_change: BreakingChange, breaking_change: BreakingChange,
body: Body, body: Body,
references: References,
) -> Result<Self, CommitMessageError> { ) -> Result<Self, CommitMessageError> {
let commit = Self { let commit = Self {
commit_type, commit_type,
@@ -53,7 +51,6 @@ impl ConventionalCommit {
description, description,
breaking_change, breaking_change,
body, body,
references,
}; };
let len = commit.first_line_len(); let len = commit.first_line_len();
if len > Self::FIRST_LINE_MAX_LENGTH { if len > Self::FIRST_LINE_MAX_LENGTH {
@@ -95,7 +92,6 @@ impl ConventionalCommit {
&self.description, &self.description,
&self.breaking_change, &self.breaking_change,
&self.body, &self.body,
&self.references,
) )
} }
@@ -110,16 +106,14 @@ impl ConventionalCommit {
description: &Description, description: &Description,
breaking_change: &BreakingChange, breaking_change: &BreakingChange,
body: &Body, body: &Body,
references: &References,
) -> String { ) -> String {
let scope = scope.header_segment(); let scope = scope.header_segment();
let breaking_change_header = breaking_change.header_segment(); let breaking_change_header = breaking_change.header_segment();
let breaking_change_footer = breaking_change.as_footer(); let breaking_change_footer = breaking_change.as_footer();
let refs_footer = references.as_footer();
format!( format!(
r#"{commit_type}{scope}{breaking_change_header}: {description} r#"{commit_type}{scope}{breaking_change_header}: {description}
{} {}
{breaking_change_footer}{refs_footer}"#, {breaking_change_footer}"#,
body.format() body.format()
) )
.trim() .trim()
@@ -160,7 +154,6 @@ mod tests {
description, description,
breaking_change, breaking_change,
Body::default(), Body::default(),
References::default(),
) )
.expect("test commit should have valid line length") .expect("test commit should have valid line length")
} }
@@ -644,7 +637,6 @@ mod tests {
Description::parse(&desc_44).unwrap(), Description::parse(&desc_44).unwrap(),
BreakingChange::No, BreakingChange::No,
Body::default(), Body::default(),
References::default(),
); );
assert!(result.is_ok()); assert!(result.is_ok());
let commit = result.unwrap(); let commit = result.unwrap();
@@ -675,7 +667,6 @@ mod tests {
Description::parse(&desc_31).unwrap(), Description::parse(&desc_31).unwrap(),
BreakingChange::No, BreakingChange::No,
Body::default(), Body::default(),
References::default(),
); );
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
@@ -700,7 +691,6 @@ mod tests {
Description::parse(&desc_40).unwrap(), Description::parse(&desc_40).unwrap(),
BreakingChange::No, BreakingChange::No,
Body::default(), Body::default(),
References::default(),
); );
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
@@ -721,7 +711,6 @@ mod tests {
test_description("quick fix"), test_description("quick fix"),
BreakingChange::No, BreakingChange::No,
Body::default(), Body::default(),
References::default(),
); );
assert!(result.is_ok()); assert!(result.is_ok());
} }
@@ -735,7 +724,6 @@ mod tests {
test_description("add feature"), test_description("add feature"),
BreakingChange::No, BreakingChange::No,
Body::default(), Body::default(),
References::default(),
); );
assert!(result.is_ok()); assert!(result.is_ok());
} }
@@ -762,7 +750,6 @@ mod tests {
test_description("test"), test_description("test"),
BreakingChange::No, BreakingChange::No,
Body::default(), Body::default(),
References::default(),
); );
// Just verify it's a Result by using is_ok() // Just verify it's a Result by using is_ok()
assert!(result.is_ok()); assert!(result.is_ok());
@@ -794,7 +781,6 @@ mod tests {
desc, desc,
BreakingChange::No, BreakingChange::No,
Body::default(), Body::default(),
References::default(),
); );
// new() itself calls git_conventional::Commit::parse internally, so // new() itself calls git_conventional::Commit::parse internally, so
// if this is Ok, SC-002 is satisfied for this case. // if this is Ok, SC-002 is satisfied for this case.
@@ -932,7 +918,6 @@ mod tests {
Description::parse(&desc_44).unwrap(), Description::parse(&desc_44).unwrap(),
BreakingChange::Yes, BreakingChange::Yes,
Body::default(), Body::default(),
References::default(),
); );
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
@@ -955,7 +940,6 @@ mod tests {
test_description("quick fix"), test_description("quick fix"),
long_note.into(), long_note.into(),
Body::default(), Body::default(),
References::default(),
); );
assert!(result.is_ok()); assert!(result.is_ok());
} }
@@ -975,7 +959,6 @@ mod tests {
&commit.description, &commit.description,
&BreakingChange::No, &BreakingChange::No,
&Body::default(), &Body::default(),
&References::default(),
); );
assert_eq!(preview, commit.format()); assert_eq!(preview, commit.format());
} }
@@ -989,7 +972,6 @@ mod tests {
&test_description("drop legacy API"), &test_description("drop legacy API"),
&"removes legacy endpoint".into(), &"removes legacy endpoint".into(),
&Body::default(), &Body::default(),
&References::default(),
); );
assert_eq!( assert_eq!(
preview, preview,
@@ -1006,7 +988,6 @@ mod tests {
&test_description("drop Node 6"), &test_description("drop Node 6"),
&"Node 6 is no longer supported".into(), &"Node 6 is no longer supported".into(),
&Body::default(), &Body::default(),
&References::default(),
); );
assert_eq!( assert_eq!(
preview, preview,
@@ -1112,7 +1093,6 @@ mod tests {
test_description("add feature"), test_description("add feature"),
BreakingChange::No, BreakingChange::No,
Body::from("This explains the change."), Body::from("This explains the change."),
References::default(),
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
@@ -1130,7 +1110,6 @@ mod tests {
test_description("handle null response"), test_description("handle null response"),
BreakingChange::No, BreakingChange::No,
Body::from("Null responses were previously unhandled."), Body::from("Null responses were previously unhandled."),
References::default(),
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
@@ -1148,7 +1127,6 @@ mod tests {
test_description("update README"), test_description("update README"),
BreakingChange::No, BreakingChange::No,
Body::from("First paragraph.\n\nSecond paragraph."), Body::from("First paragraph.\n\nSecond paragraph."),
References::default(),
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
@@ -1168,7 +1146,6 @@ mod tests {
test_description("drop legacy API"), test_description("drop legacy API"),
"removes legacy endpoint".into(), "removes legacy endpoint".into(),
Body::from("The endpoint was deprecated in v2."), Body::from("The endpoint was deprecated in v2."),
References::default(),
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
@@ -1186,7 +1163,6 @@ mod tests {
&test_description("add feature"), &test_description("add feature"),
&BreakingChange::No, &BreakingChange::No,
&Body::from("This explains the change."), &Body::from("This explains the change."),
&References::default(),
); );
assert_eq!(preview, "feat: add feature\n\nThis explains the change."); assert_eq!(preview, "feat: add feature\n\nThis explains the change.");
} }
@@ -1202,7 +1178,6 @@ mod tests {
&test_description("drop old API"), &test_description("drop old API"),
&"old API removed".into(), &"old API removed".into(),
&Body::from("Migration guide: see CHANGELOG."), &Body::from("Migration guide: see CHANGELOG."),
&References::default(),
); );
assert_eq!( assert_eq!(
preview, preview,
-3
View File
@@ -18,6 +18,3 @@ pub use body::Body;
mod message; mod message;
pub use message::{CommitMessageError, ConventionalCommit}; pub use message::{CommitMessageError, ConventionalCommit};
mod references;
pub use references::References;
-186
View File
@@ -1,186 +0,0 @@
use super::Footer;
#[repr(transparent)]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct References(Vec<String>);
impl<T> From<T> for References
where
T: ToString,
{
fn from(value: T) -> Self {
let references: Vec<String> = value
.to_string()
.split(",")
.flat_map(|e| match e.trim() {
"" => None,
e => Some(e.to_string()),
})
.collect();
Self(references)
}
}
impl Footer for References {
fn prefix(&self) -> &str {
"Refs: "
}
fn note(&self) -> &str {
""
}
fn as_footer(&self) -> String {
if self.0.is_empty() {
String::new()
} else {
let footers: Vec<String> = self
.0
.iter()
.map(|r| format!("{}{r}\n", self.prefix()))
.collect();
footers.join("")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Default is empty
#[test]
fn default_is_empty() {
let refs = References::default();
assert!(refs.0.is_empty());
}
/// Empty input produces empty references
#[test]
fn from_empty_string() {
assert_eq!(References::from(""), References::default());
}
/// Whitespace-only input produces empty references
#[test]
fn from_whitespace_only() {
assert_eq!(References::from(" "), References::default());
}
/// Single reference without commas
#[test]
fn from_single_reference() {
let refs = References::from("#123");
assert_eq!(refs.0, vec!["#123".to_string()]);
}
/// Comma-separated references are split and trimmed
#[test]
fn from_comma_separated() {
let refs = References::from("#123, #456");
assert_eq!(refs.0, vec!["#123".to_string(), "#456".to_string()]);
}
/// Leading whitespace around references is trimmed
#[test]
fn from_trims_leading_whitespace() {
let refs = References::from(" #123, #456");
assert_eq!(refs.0, vec!["#123".to_string(), "#456".to_string()]);
}
/// Trailing whitespace around references is trimmed
#[test]
fn from_trims_trailing_whitespace() {
let refs = References::from("#123 , #456 ");
assert_eq!(refs.0, vec!["#123".to_string(), "#456".to_string()]);
}
/// Empty segments from consecutive commas are filtered out
#[test]
fn from_filters_empty_segments() {
let refs = References::from("#123,,, #456");
assert_eq!(refs.0, vec!["#123".to_string(), "#456".to_string()]);
}
/// From works with owned String
#[test]
fn from_owned_string() {
let input = "#123, #456".to_string();
let refs = References::from(input);
assert_eq!(refs.0, vec!["#123".to_string(), "#456".to_string()]);
}
/// as_footer returns empty string for empty references
#[test]
fn as_footer_empty() {
let refs = References::default();
assert_eq!(refs.as_footer(), "");
}
/// as_footer returns single line for one reference
#[test]
fn as_footer_single() {
let refs = References::from("#123");
assert_eq!(refs.as_footer(), "Refs: #123\n");
}
/// as_footer returns multiple lines for multiple references
#[test]
fn as_footer_multiple() {
let refs = References::from("#123, #456");
assert_eq!(refs.as_footer(), "Refs: #123\nRefs: #456\n");
}
/// as_footer handles Jira-style references
#[test]
fn as_footer_jira_style() {
let refs = References::from("OPS-456, PROJ-789");
assert_eq!(refs.as_footer(), "Refs: OPS-456\nRefs: PROJ-789\n");
}
/// Footer trait prefix returns correct value
#[test]
fn footer_prefix() {
let refs = References::default();
assert_eq!(refs.prefix(), "Refs: ");
}
/// Footer trait note returns empty string
#[test]
fn footer_note() {
let refs = References::default();
assert_eq!(refs.note(), "");
}
/// Clone produces equal value
#[test]
fn clone_equality() {
let refs = References::from("#123, #456");
let cloned = refs.clone();
assert_eq!(refs, cloned);
}
/// Debug output is available
#[test]
fn debug_output() {
let refs = References::from("#123");
let debug = format!("{:?}", refs);
assert!(debug.contains("References"));
}
/// Different references are not equal
#[test]
fn inequality_different_refs() {
let a = References::from("#123");
let b = References::from("#456");
assert_ne!(a, b);
}
/// Empty vs non-empty are not equal
#[test]
fn inequality_empty_vs_non_empty() {
let empty = References::default();
let non_empty = References::from("#123");
assert_ne!(empty, non_empty);
}
}
+1 -59
View File
@@ -8,7 +8,7 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::{ use crate::{
commit::types::{Body, BreakingChange, CommitType, Description, References, Scope}, commit::types::{Body, BreakingChange, CommitType, Description, Scope},
error::Error, error::Error,
prompts::prompter::Prompter, prompts::prompter::Prompter,
}; };
@@ -20,7 +20,6 @@ enum MockResponse {
Scope(Scope), Scope(Scope),
Description(Description), Description(Description),
BreakingChange(BreakingChange), BreakingChange(BreakingChange),
References(References),
Body(Body), Body(Body),
Confirm(bool), Confirm(bool),
Error(Error), Error(Error),
@@ -82,15 +81,6 @@ impl MockPrompts {
self self
} }
/// Configure the mock to return specific references
pub fn with_references(self, references: References) -> Self {
self.responses
.lock()
.unwrap()
.push(MockResponse::References(references));
self
}
/// Configure the mock to return a specific body response /// Configure the mock to return a specific body response
pub fn with_body(self, body: Body) -> Self { pub fn with_body(self, body: Body) -> Self {
self.responses self.responses
@@ -150,14 +140,6 @@ impl MockPrompts {
.contains(&"input_breaking_change".to_string()) .contains(&"input_breaking_change".to_string())
} }
/// Check if input_references was called
pub fn was_references_called(&self) -> bool {
self.prompts_called
.lock()
.unwrap()
.contains(&"input_references".to_string())
}
/// Check if confirm_apply was called /// Check if confirm_apply was called
pub fn was_confirm_called(&self) -> bool { pub fn was_confirm_called(&self) -> bool {
self.prompts_called self.prompts_called
@@ -225,18 +207,6 @@ impl Prompter for MockPrompts {
} }
} }
fn input_references(&self) -> Result<References, Error> {
self.prompts_called
.lock()
.unwrap()
.push("input_references".to_string());
match self.responses.lock().unwrap().remove(0) {
MockResponse::References(r) => Ok(r),
MockResponse::Error(e) => Err(e),
_ => panic!("MockPrompts: Expected References response, got different type"),
}
}
fn input_body(&self) -> Result<Body, Error> { fn input_body(&self) -> Result<Body, Error> {
self.prompts_called self.prompts_called
.lock() .lock()
@@ -366,34 +336,6 @@ mod tests {
assert!(mock.emitted_messages().is_empty()); assert!(mock.emitted_messages().is_empty());
} }
#[test]
fn mock_input_references() {
let refs = References::from("#123, #456");
let mock = MockPrompts::new().with_references(refs.clone());
let result = mock.input_references();
assert!(result.is_ok());
assert_eq!(result.unwrap(), refs);
assert!(mock.was_references_called());
}
#[test]
fn mock_input_references_default() {
let mock = MockPrompts::new().with_references(References::default());
let result = mock.input_references();
assert!(result.is_ok());
assert_eq!(result.unwrap(), References::default());
assert!(mock.was_references_called());
}
#[test]
fn mock_input_references_error() {
let mock = MockPrompts::new().with_error(Error::Cancelled);
let result = mock.input_references();
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::Cancelled));
assert!(mock.was_references_called());
}
#[test] #[test]
fn mock_input_breaking_change_no() { fn mock_input_breaking_change_no() {
let mock = MockPrompts::new().with_breaking_change(BreakingChange::No); let mock = MockPrompts::new().with_breaking_change(BreakingChange::No);
+1 -17
View File
@@ -9,7 +9,7 @@ use inquire::{Confirm, Text};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use crate::{ use crate::{
commit::types::{Body, BreakingChange, CommitType, Description, References, Scope}, commit::types::{Body, BreakingChange, CommitType, Description, Scope},
error::Error, error::Error,
}; };
@@ -33,9 +33,6 @@ pub trait Prompter {
/// Prompt the user to optionally add a free-form body via an external editor /// Prompt the user to optionally add a free-form body via an external editor
fn input_body(&self) -> Result<Body, Error>; fn input_body(&self) -> Result<Body, Error>;
/// Prompt the user to optionally add comma-separated ticket references
fn input_references(&self) -> Result<References, Error>;
/// Prompt the user to confirm applying the commit message /// Prompt the user to confirm applying the commit message
fn confirm_apply(&self, message: &str) -> Result<bool, Error>; fn confirm_apply(&self, message: &str) -> Result<bool, Error>;
@@ -94,19 +91,6 @@ impl Prompter for RealPrompts {
} }
} }
fn input_references(&self) -> Result<References, Error> {
let answer = inquire::Text::new("Enter comma-separated references (optional):")
.with_help_message("References are optional. If provided, will become footer(s) in the commit message. References must be comma-separated.")
.with_placeholder("Leave empty if no references")
.prompt_skippable()
.map_err(|_| Error::Cancelled)?;
match answer {
None => Ok(References::default()),
Some(s) if s.trim().is_empty() => Ok(References::default()),
Some(s) => Ok(References::from(s)),
}
}
fn input_description(&self) -> Result<Description, Error> { fn input_description(&self) -> Result<Description, Error> {
loop { loop {
let answer = Text::new("Enter description (required):") let answer = Text::new("Enter description (required):")
+4 -42
View File
@@ -6,7 +6,7 @@
use crate::{ use crate::{
commit::types::{ commit::types::{
Body, BreakingChange, CommitMessageError, CommitType, ConventionalCommit, Description, Body, BreakingChange, CommitMessageError, CommitType, ConventionalCommit, Description,
References, Scope, Scope,
}, },
error::Error, error::Error,
jj::JjExecutor, jj::JjExecutor,
@@ -65,16 +65,8 @@ impl<J: JjExecutor, P: Prompter> CommitWorkflow<J, P> {
let scope = self.scope_input()?; let scope = self.scope_input()?;
let description = self.description_input()?; let description = self.description_input()?;
let breaking_change = self.breaking_change_input()?; let breaking_change = self.breaking_change_input()?;
let references = self.references_input()?;
let body = self.body_input()?; let body = self.body_input()?;
match self.preview_and_confirm( match self.preview_and_confirm(commit_type, scope, description, breaking_change, body) {
commit_type,
scope,
description,
breaking_change,
body,
references,
) {
Ok(conventional_commit) => { Ok(conventional_commit) => {
self.executor self.executor
.describe(revset, &conventional_commit.to_string()) .describe(revset, &conventional_commit.to_string())
@@ -121,11 +113,6 @@ impl<J: JjExecutor, P: Prompter> CommitWorkflow<J, P> {
self.prompts.input_breaking_change() self.prompts.input_breaking_change()
} }
/// Prompt user for references
fn references_input(&self) -> Result<References, Error> {
self.prompts.input_references()
}
/// Prompt user to optionally add a free-form body via an external editor /// Prompt user to optionally add a free-form body via an external editor
fn body_input(&self) -> Result<Body, Error> { fn body_input(&self) -> Result<Body, Error> {
self.prompts.input_body() self.prompts.input_body()
@@ -142,7 +129,6 @@ impl<J: JjExecutor, P: Prompter> CommitWorkflow<J, P> {
description: Description, description: Description,
breaking_change: BreakingChange, breaking_change: BreakingChange,
body: Body, body: Body,
references: References,
) -> Result<ConventionalCommit, Error> { ) -> Result<ConventionalCommit, Error> {
// Format the message for preview // Format the message for preview
let message = ConventionalCommit::format_preview( let message = ConventionalCommit::format_preview(
@@ -151,7 +137,6 @@ impl<J: JjExecutor, P: Prompter> CommitWorkflow<J, P> {
&description, &description,
&breaking_change, &breaking_change,
&body, &body,
&references,
); );
// Try to build the conventional commit (this validates the 72-char limit) // Try to build the conventional commit (this validates the 72-char limit)
@@ -161,7 +146,6 @@ impl<J: JjExecutor, P: Prompter> CommitWorkflow<J, P> {
description.clone(), description.clone(),
breaking_change, breaking_change,
body, body,
references,
) { ) {
Ok(cc) => cc, Ok(cc) => cc,
Err(CommitMessageError::FirstLineTooLong { actual, max }) => { Err(CommitMessageError::FirstLineTooLong { actual, max }) => {
@@ -292,15 +276,8 @@ mod tests {
let description = Description::parse("test description").unwrap(); let description = Description::parse("test description").unwrap();
let breaking_change = BreakingChange::No; let breaking_change = BreakingChange::No;
let body = Body::default(); let body = Body::default();
let references = References::default(); let result =
let result = workflow.preview_and_confirm( workflow.preview_and_confirm(commit_type, scope, description, breaking_change, body);
commit_type,
scope,
description,
breaking_change,
body,
references,
);
assert!(result.is_ok()); assert!(result.is_ok());
} }
@@ -345,7 +322,6 @@ mod tests {
.with_scope(Scope::empty()) .with_scope(Scope::empty())
.with_description(Description::parse("add new feature").unwrap()) .with_description(Description::parse("add new feature").unwrap())
.with_breaking_change(BreakingChange::Yes) .with_breaking_change(BreakingChange::Yes)
.with_references(References::default())
.with_body(Body::default()) .with_body(Body::default())
.with_confirm(true); .with_confirm(true);
@@ -379,7 +355,6 @@ mod tests {
.with_scope(Scope::parse("api").unwrap()) .with_scope(Scope::parse("api").unwrap())
.with_description(Description::parse("fix bug").unwrap()) .with_description(Description::parse("fix bug").unwrap())
.with_breaking_change(BreakingChange::No) .with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::default()) .with_body(Body::default())
.with_confirm(false); // User cancels at confirmation .with_confirm(false); // User cancels at confirmation
@@ -404,13 +379,11 @@ mod tests {
.with_scope(Scope::parse("very-long-scope-name").unwrap()) .with_scope(Scope::parse("very-long-scope-name").unwrap())
.with_description(Description::parse("a".repeat(45)).unwrap()) .with_description(Description::parse("a".repeat(45)).unwrap())
.with_breaking_change(BreakingChange::No) .with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::default()) .with_body(Body::default())
// Second iteration: short enough to succeed // Second iteration: short enough to succeed
.with_scope(Scope::empty()) .with_scope(Scope::empty())
.with_description(Description::parse("short description").unwrap()) .with_description(Description::parse("short description").unwrap())
.with_breaking_change(BreakingChange::No) .with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::default()) .with_body(Body::default())
.with_confirm(true); .with_confirm(true);
@@ -505,7 +478,6 @@ mod tests {
.with_scope(Scope::empty()) .with_scope(Scope::empty())
.with_description(Description::parse("test").unwrap()) .with_description(Description::parse("test").unwrap())
.with_breaking_change(BreakingChange::Yes) .with_breaking_change(BreakingChange::Yes)
.with_references(References::default())
.with_body(Body::default()) .with_body(Body::default())
.with_confirm(true); .with_confirm(true);
@@ -529,7 +501,6 @@ mod tests {
.with_scope(Scope::empty()) .with_scope(Scope::empty())
.with_description(Description::parse("test").unwrap()) .with_description(Description::parse("test").unwrap())
.with_breaking_change(BreakingChange::Yes) .with_breaking_change(BreakingChange::Yes)
.with_references(References::default())
.with_body(Body::default()) .with_body(Body::default())
.with_confirm(true); .with_confirm(true);
@@ -548,7 +519,6 @@ mod tests {
.with_scope(Scope::parse("api").unwrap()) .with_scope(Scope::parse("api").unwrap())
.with_description(Description::parse("test").unwrap()) .with_description(Description::parse("test").unwrap())
.with_breaking_change(BreakingChange::No) .with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::default()) .with_body(Body::default())
.with_confirm(true); .with_confirm(true);
@@ -596,7 +566,6 @@ mod tests {
Description::parse("remove old API").unwrap(), Description::parse("remove old API").unwrap(),
BreakingChange::Yes, BreakingChange::Yes,
Body::default(), Body::default(),
References::default(),
); );
assert!(result.is_ok(), "expected Ok, got: {:?}", result); assert!(result.is_ok(), "expected Ok, got: {:?}", result);
@@ -624,7 +593,6 @@ mod tests {
Description::parse("drop legacy API").unwrap(), Description::parse("drop legacy API").unwrap(),
breaking_change, breaking_change,
Body::default(), Body::default(),
References::default(),
); );
assert!(result.is_ok(), "expected Ok, got: {:?}", result); assert!(result.is_ok(), "expected Ok, got: {:?}", result);
@@ -655,7 +623,6 @@ mod tests {
.with_scope(Scope::empty()) .with_scope(Scope::empty())
.with_description(Description::parse("remove old API").unwrap()) .with_description(Description::parse("remove old API").unwrap())
.with_breaking_change(BreakingChange::Yes) .with_breaking_change(BreakingChange::Yes)
.with_references(References::default())
.with_body(Body::default()) .with_body(Body::default())
.with_confirm(true); .with_confirm(true);
@@ -698,7 +665,6 @@ mod tests {
Description::parse("add feature").unwrap(), Description::parse("add feature").unwrap(),
BreakingChange::No, BreakingChange::No,
Body::from("This explains the change."), Body::from("This explains the change."),
References::default(),
); );
assert!(result.is_ok(), "expected Ok, got: {:?}", result); assert!(result.is_ok(), "expected Ok, got: {:?}", result);
@@ -726,7 +692,6 @@ mod tests {
Description::parse("drop legacy API").unwrap(), Description::parse("drop legacy API").unwrap(),
"removes legacy endpoint".into(), "removes legacy endpoint".into(),
Body::from("The endpoint was deprecated in v2."), Body::from("The endpoint was deprecated in v2."),
References::default(),
); );
assert!(result.is_ok(), "expected Ok, got: {:?}", result); assert!(result.is_ok(), "expected Ok, got: {:?}", result);
@@ -753,7 +718,6 @@ mod tests {
.with_scope(Scope::empty()) .with_scope(Scope::empty())
.with_description(Description::parse("add feature").unwrap()) .with_description(Description::parse("add feature").unwrap())
.with_breaking_change(BreakingChange::No) .with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::from("This explains the change.")) .with_body(Body::from("This explains the change."))
.with_confirm(true); .with_confirm(true);
@@ -786,7 +750,6 @@ mod tests {
.with_scope(Scope::empty()) .with_scope(Scope::empty())
.with_description(Description::parse("fix crash").unwrap()) .with_description(Description::parse("fix crash").unwrap())
.with_breaking_change(BreakingChange::No) .with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::default()) .with_body(Body::default())
.with_confirm(true); .with_confirm(true);
@@ -840,7 +803,6 @@ mod tests {
.with_scope(Scope::empty()) .with_scope(Scope::empty())
.with_description(Description::parse("add feature").unwrap()) .with_description(Description::parse("add feature").unwrap())
.with_breaking_change(BreakingChange::No) .with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::default()) .with_body(Body::default())
.with_confirm(true); .with_confirm(true);
+2 -2
View File
@@ -170,7 +170,7 @@ fn test_from_poison_error() {
/// Test from_revset_evaluation_error constructs RevsetResolutionError /// Test from_revset_evaluation_error constructs RevsetResolutionError
#[test] #[test]
fn test_from_revset_evaluation_error() { fn test_from_revset_evaluation_error() {
let underlying = std::io::Error::other("store failure"); let underlying = std::io::Error::new(std::io::ErrorKind::Other, "store failure");
let eval_err = jj_lib::revset::RevsetEvaluationError::Other(Box::new(underlying)); let eval_err = jj_lib::revset::RevsetEvaluationError::Other(Box::new(underlying));
let error = Error::from_revset_evaluation_error("@", eval_err); let error = Error::from_revset_evaluation_error("@", eval_err);
assert!(matches!(error, Error::RevsetResolutionError { .. })); assert!(matches!(error, Error::RevsetResolutionError { .. }));
@@ -281,7 +281,7 @@ fn test_error_matching_all_variants() {
} }
// Verify all variants can be cloned // Verify all variants can be cloned
let cloned: Vec<Error> = variants.to_vec(); let cloned: Vec<Error> = variants.iter().map(|e| e.clone()).collect();
assert_eq!(variants.len(), cloned.len()); assert_eq!(variants.len(), cloned.len());
for (original, clone) in variants.iter().zip(cloned.iter()) { for (original, clone) in variants.iter().zip(cloned.iter()) {
assert_eq!(original, clone); assert_eq!(original, clone);