Commit f318a56d1e1 for woocommerce
commit f318a56d1e1320a20ab42bce489ddff8025a3326
Author: Rostislav Wolný <1082140+costasovo@users.noreply.github.com>
Date: Wed Jul 29 11:59:28 2026 +0200
Fix changelogger destroying major entries on package release (#67059)
* Fix changelogger destroying major entries and truncating text
The changelogger rewrites a package's entire CHANGELOG.md on every release,
so parse() must be able to read back what format() writes. It could not, in
two ways.
format() appends "[ **BREAKING CHANGE** ]" after a major significance, but
parse() derived the significance from everything before the first " - ",
so the marker was included, failed the patch/minor/major check, and the
significance became null. The next release then re-emitted the entry as
"- - text": the breaking-change annotation was gone from released
history and the 5-space indent broke markdownlint (MD007). It also emitted
a PHP 8.2 ucfirst(null) deprecation. This already corrupted two 2.0.0
entries in @woocommerce/email-editor.
Separately, parse() kept only $row_segments[1], so any entry whose text
contains " - " lost everything after its own separator. Three entries in
the data, currency and onboarding packages are currently set up to be
truncated on their next release.
The marker is now a shared constant, since the bug was really that the
writer and reader each carried their own copy of it. Running the real
email-editor changelog through parse()/format() alters 0 of 106 entries
after this change, against 2 before it.
* Strip the changelog bullet as a literal prefix
The bullet was interpolated into a regex, but it is a configurable property:
Legacy_Core_Formatter sets it to "* ", which builds the invalid pattern "/* /".
preg_replace then returns null rather than a string, so every row it touched
was discarded and that formatter could not parse a changelog at all.
The match was also unanchored, so on a line that did not begin with a bullet
the first occurrence was removed from the middle of the entry text.
Comparing a literal prefix addresses both and needs no escaping.
diff --git a/tools/changelogger/class-formatter.php b/tools/changelogger/class-formatter.php
index 230f75f8b5b..a707129140b 100644
--- a/tools/changelogger/class-formatter.php
+++ b/tools/changelogger/class-formatter.php
@@ -26,6 +26,14 @@ use Automattic\Jetpack\Changelogger\PluginTrait;
class Formatter extends KeepAChangelogParser {
use PluginTrait;
+ /**
+ * Marker appended after the significance of a major change. Written by format() and stripped by
+ * parse(), so both sides must share this definition for a changelog to survive a round trip.
+ *
+ * @var string
+ */
+ const BREAKING_CHANGE_MARKER = '[ **BREAKING CHANGE** ]';
+
/**
* Bullet for changes.
*
@@ -178,9 +186,19 @@ class Formatter extends KeepAChangelogParser {
$changes = array();
$rows = explode( "\n", $content );
foreach ( $rows as $row ) {
- $row = trim( $row );
- $row = preg_replace( '/' . $this->bullet . '/', '', $row, 1 );
- $row_segments = explode( ' - ', $row );
+ $row = trim( $row );
+ // Strip the bullet only as a literal prefix. It is configurable ('* ' in the legacy core
+ // formatter), so it is not safe to interpolate into a pattern, and removing a later
+ // occurrence would corrupt the entry text.
+ if ( 0 === strpos( $row, $this->bullet ) ) {
+ $row = substr( $row, strlen( $this->bullet ) );
+ }
+ // Drop the marker that format() appends after a major significance, otherwise it lands in the
+ // significance segment below and the entry is re-emitted with no significance at all.
+ $row = preg_replace( '/^(major)\s*' . preg_quote( self::BREAKING_CHANGE_MARKER, '/' ) . '/i', '$1', $row );
+
+ // Limit the split so that content containing " - " is not truncated at its own separator.
+ $row_segments = explode( ' - ', $row, 2 );
$significance = trim( strtolower( $row_segments[0] ) );
array_push(
@@ -253,7 +271,7 @@ class Formatter extends KeepAChangelogParser {
foreach ( $entry->getChangesBySubheading() as $heading => $changes ) {
foreach ( $changes as $change ) {
$significance = $change->getSignificance();
- $breaking_change = 'major' === $significance ? ' [ **BREAKING CHANGE** ]' : '';
+ $breaking_change = 'major' === $significance ? ' ' . self::BREAKING_CHANGE_MARKER : '';
$text = trim( $change->getContent() );
if ( '' !== $text ) {
$preamble = $is_subentry ? '' : $bullet . ucfirst( $significance ) . $breaking_change . ' - ';