feat(references): add ticket reference footers
Run checks and build archives / build (linux-aarch64) (push) Successful in 6m58s
Run checks and build archives / coverage-and-sonar (push) Successful in 5m36s
Run checks and build archives / build (linux-x86_64) (push) Successful in 8m14s
Run checks and build archives / build (windows-x86_64) (push) Successful in 6m56s

Refs: #4
This commit is contained in:
2026-06-14 16:24:45 +02:00
parent d1c67afd33
commit a5bec93228
10 changed files with 349 additions and 15 deletions
+42 -4
View File
@@ -6,7 +6,7 @@
use crate::{
commit::types::{
Body, BreakingChange, CommitMessageError, CommitType, ConventionalCommit, Description,
Scope,
References, Scope,
},
error::Error,
jj::JjExecutor,
@@ -65,8 +65,16 @@ impl<J: JjExecutor, P: Prompter> CommitWorkflow<J, P> {
let scope = self.scope_input()?;
let description = self.description_input()?;
let breaking_change = self.breaking_change_input()?;
let references = self.references_input()?;
let body = self.body_input()?;
match self.preview_and_confirm(commit_type, scope, description, breaking_change, body) {
match self.preview_and_confirm(
commit_type,
scope,
description,
breaking_change,
body,
references,
) {
Ok(conventional_commit) => {
self.executor
.describe(revset, &conventional_commit.to_string())
@@ -113,6 +121,11 @@ impl<J: JjExecutor, P: Prompter> CommitWorkflow<J, P> {
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
fn body_input(&self) -> Result<Body, Error> {
self.prompts.input_body()
@@ -129,6 +142,7 @@ impl<J: JjExecutor, P: Prompter> CommitWorkflow<J, P> {
description: Description,
breaking_change: BreakingChange,
body: Body,
references: References,
) -> Result<ConventionalCommit, Error> {
// Format the message for preview
let message = ConventionalCommit::format_preview(
@@ -137,6 +151,7 @@ impl<J: JjExecutor, P: Prompter> CommitWorkflow<J, P> {
&description,
&breaking_change,
&body,
&references,
);
// Try to build the conventional commit (this validates the 72-char limit)
@@ -146,6 +161,7 @@ impl<J: JjExecutor, P: Prompter> CommitWorkflow<J, P> {
description.clone(),
breaking_change,
body,
references,
) {
Ok(cc) => cc,
Err(CommitMessageError::FirstLineTooLong { actual, max }) => {
@@ -276,8 +292,15 @@ mod tests {
let description = Description::parse("test description").unwrap();
let breaking_change = BreakingChange::No;
let body = Body::default();
let result =
workflow.preview_and_confirm(commit_type, scope, description, breaking_change, body);
let references = References::default();
let result = workflow.preview_and_confirm(
commit_type,
scope,
description,
breaking_change,
body,
references,
);
assert!(result.is_ok());
}
@@ -322,6 +345,7 @@ mod tests {
.with_scope(Scope::empty())
.with_description(Description::parse("add new feature").unwrap())
.with_breaking_change(BreakingChange::Yes)
.with_references(References::default())
.with_body(Body::default())
.with_confirm(true);
@@ -355,6 +379,7 @@ mod tests {
.with_scope(Scope::parse("api").unwrap())
.with_description(Description::parse("fix bug").unwrap())
.with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::default())
.with_confirm(false); // User cancels at confirmation
@@ -379,11 +404,13 @@ mod tests {
.with_scope(Scope::parse("very-long-scope-name").unwrap())
.with_description(Description::parse("a".repeat(45)).unwrap())
.with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::default())
// Second iteration: short enough to succeed
.with_scope(Scope::empty())
.with_description(Description::parse("short description").unwrap())
.with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::default())
.with_confirm(true);
@@ -478,6 +505,7 @@ mod tests {
.with_scope(Scope::empty())
.with_description(Description::parse("test").unwrap())
.with_breaking_change(BreakingChange::Yes)
.with_references(References::default())
.with_body(Body::default())
.with_confirm(true);
@@ -501,6 +529,7 @@ mod tests {
.with_scope(Scope::empty())
.with_description(Description::parse("test").unwrap())
.with_breaking_change(BreakingChange::Yes)
.with_references(References::default())
.with_body(Body::default())
.with_confirm(true);
@@ -519,6 +548,7 @@ mod tests {
.with_scope(Scope::parse("api").unwrap())
.with_description(Description::parse("test").unwrap())
.with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::default())
.with_confirm(true);
@@ -566,6 +596,7 @@ mod tests {
Description::parse("remove old API").unwrap(),
BreakingChange::Yes,
Body::default(),
References::default(),
);
assert!(result.is_ok(), "expected Ok, got: {:?}", result);
@@ -593,6 +624,7 @@ mod tests {
Description::parse("drop legacy API").unwrap(),
breaking_change,
Body::default(),
References::default(),
);
assert!(result.is_ok(), "expected Ok, got: {:?}", result);
@@ -623,6 +655,7 @@ mod tests {
.with_scope(Scope::empty())
.with_description(Description::parse("remove old API").unwrap())
.with_breaking_change(BreakingChange::Yes)
.with_references(References::default())
.with_body(Body::default())
.with_confirm(true);
@@ -665,6 +698,7 @@ mod tests {
Description::parse("add feature").unwrap(),
BreakingChange::No,
Body::from("This explains the change."),
References::default(),
);
assert!(result.is_ok(), "expected Ok, got: {:?}", result);
@@ -692,6 +726,7 @@ mod tests {
Description::parse("drop legacy API").unwrap(),
"removes legacy endpoint".into(),
Body::from("The endpoint was deprecated in v2."),
References::default(),
);
assert!(result.is_ok(), "expected Ok, got: {:?}", result);
@@ -718,6 +753,7 @@ mod tests {
.with_scope(Scope::empty())
.with_description(Description::parse("add feature").unwrap())
.with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::from("This explains the change."))
.with_confirm(true);
@@ -750,6 +786,7 @@ mod tests {
.with_scope(Scope::empty())
.with_description(Description::parse("fix crash").unwrap())
.with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::default())
.with_confirm(true);
@@ -803,6 +840,7 @@ mod tests {
.with_scope(Scope::empty())
.with_description(Description::parse("add feature").unwrap())
.with_breaking_change(BreakingChange::No)
.with_references(References::default())
.with_body(Body::default())
.with_confirm(true);