Repository: orhun/git-cliff (rust)

Issue:
Git-cliff does not parse changes after breaking change (tag sha-4c3c946)
### Is there an existing issue for this?

- [x] I have searched the existing issues

### Description of the bug

When I run `git cliff` (v. 2.8.0) from Powershell against my repo, it correctly populates the changelog. If I use a Docker image with the tag `sha-4c3c946` or prior, it populates the changelog correctly. However, when I try to use the latest Docker image, or any image after tag `sha-4c3c946`, it creates an empty changelog with only a header and footer. 

I compiled the latest version of git-cliff (3 days prior to v2.9.0 release) and came up with the same results where it produces a nearly empty changelog.

It appears to be an issue related to [issue 1143](https://github.com/orhun/git-cliff/issues/1143), which resulted in [this commit](https://github.com/orhun/git-cliff/commit/e981e1d1b27a65fc2d2fd51b025c27692a6c8910).

### Steps To Reproduce

1. Copy down the [mta-solutions/fsharp-data-validation](https://github.com/[mta-solutions/fsharp-data-validation](https://github.com/mta-solutions/fsharp-data-validation)) GitHub Repo (this is just for illustrative purposes) to `D:/fsharp-data-validation/` (or the directory of your choice). 
2. Create a `cliff.toml` file in the root of the repo with the following contents:
   ```toml
   [remote.github]
   owner = "mta-solutions"
   repo = "fsharp-data-validation"
   
   [changelog]
   output = "CHANGELOG.md"
   
   header = """
   # Changelog
   
   """
   
   body = """
   {% if version %}\
       ## [{{ version | trim_start_matches(pat="v") }}](https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/tree/{{ version }}) ({{ timestamp | date(format="%Y-%m-%d") }})
   {% else %}\
       {% if commits | length > 0 %}\
           \n## [unreleased]
       {% endif %}\
   {% endif %}\
   
   {% for group, commits in commits | unique(attribute="id") | filter(attribute="merge_commit", value=true) | group_by(attribute="group") %}
       ### {{ group | upper_first }}
       {% for commit in commits %}
           - {{ commit.message | split(pat="\n") | nth(n=2) | trim_end }} \
           ([PR #{{ commit.remote.pr_number }}](https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/pull/{{ commit.remote.pr_number }})) \
           ([{{ commit.remote.username }}](https://github.com/{{ commit.remote.username }}))\
       {% endfor %}
   {% endfor %}\n
   """
   
   footer = """
   \n\n*This changelog was automatically generated by [git-cliff](https://github.com/orhun/git-cliff)*
   """
   
   trim = true
   
   [git]
   conventional_commits = false
   filter_unconventional = false
   split_commits = false
   commit_parsers = [
     { field = "remote.pr_labels", pattern = "duplicate|invalid|wontfix|skip changelog", skip = true },
     { field = "remote.pr_labels", pattern = "breaking change", group = "<!-- 0 -->🏗️ Breaking Changes" },
     { field = "remote.pr_labels", pattern = "feature|deprecation", group = "<!-- 1 -->🚀 Features" },
     { field = "remote.pr_labels", pattern = "enhancement|refactor", group = "<!-- 1 -->🛠️ Enhancements" },
     { field = "remote.pr_labels", pattern = "bug|regression", group = "<!-- 2 -->🐛 Bug Fixes" },
     { field = "remote.pr_labels", pattern = "security", group = "<!-- 3 -->🔐 Security" },
     { field = "remote.pr_labels", pattern = "documentation", group = "<!-- 4 -->📝 Documentation" },
     { field = "remote.pr_labels", pattern = ".*", group = "<!-- 5 -->🌀 Miscellaneous" },
   ]
   filter_commits = true
   sort_commits = "newest"
   ```
3. Run this command from a Powershell console (adjust the repo directory if necessary): `docker run -it --rm --env GITHUB_TOKEN -v D:/fsharp-data-validation:/app orhunp/git-cliff:latest`
4. View the `CHANGELOG.md`, which should be empty except for a single tag and the header and footer.

### Expected behavior

By following the same directions to reproduce, but replacing the command in step three with `docker run -it --rm --env GITHUB_TOKEN -v D:/fsharp-data-validation:/app orhunp/git-cliff:sha-4d3b2d5` (using `sha-4d3b2d5` any prior tag), the `CHANGELOG.md` file will be fully populated.

### Screenshots / Logs

I manually compiled the latest git-cliff v2.8.0 (commit 7148b2dbff518eb71c276f3c256db49970730863) and ran it with the `--verbose` option against the sample repo and this is what I received:

```
D:\git-cliff\target\release\git-cliff.exe --verbose
 INFO  git_cliff > A new version of git-cliff is available: v2.8.0 -> v2.9.0
 DEBUG git_cliff_core::changelog > Adding remote data...
 DEBUG git_cliff_core::changelog > You are using an experimental feature! Please report bugs at <https://git-cliff.org/issues>
▸▹▹▹▹ Retrieving data from GitHub... (mta-solutions/fsharp-data-validation)                                                                                                DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/commits?per_page=100&page=0
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/commits?per_page=100&page=1
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/commits?per_page=100&page=2
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/commits?per_page=100&page=3
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/commits?per_page=100&page=4
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/commits?per_page=100&page=5
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/commits?per_page=100&page=6
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/commits?per_page=100&page=7
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/commits?per_page=100&page=8
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/commits?per_page=100&page=9
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/pulls?per_page=100&page=0&state=closed
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/pulls?per_page=100&page=1&state=closed
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/pulls?per_page=100&page=2&state=closed
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/pulls?per_page=100&page=3&state=closed
 DEBUG git_cliff_core::remote    > Sending request to: https://api.github.com/repos/mta-solutions/fsharp-data-validation/pulls?per_page=100&page=4&state=closed
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG reqwest::connect          > starting new connection: https://api.github.com/
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
 DEBUG rustls::client::hs        > No cached session for DnsName("api.github.com")
 DEBUG rustls::client::hs        > Not resuming any session
▹▸▹▹▹ Retrieving data from GitHub... (mta-solutions/fsharp-data-validation)                                                                                                DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
 DEBUG rustls::client::hs        > Using ciphersuite TLS13_AES_128_GCM_SHA256
 DEBUG rustls::client::tls13     > Not resuming
 DEBUG rustls::client::tls13     > TLS1.3 encrypted extensions: [ServerNameAck, Protocols([ProtocolName(687474702f312e31)])]
 DEBUG rustls::client::hs        > ALPN protocol is Some(b"http/1.1")
▸▹▹▹▹ Retrieving data from GitHub... (mta-solutions/fsharp-data-validation)                                                                                                DEBUG git_cliff_core::remote    > Error while fetching page: PaginationError("end of entries")
▹▸▹▹▹ Retrieving data from GitHub... (mta-solutions/fsharp-data-validation)                                                                                                DEBUG git_cliff_core::remote    > Error while fetching page: PaginationError("end of entries")
 DEBUG git_cliff_core::changelog > Number of GitHub commits: 168
 DEBUG git_cliff_core::changelog > Number of GitHub pull requests: 54
 DEBUG git_cliff_core::changelog > Processing the commits...
 DEBUG git_cliff_core::changelog > Processing 8 release(s)...
 DEBUG git_cliff_core::changelog > Generating changelog...
```

I also ran it with the tagged v2.8.0 release version, which came up with the same results.

When I run git cliff with the context, the context for the working/older versions of git cliff produce a mile-long context. When I do the same with the compiled version, it produces a tiny context that includes barely anything.

### Software information

- Operating system: Windows 11 Pro
- Rust version: 1.85.0
- Project version: 2.8.0/latest


### Additional context

When I remove the `filter_commits` and `commit_parsers` options from the config, then it produces a large context and changelog, though things are obviously not categorized or anything correctly then. This makes me suspect something changed with the commit_parses.

I have also tried this with at least three different private repos where we have every kind of label and it is the same issue.

Pull request description:
feat(parser): support regex matching on JSON arrays with scalar elements
## Description

This PR extends the commit parser to support regex matching on JSON arrays, where each element is a scalar value (e.g., strings, numbers, booleans). When a commit field (like `remote.pr_labels`) is a JSON array, all entries are evaluated according to the specified `match_mode` (`any` or `all`). Fixes #1157

## Motivation and Context

Previously, only scalar JSON values (e.g., strings, numbers) were matched against regex patterns. This change extends support to JSON arrays, where each element is a scalar value. All values in the array are checked based on the configured match mode (`any`/`all`).

## How Has This Been Tested?

 1. Clone the [mta-solutions/fsharp-data-validation](https://github.com/mta-solutions/fsharp-data-validation) GitHub Repo.
 2. Create a cliff.toml file in the root of the repo with the following contents:

```toml
[remote.github]
owner = "mta-solutions"
repo = "fsharp-data-validation"

[changelog]
output = "CHANGELOG.md"

header = """
# Changelog

"""

body = """
{% if version %}\
    ## [{{ version | trim_start_matches(pat="v") }}](https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/tree/{{ version }}) ({{ timestamp | date(format="%Y-%m-%d") }})
{% else %}\
    {% if commits | length > 0 %}\
        \n## [unreleased]
    {% endif %}\
{% endif %}\

{% for group, commits in commits | unique(attribute="id") | filter(attribute="merge_commit", value=true) | group_by(attribute="group") %}
    ### {{ group | upper_first }}
    {% for commit in commits %}
        - {{ commit.message | split(pat="\n") | nth(n=2) | trim_end }} \
        ([PR #{{ commit.remote.pr_number }}](https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/pull/{{ commit.remote.pr_number }})) \
        ([{{ commit.remote.username }}](https://github.com/{{ commit.remote.username }}))\
    {% endfor %}
{% endfor %}\n
"""

footer = """
\n\n*This changelog was automatically generated by [git-cliff](https://github.com/orhun/git-cliff)*
"""

trim = true

[git]
conventional_commits = false
filter_unconventional = false
split_commits = false
commit_parsers = [
  { field = "remote.pr_labels", pattern = "duplicate|invalid|wontfix|skip changelog", skip = true },
  { field = "remote.pr_labels", pattern = "breaking change", group = "<!-- 0 -->🏗️ Breaking Changes" },
  { field = "remote.pr_labels", pattern = "feature|deprecation", group = "<!-- 1 -->🚀 Features" },
  { field = "remote.pr_labels", pattern = "enhancement|refactor", group = "<!-- 1 -->🛠️ Enhancements" },
  { field = "remote.pr_labels", pattern = "bug|regression", group = "<!-- 2 -->🐛 Bug Fixes" },
  { field = "remote.pr_labels", pattern = "security", group = "<!-- 3 -->🔐 Security" },
  { field = "remote.pr_labels", pattern = "documentation", group = "<!-- 4 -->📝 Documentation" },
  { field = "remote.pr_labels", pattern = ".*", group = "<!-- 5 -->🌀 Miscellaneous" },
]
filter_commits = true
sort_commits = "newest"
```

 3. Run this command from a console:
 
 ```bash
 path/to/my/local/git-cliff/repository/target/debug/git-cliff -v 
 ```
 
  4. View the `CHANGELOG.md`:
  
  ```md
  # Changelog

## [2.2.0](https://github.com/mta-solutions/fsharp-data-validation/tree/v2.2.0) (2024-12-13)

### <!-- 1 -->🛠️ Enhancements

- Add additional VCtx functions ([PR #24](https://github.com/mta-solutions/fsharp-data-validation/pull/24)) ([RJSonnenberg](https://github.com/RJSonnenberg))

## [2.1.0](https://github.com/mta-solutions/fsharp-data-validation/tree/v2.1.0) (2024-12-10)

### <!-- 1 -->🛠️ Enhancements

- Remove internal scoping and add Async extension methods for validation contexts ([PR #22](https://github.com/mta-solutions/fsharp-data-validation/pull/22)) ([RJSonnenberg](https://github.com/RJSonnenberg))

### <!-- 4 -->📝 Documentation

- Prep v2.1.0 Release ([PR #23](https://github.com/mta-solutions/fsharp-data-validation/pull/23)) ([RJSonnenberg](https://github.com/RJSonnenberg))

## [2.0.0](https://github.com/mta-solutions/fsharp-data-validation/tree/v2.0.0) (2024-10-08)

### <!-- 1 -->🛠️ Enhancements

- Use FSharpPlus ([PR #20](https://github.com/mta-solutions/fsharp-data-validation/pull/20)) ([devinlyons](https://github.com/devinlyons))

### <!-- 4 -->📝 Documentation

- Prep for Release v2.0.0 ([PR #21](https://github.com/mta-solutions/fsharp-data-validation/pull/21)) ([devinlyons](https://github.com/devinlyons))

## [1.0.1](https://github.com/mta-solutions/fsharp-data-validation/tree/v1.0.1) (2023-11-17)

### <!-- 1 -->🛠️ Enhancements

- Convert to NuGet ([PR #18](https://github.com/mta-solutions/fsharp-data-validation/pull/18)) ([devinlyons](https://github.com/devinlyons))
- Add Giraffe model validation library and examples ([PR #17](https://github.com/mta-solutions/fsharp-data-validation/pull/17)) ([RJSonnenberg](https://github.com/RJSonnenberg))

## [1.0.0](https://github.com/mta-solutions/fsharp-data-validation/tree/v1.0.0) (2022-03-14)

### <!-- 1 -->🛠️ Enhancements

- Add utility functions to help with using NonEmptyLists [MBI-53] ([PR #14](https://github.com/mta-solutions/fsharp-data-validation/pull/14)) ([mleone11](https://github.com/mleone11))



*This changelog was automatically generated by [git-cliff](https://github.com/orhun/git-cliff)*
  ```

## Screenshots / Logs (if applicable)

N/A

## Types of Changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation (no code change)
- [ ] Refactor (refactoring production code)
- [ ] Other <!--- (provide information) -->

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] My code follows the code style of this project.
- [x] I have updated the documentation accordingly.
- [x] I have formatted the code with [rustfmt](https://github.com/rust-lang/rustfmt).
- [x] I checked the lints with [clippy](https://github.com/rust-lang/rust-clippy).
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.

<!--- Thank you for contributing to git-cliff! ⛰️  -->


Patch:
diff --git a/examples/detailed.toml b/examples/detailed.toml
index 47bb551..e0bb2a8 100644
--- a/examples/detailed.toml
+++ b/examples/detailed.toml
@@ -26,9 +26,11 @@ body = """
     ### {{ group | upper_first }}
     {% for commit in commits %}
         - {{ commit.message | split(pat="\n") | first | upper_first | trim }} ([{{ commit.id | truncate(length=7, end="") }}]({{ commit.id }}))\
-          {% for footer in commit.footers -%}
-            , {{ footer.token }}{{ footer.separator }}{{ footer.value }}\
-          {% endfor %}\
+          {% if commit.footers %}\
+            {% for footer in commit.footers -%}
+                , {{ footer.token }}{{ footer.separator }}{{ footer.value }}\
+            {% endfor %}\
+          {% endif %}\
     {% endfor %}
 {% endfor %}\n
 """
diff --git a/git-cliff-core/src/commit.rs b/git-cliff-core/src/commit.rs
index 548717f..1c96ebc 100644
--- a/git-cliff-core/src/commit.rs
+++ b/git-cliff-core/src/commit.rs
@@ -332,27 +332,47 @@ impl Commit<'_> {
 			if let (Some(field_name), Some(pattern_regex)) =
 				(parser.field.as_ref(), parser.pattern.as_ref())
 			{
-				let value = if field_name == "body" {
-					body.clone()
+				let values = if field_name == "body" {
+					vec![body.clone()].into_iter().collect()
 				} else {
 					tera::dotted_pointer(&lookup_context, field_name).and_then(|v| {
-						match &v {
-							Value::String(s) => Some(s.clone()),
+						match v {
+							Value::String(s) => Some(vec![s.clone()]),
 							Value::Number(_) | Value::Bool(_) | Value::Null => {
-								Some(v.to_string())
+								Some(vec![v.to_string()])
+							}
+							Value::Array(arr) => {
+								let mut values = Vec::new();
+								for item in arr {
+									match item {
+										Value::String(s) => values.push(s.clone()),
+										Value::Number(_) |
+										Value::Bool(_) |
+										Value::Null => values.push(item.to_string()),
+										_ => continue,
+									}
+								}
+								Some(values)
 							}
 							_ => None,
 						}
 					})
 				};
-				match value {
-					Some(value) => {
-						regex_checks.push((pattern_regex, value));
+				match values {
+					Some(values) => {
+						if values.is_empty() {
+							trace!("field '{field_name}' is present but empty");
+						} else {
+							for value in values {
+								regex_checks.push((pattern_regex, value));
+							}
+						}
 					}
 					None => {
 						return Err(AppError::FieldError(format!(
 							"field '{field_name}' is missing or has unsupported \
-							 type (expected String, Number, Bool, or Null)",
+							 type (expected a String, Number, Bool, or Null — or \
+							 an Array of these scalar values)",
 						)));
 					}
 				}
@@ -727,11 +747,83 @@ mod test {
 		);
 	}
 
+	#[test]
+	fn parse_body() -> Result<()> {
+		let mut commit = Commit::new(
+			String::from("8f55e69eba6e6ce811ace32bd84cc82215673cb6"),
+			String::from(
+				"fix: do something
+
+Introduce something great
+
+BREAKING CHANGE: drop support for something else
+Refs: #123
+",
+			),
+		);
+		commit.author = Signature {
+			name:      Some("John Doe".to_string()),
+			email:     None,
+			timestamp: 0x0,
+		};
+		commit.remote = Some(crate::contributor::RemoteContributor {
+			username:      None,
+			pr_title:      Some("feat: do something".to_string()),
+			pr_number:     None,
+			pr_labels:     vec![
+				String::from("feature"),
+				String::from("deprecation"),
+			],
+			is_first_time: true,
+		});
+		let commit = commit.into_conventional()?;
+		let commit = commit.parse_links(&[
+			LinkParser {
+				pattern: Regex::new("RFC(\\d+)")?,
+				href:    String::from("rfc://$1"),
+				text:    None,
+			},
+			LinkParser {
+				pattern: Regex::new("#(\\d+)")?,
+				href:    String::from("https://github.com/$1"),
+				text:    None,
+			},
+		])?;
+
+		let parsed_commit = commit.clone().parse(
+			&[CommitParser {
+				sha:           None,
+				message:       None,
+				body:          Regex::new("something great").ok(),
+				footer:        None,
+				group:         Some(String::from("Test group")),
+				default_scope: None,
+				scope:         None,
+				skip:          None,
+				field:         None,
+				pattern:       None,
+			}],
+			false,
+			false,
+		)?;
+		assert_eq!(Some(String::from("Test group")), parsed_commit.group);
+
+		Ok(())
+	}
+
 	#[test]
 	fn parse_commit_field() -> Result<()> {
 		let mut commit = Commit::new(
 			String::from("8f55e69eba6e6ce811ace32bd84cc82215673cb6"),
-			String::from("feat: do something"),
+			String::from(
+				"fix: do something
+
+Introduce something great
+
+BREAKING CHANGE: drop support for something else
+Refs: #123
+",
+			),
 		);
 		commit.author = Signature {
 			name:      Some("John Doe".to_string()),
@@ -742,9 +834,25 @@ mod test {
 			username:      None,
 			pr_title:      Some("feat: do something".to_string()),
 			pr_number:     None,
-			pr_labels:     Vec::new(),
+			pr_labels:     vec![
+				String::from("feature"),
+				String::from("deprecation"),
+			],
 			is_first_time: true,
 		});
+		let commit = commit.into_conventional()?;
+		let commit = commit.parse_links(&[
+			LinkParser {
+				pattern: Regex::new("RFC(\\d+)")?,
+				href:    String::from("rfc://$1"),
+				text:    None,
+			},
+			LinkParser {
+				pattern: Regex::new("#(\\d+)")?,
+				href:    String::from("https://github.com/$1"),
+				text:    None,
+			},
+		])?;
 
 		let parsed_commit = commit.clone().parse(
 			&[CommitParser {
@@ -782,7 +890,25 @@ mod test {
 		)?;
 		assert_eq!(Some(String::from("Test group")), parsed_commit.group);
 
-		let parse_result = commit.clone().parse(
+		let parsed_commit = commit.clone().parse(
+			&[CommitParser {
+				sha:           None,
+				message:       None,
+				body:          None,
+				footer:        None,
+				group:         Some(String::from("Test group")),
+				default_scope: None,
+				scope:         None,
+				skip:          None,
+				field:         Some(String::from("body")),
+				pattern:       Regex::new("something great").ok(),
+			}],
+			false,
+			false,
+		)?;
+		assert_eq!(Some(String::from("Test group")), parsed_commit.group);
+
+		let parsed_commit = commit.clone().parse(
 			&[CommitParser {
 				sha:           None,
 				message:       None,
@@ -793,6 +919,42 @@ mod test {
 				scope:         None,
 				skip:          None,
 				field:         Some(String::from("remote.pr_labels")),
+				pattern:       Regex::new("feature|deprecation").ok(),
+			}],
+			false,
+			false,
+		)?;
+		assert_eq!(Some(String::from("Test group")), parsed_commit.group);
+
+		let parsed_commit = commit.clone().parse(
+			&[CommitParser {
+				sha:           None,
+				message:       None,
+				body:          None,
+				footer:        None,
+				group:         Some(String::from("Test group")),
+				default_scope: None,
+				scope:         None,
+				skip:          None,
+				field:         Some(String::from("links")),
+				pattern:       Regex::new(".*").ok(),
+			}],
+			false,
+			false,
+		)?;
+		assert_eq!(None, parsed_commit.group);
+
+		let parse_result = commit.clone().parse(
+			&[CommitParser {
+				sha:           None,
+				message:       None,
+				body:          None,
+				footer:        None,
+				group:         Some(String::from("Test group")),
+				default_scope: None,
+				scope:         None,
+				skip:          None,
+				field:         Some(String::from("remote")),
 				pattern:       Regex::new(".*").ok(),
 			}],
 			false,
@@ -800,8 +962,7 @@ mod test {
 		);
 		assert!(
 			parse_result.is_err(),
-			"Expected error when using unsupported field `remote.pr_labels`, but \
-			 got Ok"
+			"Expected error when using unsupported field `remote`, but got Ok"
 		);
 
 		Ok(())
diff --git a/website/docs/integration/bitbucket.md b/website/docs/integration/bitbucket.md
index d8ddd8d..2cc532d 100644
--- a/website/docs/integration/bitbucket.md
+++ b/website/docs/integration/bitbucket.md
@@ -96,7 +96,7 @@ https://bitbucket.org/{{ remote.bitbucket.owner }}/{{ remote.bitbucket.repo }}/c
 
 ### Commit authors
 
-For each commit, Bitbucket related values are added as a nested object (named `bitbucket`) to the [template context](/docs/templating/context):
+For each commit, Bitbucket related values are added as a nested object (named `remote`) to the [template context](/docs/templating/context):
 
 ```json
 {
diff --git a/website/docs/integration/gitea.md b/website/docs/integration/gitea.md
index 6bad02f..10e09a3 100644
--- a/website/docs/integration/gitea.md
+++ b/website/docs/integration/gitea.md
@@ -96,7 +96,7 @@ https://codeberg.org/{{ remote.gitea.owner }}/{{ remote.gitea.repo }}/commits/ta
 
 ### Commit authors
 
-For each commit, Gitea related values are added as a nested object (named `gitea`) to the [template context](/docs/templating/context):
+For each commit, Gitea related values are added as a nested object (named `remote`) to the [template context](/docs/templating/context):
 
 ```json
 {
diff --git a/website/docs/integration/github.md b/website/docs/integration/github.md
index ffab28a..899f333 100644
--- a/website/docs/integration/github.md
+++ b/website/docs/integration/github.md
@@ -100,7 +100,7 @@ https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/compare/{{
 
 ### Commit authors
 
-For each commit, GitHub related values are added as a nested object (named `github`) to the [template context](/docs/templating/context):
+For each commit, GitHub related values are added as a nested object (named `remote`) to the [template context](/docs/templating/context):
 
 ```json
 {
diff --git a/website/docs/integration/gitlab.md b/website/docs/integration/gitlab.md
index fa43695..3d5c3f1 100644
--- a/website/docs/integration/gitlab.md
+++ b/website/docs/integration/gitlab.md
@@ -108,7 +108,7 @@ If you are using GitLab CI, you can use [`CI_PROJECT_URL`](https://docs.gitlab.c
 
 ### Commit authors
 
-For each commit, GitLab related values are added as a nested object (named `gitlab`) to the [template context](/docs/templating/context):
+For each commit, GitLab related values are added as a nested object (named `remote`) to the [template context](/docs/templating/context):
 
 ```json
 {


Test patch:
diff --git a/.github/fixtures/test-commit-footers/cliff.toml b/.github/fixtures/test-commit-footers/cliff.toml
index 6ad408b..d7cfbda 100644
--- a/.github/fixtures/test-commit-footers/cliff.toml
+++ b/.github/fixtures/test-commit-footers/cliff.toml
@@ -14,9 +14,11 @@ body = """
     ### {{ group | upper_first }}
     {% for commit in commits %}
         - {{ commit.message | upper_first }}\
-          {% for footer in commit.footers -%}
-            , {{ footer.token }}{{ footer.separator }}{{ footer.value }}\
-          {% endfor %}\
+          {% if commit.footers %}\
+            {% for footer in commit.footers -%}
+                , {{ footer.token }}{{ footer.separator }}{{ footer.value }}\
+            {% endfor %}\
+          {% endif %}\
     {% endfor %}
 {% endfor %}\n
 """
diff --git a/.github/fixtures/test-regex-json-array/cliff.toml b/.github/fixtures/test-regex-json-array/cliff.toml
new file mode 100644
index 00000000..4a77040
--- /dev/null
+++ b/.github/fixtures/test-regex-json-array/cliff.toml
@@ -0,0 +1,51 @@
+# git-cliff ~ configuration file
+# https://git-cliff.org/docs/configuration
+
+[remote.github]
+owner = "mta-solutions"
+repo = "fsharp-data-validation"
+
+[changelog]
+# A Tera template to be rendered for each release in the changelog.
+# See https://keats.github.io/tera/docs/#introduction
+body = """
+{% if version %}\
+    ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
+{% else %}\
+    ## [unreleased]
+{% endif %}\
+{% for group, commits in commits | unique(attribute="id") | filter(attribute="merge_commit", value=true) | group_by(attribute="group") %}
+    ### {{ group | upper_first }}
+    {% for commit in commits %}
+        - {{ commit.message | split(pat="\n") | nth(n=2) | trim_end }} \
+        ([PR #{{ commit.remote.pr_number }}](https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/pull/{{ commit.remote.pr_number }}))\
+    {% endfor %}
+{% endfor %}\n
+"""
+
+[git]
+# Parse commits according to the conventional commits specification.
+# See https://www.conventionalcommits.org
+conventional_commits = false
+# Exclude commits that do not match the conventional commits specification.
+filter_unconventional = false
+# Split commits on newlines, treating each line as an individual commit.
+split_commits = false
+# An array of regex based parsers for extracting data from the commit message.
+# Assigns commits to groups.
+# Optionally sets the commit's `scope` and can decide to exclude commits from further processing.
+commit_parsers = [
+    { field = "remote.pr_labels", pattern = "duplicate|invalid|wontfix|skip changelog", skip = true },
+    { field = "remote.pr_labels", pattern = "breaking change", group = "<!-- 0 -->🏗️ Breaking Changes" },
+    { field = "remote.pr_labels", pattern = "feature|deprecation", group = "<!-- 1 -->🚀 Features" },
+    { field = "remote.pr_labels", pattern = "enhancement|refactor", group = "<!-- 1 -->🛠️ Enhancements" },
+    { field = "remote.pr_labels", pattern = "bug|regression", group = "<!-- 2 -->🐛 Bug Fixes" },
+    { field = "remote.pr_labels", pattern = "security", group = "<!-- 3 -->🔐 Security" },
+    { field = "remote.pr_labels", pattern = "documentation", group = "<!-- 4 -->📝 Documentation" },
+    { message = ".*", group = "<!-- 5 -->🌀 Miscellaneous" },
+]
+# Exclude commits that are not matched by any commit parser.
+filter_commits = true
+# Order of commits in each group/release within the changelog.
+# Allowed values: newest, oldest
+sort_commits = "newest"
diff --git a/.github/fixtures/test-regex-json-array/commit.sh b/.github/fixtures/test-regex-json-array/commit.sh
new file mode 100755
index 00000000..fb9ec01
--- /dev/null
+++ b/.github/fixtures/test-regex-json-array/commit.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+set -e
+
+git remote add origin https://github.com/mta-solutions/fsharp-data-validation
+git pull origin main
+git fetch --tags
+git checkout 9201e2729ad3afb34171c493c2cb9984e9d64784
diff --git a/.github/fixtures/test-regex-json-array/expected.md b/.github/fixtures/test-regex-json-array/expected.md
new file mode 100644
index 00000000..09c34e4
--- /dev/null
+++ b/.github/fixtures/test-regex-json-array/expected.md
@@ -0,0 +1,78 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+## [3.0.0] - 2025-01-25
+
+### <!-- 5 -->🌀 Miscellaneous
+
+- Drop Support for .NET 6.0 ([PR #27](https://github.com/mta-solutions/fsharp-data-validation/pull/27))
+
+## [2.2.0] - 2024-12-13
+
+### <!-- 1 -->🛠️ Enhancements
+
+- Add additional VCtx functions ([PR #24](https://github.com/mta-solutions/fsharp-data-validation/pull/24))
+
+### <!-- 5 -->🌀 Miscellaneous
+
+- Prep FSharp.Data.Validation.Giraffe v2.0.1 Release ([PR #26](https://github.com/mta-solutions/fsharp-data-validation/pull/26))
+- Update CHANGELOG for version 2.2.0 ([PR #25](https://github.com/mta-solutions/fsharp-data-validation/pull/25))
+
+## [2.1.0] - 2024-12-10
+
+### <!-- 1 -->🛠️ Enhancements
+
+- Remove internal scoping and add Async extension methods for validation contexts ([PR #22](https://github.com/mta-solutions/fsharp-data-validation/pull/22))
+
+### <!-- 4 -->📝 Documentation
+
+- Prep v2.1.0 Release ([PR #23](https://github.com/mta-solutions/fsharp-data-validation/pull/23))
+
+## [2.0.0] - 2024-10-08
+
+### <!-- 1 -->🛠️ Enhancements
+
+- Use FSharpPlus ([PR #20](https://github.com/mta-solutions/fsharp-data-validation/pull/20))
+
+### <!-- 4 -->📝 Documentation
+
+- Prep for Release v2.0.0 ([PR #21](https://github.com/mta-solutions/fsharp-data-validation/pull/21))
+
+## [giraffe-v1.0.0] - 2023-11-17
+
+### <!-- 5 -->🌀 Miscellaneous
+
+- Fix Giraffe Pipeline ([PR #19](https://github.com/mta-solutions/fsharp-data-validation/pull/19))
+
+## [1.0.1] - 2023-11-17
+
+### <!-- 1 -->🛠️ Enhancements
+
+- Convert to NuGet ([PR #18](https://github.com/mta-solutions/fsharp-data-validation/pull/18))
+- Add Giraffe model validation library and examples ([PR #17](https://github.com/mta-solutions/fsharp-data-validation/pull/17))
+
+## [1.0.0] - 2022-03-14
+
+### <!-- 1 -->🛠️ Enhancements
+
+- Add utility functions to help with using NonEmptyLists [MBI-53] ([PR #14](https://github.com/mta-solutions/fsharp-data-validation/pull/14))
+
+### <!-- 5 -->🌀 Miscellaneous
+
+- [MBI-48]: Wire Up Release Actions ([PR #15](https://github.com/mta-solutions/fsharp-data-validation/pull/15))
+- Add tests for DisputeWith, DisputeWithFact, RefuteWith, and RefuteWithProof [MBI-46] ([PR #13](https://github.com/mta-solutions/fsharp-data-validation/pull/13))
+- [MBI-47]: Add Documentation and NonEmptyList Type ([PR #12](https://github.com/mta-solutions/fsharp-data-validation/pull/12))
+- Add Documentation ([PR #11](https://github.com/mta-solutions/fsharp-data-validation/pull/11))
+- Rename Solution File ([PR #10](https://github.com/mta-solutions/fsharp-data-validation/pull/10))
+- Fixed random test failures ([PR #9](https://github.com/mta-solutions/fsharp-data-validation/pull/9))
+- [MBI-33] Added tests for the most interesting parts of VCtxBuilder ([PR #8](https://github.com/mta-solutions/fsharp-data-validation/pull/8))
+- [MBI-33] Renamed root namespace to FSharp.Data.Validation ([PR #7](https://github.com/mta-solutions/fsharp-data-validation/pull/7))
+- [MBI-33] Finished Proof and Library test coverage ([PR #6](https://github.com/mta-solutions/fsharp-data-validation/pull/6))
+- Add raiseIfInvalid Function ([PR #5](https://github.com/mta-solutions/fsharp-data-validation/pull/5))
+- [MBI-33] Broke out samples/src/tests and expanded unit testing ([PR #4](https://github.com/mta-solutions/fsharp-data-validation/pull/4))
+- [MBI-33]: Add Custom Serializer for Proof Type ([PR #3](https://github.com/mta-solutions/fsharp-data-validation/pull/3))
+- [MBI-33] Restructured modules for Proof, ValueCtx, VCtx ([PR #2](https://github.com/mta-solutions/fsharp-data-validation/pull/2))
+- [MBI-33] Initial unit tests ([PR #1](https://github.com/mta-solutions/fsharp-data-validation/pull/1))
+
+<!-- generated by git-cliff -->
diff --git a/.github/workflows/test-fixtures.yml b/.github/workflows/test-fixtures.yml
index c1625e0..c6cd80f 100644
--- a/.github/workflows/test-fixtures.yml
+++ b/.github/workflows/test-fixtures.yml
@@ -129,6 +129,7 @@ jobs:
           - fixtures-name: test-commit-range-with-given-range
             command: a140cef^..a9d4050 --ignore-tags "."
           - fixtures-name: test-override-scope
+          - fixtures-name: test-regex-json-array
 
     steps:
       - name: Checkout