From f919b1249bbc28f112bc6327c27b782050d3b361 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 12 Jan 2026 12:44:23 +0000
Subject: [PATCH 01/12] Initial plan
From 8f2b422088b37344588325b0664a2a5111a17069 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 12 Jan 2026 12:50:51 +0000
Subject: [PATCH 02/12] Add Post_Revision_Command with restore and diff
subcommands
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
---
composer.json | 3 +
entity-command.php | 1 +
features/post-revision.feature | 108 ++++++++++++++++++++++
phpcs.xml.dist | 2 +-
src/Post_Revision_Command.php | 159 +++++++++++++++++++++++++++++++++
5 files changed, 272 insertions(+), 1 deletion(-)
create mode 100644 features/post-revision.feature
create mode 100644 src/Post_Revision_Command.php
diff --git a/composer.json b/composer.json
index 0c743040..caed252e 100644
--- a/composer.json
+++ b/composer.json
@@ -114,6 +114,9 @@
"post meta patch",
"post meta pluck",
"post meta update",
+ "post revision",
+ "post revision diff",
+ "post revision restore",
"post term",
"post term add",
"post term list",
diff --git a/entity-command.php b/entity-command.php
index 0cecf202..cdad9235 100644
--- a/entity-command.php
+++ b/entity-command.php
@@ -46,6 +46,7 @@
)
);
WP_CLI::add_command( 'post meta', 'Post_Meta_Command' );
+WP_CLI::add_command( 'post revision', 'Post_Revision_Command' );
WP_CLI::add_command( 'post term', 'Post_Term_Command' );
WP_CLI::add_command( 'post-type', 'Post_Type_Command' );
WP_CLI::add_command( 'site', 'Site_Command' );
diff --git a/features/post-revision.feature b/features/post-revision.feature
new file mode 100644
index 00000000..c827e126
--- /dev/null
+++ b/features/post-revision.feature
@@ -0,0 +1,108 @@
+Feature: Manage WordPress post revisions
+
+ Background:
+ Given a WP install
+
+ Scenario: Restore a post revision
+ When I run `wp post create --post_title='Original Post' --post_content='Original content' --porcelain`
+ Then STDOUT should be a number
+ And save STDOUT as {POST_ID}
+
+ When I run `wp post update {POST_ID} --post_content='Updated content'`
+ Then STDOUT should contain:
+ """
+ Success: Updated post {POST_ID}.
+ """
+
+ When I run `wp post list --post_type=revision --post_parent={POST_ID} --fields=ID,post_title --format=ids`
+ Then STDOUT should not be empty
+ And save STDOUT as {REVISION_ID}
+
+ When I run `wp post revision restore {REVISION_ID}`
+ Then STDOUT should contain:
+ """
+ Success: Restored revision
+ """
+
+ When I run `wp post get {POST_ID} --field=post_content`
+ Then STDOUT should contain:
+ """
+ Original content
+ """
+
+ Scenario: Restore invalid revision should fail
+ When I try `wp post revision restore 99999`
+ Then STDERR should contain:
+ """
+ Error: Invalid revision ID
+ """
+ And the return code should be 1
+
+ Scenario: Show diff between two revisions
+ When I run `wp post create --post_title='Test Post' --post_content='First version' --porcelain`
+ Then STDOUT should be a number
+ And save STDOUT as {POST_ID}
+
+ When I run `wp post update {POST_ID} --post_content='Second version'`
+ Then STDOUT should contain:
+ """
+ Success: Updated post {POST_ID}.
+ """
+
+ When I run `wp post list --post_type=revision --post_parent={POST_ID} --fields=ID --format=csv --orderby=ID --order=ASC`
+ Then STDOUT should not be empty
+
+ When I run `wp post list --post_type=revision --post_parent={POST_ID} --format=ids --orderby=ID --order=ASC`
+ Then STDOUT should not be empty
+
+ Scenario: Show diff between revision and current post
+ When I run `wp post create --post_title='Diff Test' --post_content='Original text' --porcelain`
+ Then STDOUT should be a number
+ And save STDOUT as {POST_ID}
+
+ When I run `wp post update {POST_ID} --post_content='Modified text'`
+ Then STDOUT should contain:
+ """
+ Success: Updated post {POST_ID}.
+ """
+
+ When I run `wp post list --post_type=revision --post_parent={POST_ID} --fields=ID --format=ids --orderby=ID --order=ASC`
+ Then STDOUT should not be empty
+ And save STDOUT as {REVISION_ID}
+
+ When I run `wp post revision diff {REVISION_ID}`
+ Then the return code should be 0
+
+ Scenario: Diff with invalid revision should fail
+ When I try `wp post revision diff 99999`
+ Then STDERR should contain:
+ """
+ Error: Invalid 'from' ID
+ """
+ And the return code should be 1
+
+ Scenario: Diff between two invalid revisions should fail
+ When I try `wp post revision diff 99998 99999`
+ Then STDERR should contain:
+ """
+ Error: Invalid 'from' ID
+ """
+ And the return code should be 1
+
+ Scenario: Diff with specific field
+ When I run `wp post create --post_title='Field Test' --post_content='Some content' --porcelain`
+ Then STDOUT should be a number
+ And save STDOUT as {POST_ID}
+
+ When I run `wp post update {POST_ID} --post_title='Modified Field Test'`
+ Then STDOUT should contain:
+ """
+ Success: Updated post {POST_ID}.
+ """
+
+ When I run `wp post list --post_type=revision --post_parent={POST_ID} --fields=ID --format=ids --orderby=ID --order=ASC`
+ Then STDOUT should not be empty
+ And save STDOUT as {REVISION_ID}
+
+ When I run `wp post revision diff {REVISION_ID} --field=post_title`
+ Then the return code should be 0
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 0df76141..6ab1a682 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -65,7 +65,7 @@
*/src/Network_Meta_Command\.php$
*/src/Network_Namespace\.php$
*/src/Option_Command\.php$
- */src/Post(_Block|_Meta|_Term|_Type)?_Command\.php$
+ */src/Post(_Block|_Meta|_Revision|_Term|_Type)?_Command\.php$
*/src/Signup_Command\.php$
*/src/Site(_Meta|_Option)?_Command\.php$
*/src/Term(_Meta)?_Command\.php$
diff --git a/src/Post_Revision_Command.php b/src/Post_Revision_Command.php
new file mode 100644
index 00000000..f3ca23a7
--- /dev/null
+++ b/src/Post_Revision_Command.php
@@ -0,0 +1,159 @@
+fetcher = new PostFetcher();
+ }
+
+ /**
+ * Restores a post revision.
+ *
+ * ## OPTIONS
+ *
+ *
+ * : The revision ID to restore.
+ *
+ * ## EXAMPLES
+ *
+ * # Restore a post revision
+ * $ wp post revision restore 123
+ * Success: Restored revision 123.
+ *
+ * @subcommand restore
+ */
+ public function restore( $args ) {
+ $revision_id = (int) $args[0];
+
+ // Get the revision post
+ $revision = wp_get_post_revision( $revision_id );
+
+ if ( ! $revision ) {
+ WP_CLI::error( "Invalid revision ID {$revision_id}." );
+ }
+
+ // Restore the revision
+ $restored_post_id = wp_restore_post_revision( $revision_id );
+
+ if ( false === $restored_post_id || null === $restored_post_id ) {
+ WP_CLI::error( "Failed to restore revision {$revision_id}." );
+ }
+
+ WP_CLI::success( "Restored revision {$revision_id}." );
+ }
+
+ /**
+ * Shows the difference between two revisions.
+ *
+ * ## OPTIONS
+ *
+ *
+ * : The 'from' revision ID or post ID.
+ *
+ * []
+ * : The 'to' revision ID. If not provided, compares with the current post.
+ *
+ * [--field=]
+ * : Compare specific field(s). Default: post_content
+ *
+ * ## EXAMPLES
+ *
+ * # Show diff between two revisions
+ * $ wp post revision diff 123 456
+ *
+ * # Show diff between a revision and the current post
+ * $ wp post revision diff 123
+ *
+ * @subcommand diff
+ */
+ public function diff( $args, $assoc_args ) {
+ $from_id = (int) $args[0];
+ $to_id = isset( $args[1] ) ? (int) $args[1] : null;
+ $field = Utils\get_flag_value( $assoc_args, 'field', 'post_content' );
+
+ // Get the 'from' revision or post
+ $from_revision = wp_get_post_revision( $from_id );
+ if ( ! $from_revision instanceof \WP_Post ) {
+ // Try as a regular post
+ $from_revision = get_post( $from_id );
+ if ( ! $from_revision instanceof \WP_Post ) {
+ WP_CLI::error( "Invalid 'from' ID {$from_id}." );
+ }
+ }
+
+ // Get the 'to' revision or post
+ $to_revision = null;
+ if ( $to_id ) {
+ $to_revision = wp_get_post_revision( $to_id );
+ if ( ! $to_revision instanceof \WP_Post ) {
+ // Try as a regular post
+ $to_revision = get_post( $to_id );
+ if ( ! $to_revision instanceof \WP_Post ) {
+ WP_CLI::error( "Invalid 'to' ID {$to_id}." );
+ }
+ }
+ } elseif ( 'revision' === $from_revision->post_type ) {
+ // If no 'to' ID provided, use the parent post of the revision
+ $to_revision = get_post( $from_revision->post_parent );
+ if ( ! $to_revision instanceof \WP_Post ) {
+ WP_CLI::error( "Could not find parent post for revision {$from_id}." );
+ }
+ } else {
+ WP_CLI::error( "Please provide a 'to' revision ID when comparing posts." );
+ }
+
+ // Validate field
+ if ( ! property_exists( $from_revision, $field ) || ! property_exists( $to_revision, $field ) ) {
+ WP_CLI::error( "Invalid field '{$field}'." );
+ }
+
+ // Get the field values
+ $left_string = $from_revision->{$field};
+ $right_string = $to_revision->{$field};
+
+ // Generate the diff
+ $diff_args = [
+ 'title_left' => sprintf(
+ '%s (%s) - ID %d',
+ $from_revision->post_title,
+ $from_revision->post_modified,
+ $from_revision->ID
+ ),
+ 'title_right' => sprintf(
+ '%s (%s) - ID %d',
+ $to_revision->post_title,
+ $to_revision->post_modified,
+ $to_revision->ID
+ ),
+ ];
+
+ $diff = wp_text_diff( $left_string, $right_string, $diff_args );
+
+ if ( ! $diff ) {
+ WP_CLI::success( 'No difference found.' );
+ return;
+ }
+
+ // Output the diff
+ WP_CLI::line( $diff );
+ }
+}
From 98d85906af44c31fe7ee1b4bce5df029ac8cc4d4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 12 Jan 2026 12:53:56 +0000
Subject: [PATCH 03/12] Address code review feedback: validate fields properly
and remove unused property
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
---
src/Post_Revision_Command.php | 41 +++++++++++++++++++++++++++--------
1 file changed, 32 insertions(+), 9 deletions(-)
diff --git a/src/Post_Revision_Command.php b/src/Post_Revision_Command.php
index f3ca23a7..5d42de75 100644
--- a/src/Post_Revision_Command.php
+++ b/src/Post_Revision_Command.php
@@ -1,6 +1,5 @@
fetcher = new PostFetcher();
- }
+ /**
+ * Valid post fields that can be compared.
+ *
+ * @var array
+ */
+ private $valid_fields = [
+ 'post_title',
+ 'post_content',
+ 'post_excerpt',
+ 'post_name',
+ 'post_status',
+ 'post_type',
+ 'post_author',
+ 'post_date',
+ 'post_date_gmt',
+ 'post_modified',
+ 'post_modified_gmt',
+ 'post_parent',
+ 'menu_order',
+ 'comment_status',
+ 'ping_status',
+ ];
/**
* Restores a post revision.
@@ -122,11 +138,18 @@ public function diff( $args, $assoc_args ) {
}
// Validate field
- if ( ! property_exists( $from_revision, $field ) || ! property_exists( $to_revision, $field ) ) {
- WP_CLI::error( "Invalid field '{$field}'." );
+ if ( ! in_array( $field, $this->valid_fields, true ) ) {
+ WP_CLI::error( "Invalid field '{$field}'. Valid fields: " . implode( ', ', $this->valid_fields ) );
+ }
+
+ // Get the field values - use isset to check if field exists on the object
+ if ( ! isset( $from_revision->{$field} ) ) {
+ WP_CLI::error( "Field '{$field}' not found on revision {$from_id}." );
+ }
+ if ( ! isset( $to_revision->{$field} ) ) {
+ WP_CLI::error( "Field '{$field}' not found on revision/post {$to_id}." );
}
- // Get the field values
$left_string = $from_revision->{$field};
$right_string = $to_revision->{$field};
From 598c559c4736a7371387ebf4a642ab25e1c7466f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 12 Jan 2026 12:55:31 +0000
Subject: [PATCH 04/12] Fix error message display and simplify return value
check
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
---
src/Post_Revision_Command.php | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/Post_Revision_Command.php b/src/Post_Revision_Command.php
index 5d42de75..41e8f7ee 100644
--- a/src/Post_Revision_Command.php
+++ b/src/Post_Revision_Command.php
@@ -70,7 +70,7 @@ public function restore( $args ) {
// Restore the revision
$restored_post_id = wp_restore_post_revision( $revision_id );
- if ( false === $restored_post_id || null === $restored_post_id ) {
+ if ( false === $restored_post_id ) {
WP_CLI::error( "Failed to restore revision {$revision_id}." );
}
@@ -147,7 +147,8 @@ public function diff( $args, $assoc_args ) {
WP_CLI::error( "Field '{$field}' not found on revision {$from_id}." );
}
if ( ! isset( $to_revision->{$field} ) ) {
- WP_CLI::error( "Field '{$field}' not found on revision/post {$to_id}." );
+ $to_error_id = $to_id ?? $to_revision->ID;
+ WP_CLI::error( "Field '{$field}' not found on revision/post {$to_error_id}." );
}
$left_string = $from_revision->{$field};
From af620a236d24ec18489252a0a4bb103fbcd62258 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 12 Jan 2026 12:57:53 +0000
Subject: [PATCH 05/12] Handle null return value from wp_restore_post_revision
and improve error handling
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
---
src/Post_Revision_Command.php | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/Post_Revision_Command.php b/src/Post_Revision_Command.php
index 41e8f7ee..7edcb6d0 100644
--- a/src/Post_Revision_Command.php
+++ b/src/Post_Revision_Command.php
@@ -70,7 +70,8 @@ public function restore( $args ) {
// Restore the revision
$restored_post_id = wp_restore_post_revision( $revision_id );
- if ( false === $restored_post_id ) {
+ // wp_restore_post_revision() returns post ID on success, false on failure, or null if revision is same as current
+ if ( ! $restored_post_id ) {
WP_CLI::error( "Failed to restore revision {$revision_id}." );
}
@@ -146,6 +147,8 @@ public function diff( $args, $assoc_args ) {
if ( ! isset( $from_revision->{$field} ) ) {
WP_CLI::error( "Field '{$field}' not found on revision {$from_id}." );
}
+
+ // $to_revision is guaranteed to be non-null at this point due to earlier validation
if ( ! isset( $to_revision->{$field} ) ) {
$to_error_id = $to_id ?? $to_revision->ID;
WP_CLI::error( "Field '{$field}' not found on revision/post {$to_error_id}." );
From 9b54f2f9c70aa69b143d7680b525ead5cd5c8bac Mon Sep 17 00:00:00 2001
From: Pascal Birchler
Date: Tue, 13 Jan 2026 12:27:47 +0100
Subject: [PATCH 06/12] Update src/Post_Revision_Command.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/Post_Revision_Command.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Post_Revision_Command.php b/src/Post_Revision_Command.php
index 7edcb6d0..1b1b7522 100644
--- a/src/Post_Revision_Command.php
+++ b/src/Post_Revision_Command.php
@@ -71,7 +71,7 @@ public function restore( $args ) {
$restored_post_id = wp_restore_post_revision( $revision_id );
// wp_restore_post_revision() returns post ID on success, false on failure, or null if revision is same as current
- if ( ! $restored_post_id ) {
+ if ( false === $restored_post_id ) {
WP_CLI::error( "Failed to restore revision {$revision_id}." );
}
From 3e511a1395831c4bc815d222e286e5301b631666 Mon Sep 17 00:00:00 2001
From: Pascal Birchler
Date: Tue, 13 Jan 2026 12:28:03 +0100
Subject: [PATCH 07/12] Update src/Post_Revision_Command.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/Post_Revision_Command.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Post_Revision_Command.php b/src/Post_Revision_Command.php
index 1b1b7522..4f2de5db 100644
--- a/src/Post_Revision_Command.php
+++ b/src/Post_Revision_Command.php
@@ -87,7 +87,7 @@ public function restore( $args ) {
* : The 'from' revision ID or post ID.
*
* []
- * : The 'to' revision ID. If not provided, compares with the current post.
+ * : The 'to' revision ID or post ID. If not provided, compares with the current post.
*
* [--field=]
* : Compare specific field(s). Default: post_content
From acc29070b2ae6fc2dc6eaf18a146595f324cdb00 Mon Sep 17 00:00:00 2001
From: Pascal Birchler
Date: Tue, 13 Jan 2026 12:28:22 +0100
Subject: [PATCH 08/12] Update src/Post_Revision_Command.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/Post_Revision_Command.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Post_Revision_Command.php b/src/Post_Revision_Command.php
index 4f2de5db..961cf646 100644
--- a/src/Post_Revision_Command.php
+++ b/src/Post_Revision_Command.php
@@ -145,7 +145,7 @@ public function diff( $args, $assoc_args ) {
// Get the field values - use isset to check if field exists on the object
if ( ! isset( $from_revision->{$field} ) ) {
- WP_CLI::error( "Field '{$field}' not found on revision {$from_id}." );
+ WP_CLI::error( "Field '{$field}' not found on post/revision {$from_id}." );
}
// $to_revision is guaranteed to be non-null at this point due to earlier validation
From c1c8cf91aa7e45697ffe8c09c916b3855ebd11bc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 13 Jan 2026 11:30:43 +0000
Subject: [PATCH 09/12] Complete test scenario for diff between two revisions
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
---
features/post-revision.feature | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/features/post-revision.feature b/features/post-revision.feature
index c827e126..db80ee77 100644
--- a/features/post-revision.feature
+++ b/features/post-revision.feature
@@ -49,11 +49,24 @@ Feature: Manage WordPress post revisions
Success: Updated post {POST_ID}.
"""
- When I run `wp post list --post_type=revision --post_parent={POST_ID} --fields=ID --format=csv --orderby=ID --order=ASC`
- Then STDOUT should not be empty
+ When I run `wp post update {POST_ID} --post_content='Third version'`
+ Then STDOUT should contain:
+ """
+ Success: Updated post {POST_ID}.
+ """
- When I run `wp post list --post_type=revision --post_parent={POST_ID} --format=ids --orderby=ID --order=ASC`
+ When I run `wp post list --post_type=revision --post_parent={POST_ID} --fields=ID --format=ids --orderby=ID --order=ASC`
Then STDOUT should not be empty
+ And save STDOUT as {REVISION_IDS}
+
+ When I run `echo "{REVISION_IDS}" | awk '{print $1}'`
+ Then save STDOUT as {REVISION_ID_1}
+
+ When I run `echo "{REVISION_IDS}" | awk '{print $2}'`
+ Then save STDOUT as {REVISION_ID_2}
+
+ When I run `wp post revision diff {REVISION_ID_1} {REVISION_ID_2}`
+ Then the return code should be 0
Scenario: Show diff between revision and current post
When I run `wp post create --post_title='Diff Test' --post_content='Original text' --porcelain`
From 4b174c42e117c0fbef06467b67472bf1ff6d5b67 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 16 Jan 2026 20:42:12 +0000
Subject: [PATCH 10/12] Replace wp_text_diff HTML output with CLI-friendly
colored diff
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
---
src/Post_Revision_Command.php | 99 +++++++++++++++++++++++++++--------
1 file changed, 78 insertions(+), 21 deletions(-)
diff --git a/src/Post_Revision_Command.php b/src/Post_Revision_Command.php
index 961cf646..804193ca 100644
--- a/src/Post_Revision_Command.php
+++ b/src/Post_Revision_Command.php
@@ -157,30 +157,87 @@ public function diff( $args, $assoc_args ) {
$left_string = $from_revision->{$field};
$right_string = $to_revision->{$field};
- // Generate the diff
- $diff_args = [
- 'title_left' => sprintf(
- '%s (%s) - ID %d',
- $from_revision->post_title,
- $from_revision->post_modified,
- $from_revision->ID
- ),
- 'title_right' => sprintf(
- '%s (%s) - ID %d',
- $to_revision->post_title,
- $to_revision->post_modified,
- $to_revision->ID
- ),
- ];
-
- $diff = wp_text_diff( $left_string, $right_string, $diff_args );
-
- if ( ! $diff ) {
+ // Split content into lines for diff
+ $left_lines = explode( "\n", $left_string );
+ $right_lines = explode( "\n", $right_string );
+
+ // Create Text_Diff object
+ $text_diff = new \Text_Diff( 'auto', [ $left_lines, $right_lines ] );
+
+ // Check if there are any changes
+ if ( 0 === $text_diff->countAddedLines() && 0 === $text_diff->countDeletedLines() ) {
WP_CLI::success( 'No difference found.' );
return;
}
- // Output the diff
- WP_CLI::line( $diff );
+ // Display header
+ WP_CLI::line(
+ WP_CLI::colorize(
+ sprintf(
+ '%%y--- %s (%s) - ID %d%%n',
+ $from_revision->post_title,
+ $from_revision->post_modified,
+ $from_revision->ID
+ )
+ )
+ );
+ WP_CLI::line(
+ WP_CLI::colorize(
+ sprintf(
+ '%%y+++ %s (%s) - ID %d%%n',
+ $to_revision->post_title,
+ $to_revision->post_modified,
+ $to_revision->ID
+ )
+ )
+ );
+ WP_CLI::line( '' );
+
+ // Render the diff using CLI-friendly format
+ $this->render_cli_diff( $text_diff );
+ }
+
+ /**
+ * Renders a diff in CLI-friendly format with colors.
+ *
+ * @param \Text_Diff $diff The diff object to render.
+ */
+ private function render_cli_diff( $diff ) {
+ $edits = $diff->getDiff();
+
+ foreach ( $edits as $edit ) {
+ switch ( get_class( $edit ) ) {
+ case 'Text_Diff_Op_copy':
+ // Unchanged lines - show in default color
+ foreach ( $edit->orig as $line ) {
+ WP_CLI::line( ' ' . $line );
+ }
+ break;
+
+ case 'Text_Diff_Op_add':
+ // Added lines - show in green
+ foreach ( $edit->final as $line ) {
+ WP_CLI::line( WP_CLI::colorize( '%g+ ' . $line . '%n' ) );
+ }
+ break;
+
+ case 'Text_Diff_Op_delete':
+ // Deleted lines - show in red
+ foreach ( $edit->orig as $line ) {
+ WP_CLI::line( WP_CLI::colorize( '%r- ' . $line . '%n' ) );
+ }
+ break;
+
+ case 'Text_Diff_Op_change':
+ // Changed lines - show deletions in red, additions in green
+ foreach ( $edit->orig as $line ) {
+ WP_CLI::line( WP_CLI::colorize( '%r- ' . $line . '%n' ) );
+ }
+ foreach ( $edit->final as $line ) {
+ WP_CLI::line( WP_CLI::colorize( '%g+ ' . $line . '%n' ) );
+ }
+ break;
+ }
+ }
}
}
From 087ee6bc2b03cb08df2c7b04b0310b35b72a0a7f Mon Sep 17 00:00:00 2001
From: Pascal Birchler
Date: Fri, 16 Jan 2026 21:27:12 +0100
Subject: [PATCH 11/12] Fix test
---
features/post-revision.feature | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/features/post-revision.feature b/features/post-revision.feature
index db80ee77..2330eca7 100644
--- a/features/post-revision.feature
+++ b/features/post-revision.feature
@@ -3,6 +3,9 @@ Feature: Manage WordPress post revisions
Background:
Given a WP install
+ # Creating a published post doesn't create an initial revision,
+ # so we update it twice here and restore the middle version.
+ # See https://github.com/wp-cli/entity-command/issues/564.
Scenario: Restore a post revision
When I run `wp post create --post_title='Original Post' --post_content='Original content' --porcelain`
Then STDOUT should be a number
@@ -14,10 +17,22 @@ Feature: Manage WordPress post revisions
Success: Updated post {POST_ID}.
"""
- When I run `wp post list --post_type=revision --post_parent={POST_ID} --fields=ID,post_title --format=ids`
+ When I run `wp post list --post_type=revision --post_parent={POST_ID} --format=ids`
Then STDOUT should not be empty
And save STDOUT as {REVISION_ID}
+ When I run `wp post update {POST_ID} --post_content='Another one'`
+ Then STDOUT should contain:
+ """
+ Success: Updated post {POST_ID}.
+ """
+
+ When I run `wp post get {POST_ID} --field=post_content`
+ Then STDOUT should contain:
+ """
+ Another one
+ """
+
When I run `wp post revision restore {REVISION_ID}`
Then STDOUT should contain:
"""
@@ -27,7 +42,7 @@ Feature: Manage WordPress post revisions
When I run `wp post get {POST_ID} --field=post_content`
Then STDOUT should contain:
"""
- Original content
+ Updated content
"""
Scenario: Restore invalid revision should fail
From f812fc584999fa531ec775777c2e46d7900dd297 Mon Sep 17 00:00:00 2001
From: Pascal Birchler
Date: Fri, 16 Jan 2026 21:52:09 +0100
Subject: [PATCH 12/12] Update tests
---
features/post-revision.feature | 21 ++++++++++++++++++---
src/Post_Revision_Command.php | 5 +++++
2 files changed, 23 insertions(+), 3 deletions(-)
diff --git a/features/post-revision.feature b/features/post-revision.feature
index 2330eca7..9a63289b 100644
--- a/features/post-revision.feature
+++ b/features/post-revision.feature
@@ -64,7 +64,7 @@ Feature: Manage WordPress post revisions
Success: Updated post {POST_ID}.
"""
- When I run `wp post update {POST_ID} --post_content='Third version'`
+ When I run `wp post update {POST_ID} --post_title='New Title' --post_content='Third version'`
Then STDOUT should contain:
"""
Success: Updated post {POST_ID}.
@@ -81,7 +81,19 @@ Feature: Manage WordPress post revisions
Then save STDOUT as {REVISION_ID_2}
When I run `wp post revision diff {REVISION_ID_1} {REVISION_ID_2}`
- Then the return code should be 0
+ Then STDOUT should contain:
+ """
+ - Second version
+ + Third version
+ """
+ And STDOUT should contain:
+ """
+ --- Test Post
+ """
+ And STDOUT should contain:
+ """
+ +++ New Title
+ """
Scenario: Show diff between revision and current post
When I run `wp post create --post_title='Diff Test' --post_content='Original text' --porcelain`
@@ -99,7 +111,10 @@ Feature: Manage WordPress post revisions
And save STDOUT as {REVISION_ID}
When I run `wp post revision diff {REVISION_ID}`
- Then the return code should be 0
+ Then STDOUT should contain:
+ """
+ Success: No difference found.
+ """
Scenario: Diff with invalid revision should fail
When I try `wp post revision diff 99999`
diff --git a/src/Post_Revision_Command.php b/src/Post_Revision_Command.php
index 804193ca..71931831 100644
--- a/src/Post_Revision_Command.php
+++ b/src/Post_Revision_Command.php
@@ -161,6 +161,11 @@ public function diff( $args, $assoc_args ) {
$left_lines = explode( "\n", $left_string );
$right_lines = explode( "\n", $right_string );
+ if ( ! class_exists( 'Text_Diff', false ) ) {
+ // @phpstan-ignore constant.notFound
+ require ABSPATH . WPINC . '/wp-diff.php';
+ }
+
// Create Text_Diff object
$text_diff = new \Text_Diff( 'auto', [ $left_lines, $right_lines ] );