From 421b6783b1c8776dff0eb6a6f29ccbf66eda5700 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:43:42 +0000 Subject: [PATCH 01/11] Initial plan From ba9a95896fdbfac6a1e1fdfe130ec8bda3bc09e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:54:06 +0000 Subject: [PATCH 02/11] Add --old and --new flags to support strings starting with hyphens Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/search-replace.feature | 42 +++++++++++++++++++++++++++++++++ src/Search_Replace_Command.php | 34 +++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index eef35187..98d2f09f 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1392,3 +1392,45 @@ Feature: Do global search/replace """ Success: Made 0 replacements. """ + + @require-mysql + Scenario: Search/replace strings starting with hyphens using --old and --new flags + Given a WP install + And I run `wp post create --post_title="Test Post" --post_content="This is --old-content and more text" --porcelain` + Then save STDOUT as {POST_ID} + + When I run `wp search-replace --old='--old-content' --new='--new-content'` + Then STDOUT should contain: + """ + wp_posts + """ + And the return code should be 0 + + When I run `wp post get {POST_ID} --field=post_content` + Then STDOUT should contain: + """ + --new-content + """ + And STDOUT should not contain: + """ + --old-content + """ + + @require-mysql + Scenario: Error when neither positional args nor flags provided + Given a WP install + + When I try `wp search-replace` + Then STDERR should contain: + """ + Please provide both and arguments + """ + And STDERR should contain: + """ + --old + """ + And STDERR should contain: + """ + --new + """ + And the return code should be 1 diff --git a/src/Search_Replace_Command.php b/src/Search_Replace_Command.php index 6e3f85cf..fafd4ec4 100644 --- a/src/Search_Replace_Command.php +++ b/src/Search_Replace_Command.php @@ -144,6 +144,14 @@ class Search_Replace_Command extends WP_CLI_Command { * : List of database tables to restrict the replacement to. Wildcards are * supported, e.g. `'wp_*options'` or `'wp_post*'`. * + * [--old=] + * : An alternative way to specify the search string. Use this when the + * search string starts with '--' (e.g., --old='--some-text'). + * + * [--new=] + * : An alternative way to specify the replacement string. Use this when the + * replacement string starts with '--' (e.g., --new='--other-text'). + * * [--dry-run] * : Run the entire search/replace operation and show report, but don't save * changes to the database. @@ -248,6 +256,9 @@ class Search_Replace_Command extends WP_CLI_Command { * # Search/replace to a SQL file without transforming the database * $ wp search-replace foo bar --export=database.sql * + * # Search/replace string containing hyphens + * $ wp search-replace --old='--old-string' --new='new-string' + * * # Bash script: Search/replace production to development url (multisite compatible) * #!/bin/bash * if $(wp --url=http://example.com core is-installed --network); then @@ -257,12 +268,29 @@ class Search_Replace_Command extends WP_CLI_Command { * fi * * @param array $args Positional arguments. - * @param array{'dry-run'?: bool, 'network'?: bool, 'all-tables-with-prefix'?: bool, 'all-tables'?: bool, 'export'?: string, 'export_insert_size'?: string, 'skip-tables'?: string, 'skip-columns'?: string, 'include-columns'?: string, 'precise'?: bool, 'recurse-objects'?: bool, 'verbose'?: bool, 'regex'?: bool, 'regex-flags'?: string, 'regex-delimiter'?: string, 'regex-limit'?: string, 'format': string, 'report'?: bool, 'report-changed-only'?: bool, 'log'?: string, 'before_context'?: string, 'after_context'?: string} $assoc_args Associative arguments. + * @param array{'old'?: string, 'new'?: string, 'dry-run'?: bool, 'network'?: bool, 'all-tables-with-prefix'?: bool, 'all-tables'?: bool, 'export'?: string, 'export_insert_size'?: string, 'skip-tables'?: string, 'skip-columns'?: string, 'include-columns'?: string, 'precise'?: bool, 'recurse-objects'?: bool, 'verbose'?: bool, 'regex'?: bool, 'regex-flags'?: string, 'regex-delimiter'?: string, 'regex-limit'?: string, 'format': string, 'report'?: bool, 'report-changed-only'?: bool, 'log'?: string, 'before_context'?: string, 'after_context'?: string} $assoc_args Associative arguments. */ public function __invoke( $args, $assoc_args ) { global $wpdb; - $old = array_shift( $args ); - $new = array_shift( $args ); + + // Support --old and --new flags as an alternative to positional arguments. + // This allows users to search/replace strings that start with '--'. + $old = Utils\get_flag_value( $assoc_args, 'old' ); + $new = Utils\get_flag_value( $assoc_args, 'new' ); + + // Fall back to positional arguments if flags not provided. + if ( null === $old ) { + $old = array_shift( $args ); + } + if ( null === $new ) { + $new = array_shift( $args ); + } + + // Validate that both old and new values are provided. + if ( null === $old || null === $new ) { + WP_CLI::error( "Please provide both and arguments.\n\nNote: Strings starting with '--' (hyphens) cannot be used as positional arguments. Use the '--old' and '--new' flags instead:\n wp search-replace --old='--old-word' --new='new-word'" ); + } + $total = 0; $report = array(); $this->dry_run = Utils\get_flag_value( $assoc_args, 'dry-run', false ); From dbbd8a36855887c5658595198c0e5c1626ffe5f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:56:05 +0000 Subject: [PATCH 03/11] Improve error message to be more context-aware Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Search_Replace_Command.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Search_Replace_Command.php b/src/Search_Replace_Command.php index fafd4ec4..e43adbff 100644 --- a/src/Search_Replace_Command.php +++ b/src/Search_Replace_Command.php @@ -288,7 +288,20 @@ public function __invoke( $args, $assoc_args ) { // Validate that both old and new values are provided. if ( null === $old || null === $new ) { - WP_CLI::error( "Please provide both and arguments.\n\nNote: Strings starting with '--' (hyphens) cannot be used as positional arguments. Use the '--old' and '--new' flags instead:\n wp search-replace --old='--old-word' --new='new-word'" ); + $missing = array(); + if ( null === $old ) { + $missing[] = ''; + } + if ( null === $new ) { + $missing[] = ''; + } + $error_msg = 'Please provide both and arguments.'; + if ( count( $missing ) === 1 ) { + $error_msg = sprintf( 'Please provide the %s argument.', $missing[0] ); + } + $error_msg .= "\n\nNote: If your search or replacement string starts with '--', use the flag syntax instead:"; + $error_msg .= "\n wp search-replace --old='--text' --new='replacement'"; + WP_CLI::error( $error_msg ); } $total = 0; From f4eda282f41cd77ae5d23a6a7a6145148b3019bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:56:56 +0000 Subject: [PATCH 04/11] Simplify error message construction for better readability Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Search_Replace_Command.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Search_Replace_Command.php b/src/Search_Replace_Command.php index e43adbff..825523f8 100644 --- a/src/Search_Replace_Command.php +++ b/src/Search_Replace_Command.php @@ -295,12 +295,13 @@ public function __invoke( $args, $assoc_args ) { if ( null === $new ) { $missing[] = ''; } - $error_msg = 'Please provide both and arguments.'; - if ( count( $missing ) === 1 ) { - $error_msg = sprintf( 'Please provide the %s argument.', $missing[0] ); - } - $error_msg .= "\n\nNote: If your search or replacement string starts with '--', use the flag syntax instead:"; - $error_msg .= "\n wp search-replace --old='--text' --new='replacement'"; + $error_msg = count( $missing ) === 2 + ? 'Please provide both and arguments.' + : sprintf( 'Please provide the %s argument.', $missing[0] ); + + $error_msg .= "\n\nNote: If your search or replacement string starts with '--', use the flag syntax instead:" + . "\n wp search-replace --old='--text' --new='replacement'"; + WP_CLI::error( $error_msg ); } From 77fb3eb7e32f2d237fd93d3fa9a37d04befd0948 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 19 Jan 2026 16:34:46 +0100 Subject: [PATCH 05/11] Make `` and `` optional --- README.md | 17 ++++++++++++++--- src/Search_Replace_Command.php | 4 ++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dc609de1..1cb3662d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contr ## Using ~~~ -wp search-replace [...] [--dry-run] [--network] [--all-tables-with-prefix] [--all-tables] [--export[=]] [--export_insert_size=] [--skip-tables=] [--skip-columns=] [--include-columns=] [--precise] [--recurse-objects] [--verbose] [--regex] [--regex-flags=] [--regex-delimiter=] [--regex-limit=] [--format=] [--report] [--report-changed-only] [--log[=]] [--before_context=] [--after_context=] +wp search-replace [] [] [
...] [--old=] [--new=] [--dry-run] [--network] [--all-tables-with-prefix] [--all-tables] [--export[=]] [--export_insert_size=] [--skip-tables=] [--skip-columns=] [--include-columns=] [--precise] [--recurse-objects] [--verbose] [--regex] [--regex-flags=] [--regex-delimiter=] [--regex-limit=] [--format=] [--report] [--report-changed-only] [--log[=]] [--before_context=] [--after_context=] ~~~ Searches through all rows in a selection of tables and replaces @@ -25,16 +25,24 @@ change primary key values. **OPTIONS** - + [] A string to search for within the database. - + [] Replace instances of the first string with this new string. [
...] List of database tables to restrict the replacement to. Wildcards are supported, e.g. `'wp_*options'` or `'wp_post*'`. + [--old=] + An alternative way to specify the search string. Use this when the + search string starts with '--' (e.g., --old='--some-text'). + + [--new=] + An alternative way to specify the replacement string. Use this when the + replacement string starts with '--' (e.g., --new='--other-text'). + [--dry-run] Run the entire search/replace operation and show report, but don't save changes to the database. @@ -139,6 +147,9 @@ change primary key values. # Search/replace to a SQL file without transforming the database $ wp search-replace foo bar --export=database.sql + # Search/replace string containing hyphens + $ wp search-replace --old='--old-string' --new='new-string' + # Bash script: Search/replace production to development url (multisite compatible) #!/bin/bash if $(wp --url=http://example.com core is-installed --network); then diff --git a/src/Search_Replace_Command.php b/src/Search_Replace_Command.php index 825523f8..6f494822 100644 --- a/src/Search_Replace_Command.php +++ b/src/Search_Replace_Command.php @@ -134,10 +134,10 @@ class Search_Replace_Command extends WP_CLI_Command { * * ## OPTIONS * - * + * [] * : A string to search for within the database. * - * + * [] * : Replace instances of the first string with this new string. * * [
...] From dc5b101dea1a867a26b1b9a8cf1b7aa2d16b797e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 15:50:35 +0000 Subject: [PATCH 06/11] Add validation for empty strings and mixed argument usage - Validate that --old and --new flags contain non-empty strings - Prevent simultaneous use of flags and positional arguments - Add comprehensive test scenarios for edge cases - Test coverage for: only --old, only --new, mixed usage, empty strings, single hyphen strings Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/search-replace.feature | 78 +++++++++++++++++++++++++++++++++ src/Search_Replace_Command.php | 28 +++++++----- 2 files changed, 96 insertions(+), 10 deletions(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index 98d2f09f..d88d0a1f 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1434,3 +1434,81 @@ Feature: Do global search/replace --new """ And the return code should be 1 + + @require-mysql + Scenario: Error when only --old flag provided without --new + Given a WP install + + When I try `wp search-replace --old='test-value'` + Then STDERR should contain: + """ + Please provide the argument + """ + And the return code should be 1 + + @require-mysql + Scenario: Error when only --new flag provided without --old + Given a WP install + + When I try `wp search-replace --new='test-value'` + Then STDERR should contain: + """ + Please provide the argument + """ + And the return code should be 1 + + @require-mysql + Scenario: Error when both flags and positional arguments provided + Given a WP install + + When I try `wp search-replace --old='flag-old' 'positional-old' 'positional-new'` + Then STDERR should contain: + """ + Cannot use both positional arguments and --old/--new flags + """ + And the return code should be 1 + + @require-mysql + Scenario: Error when empty string provided via --old flag + Given a WP install + + When I try `wp search-replace --old='' --new='replacement'` + Then STDERR should contain: + """ + Please provide the argument + """ + And the return code should be 1 + + @require-mysql + Scenario: Error when empty string provided via --new flag + Given a WP install + + When I try `wp search-replace --old='search' --new=''` + Then STDERR should contain: + """ + Please provide the argument + """ + And the return code should be 1 + + @require-mysql + Scenario: Search/replace string starting with single hyphen works with positional args + Given a WP install + And I run `wp post create --post_title="Test Post" --post_content="This is -single-hyphen content" --porcelain` + Then save STDOUT as {POST_ID} + + When I run `wp search-replace '-single-hyphen' '-replaced-hyphen'` + Then STDOUT should contain: + """ + wp_posts + """ + And the return code should be 0 + + When I run `wp post get {POST_ID} --field=post_content` + Then STDOUT should contain: + """ + -replaced-hyphen + """ + And STDOUT should not contain: + """ + -single-hyphen + """ diff --git a/src/Search_Replace_Command.php b/src/Search_Replace_Command.php index 6f494822..2e41b17d 100644 --- a/src/Search_Replace_Command.php +++ b/src/Search_Replace_Command.php @@ -275,24 +275,32 @@ public function __invoke( $args, $assoc_args ) { // Support --old and --new flags as an alternative to positional arguments. // This allows users to search/replace strings that start with '--'. - $old = Utils\get_flag_value( $assoc_args, 'old' ); - $new = Utils\get_flag_value( $assoc_args, 'new' ); + $old_flag = Utils\get_flag_value( $assoc_args, 'old' ); + $new_flag = Utils\get_flag_value( $assoc_args, 'new' ); + + // Check if both flags and positional arguments are provided. + $has_flags = null !== $old_flag || null !== $new_flag; + $has_args = ! empty( $args ); + if ( $has_flags && $has_args ) { + WP_CLI::error( 'Cannot use both positional arguments and --old/--new flags. Please use one method or the other.' ); + } - // Fall back to positional arguments if flags not provided. - if ( null === $old ) { + // Determine old and new values based on flags or positional arguments. + if ( null !== $old_flag || null !== $new_flag ) { + $old = $old_flag; + $new = $new_flag; + } else { $old = array_shift( $args ); - } - if ( null === $new ) { $new = array_shift( $args ); } - // Validate that both old and new values are provided. - if ( null === $old || null === $new ) { + // Validate that both old and new values are provided and not empty. + if ( null === $old || null === $new || '' === $old || '' === $new ) { $missing = array(); - if ( null === $old ) { + if ( null === $old || '' === $old ) { $missing[] = ''; } - if ( null === $new ) { + if ( null === $new || '' === $new ) { $missing[] = ''; } $error_msg = count( $missing ) === 2 From 4fb255cfacae9bc1707d5cd9ad0eacece24e83d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 15:51:56 +0000 Subject: [PATCH 07/11] Fix mixed argument validation logic - Only prevent mixed usage when BOTH flags are provided with positional args - Allow using one flag with one positional arg (e.g., --old with positional new) - Simplify logic to check flags individually for fallback Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Search_Replace_Command.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Search_Replace_Command.php b/src/Search_Replace_Command.php index 2e41b17d..0335338e 100644 --- a/src/Search_Replace_Command.php +++ b/src/Search_Replace_Command.php @@ -279,20 +279,15 @@ public function __invoke( $args, $assoc_args ) { $new_flag = Utils\get_flag_value( $assoc_args, 'new' ); // Check if both flags and positional arguments are provided. - $has_flags = null !== $old_flag || null !== $new_flag; - $has_args = ! empty( $args ); - if ( $has_flags && $has_args ) { + $both_flags_provided = null !== $old_flag && null !== $new_flag; + $has_positional_args = ! empty( $args ); + if ( $both_flags_provided && $has_positional_args ) { WP_CLI::error( 'Cannot use both positional arguments and --old/--new flags. Please use one method or the other.' ); } - // Determine old and new values based on flags or positional arguments. - if ( null !== $old_flag || null !== $new_flag ) { - $old = $old_flag; - $new = $new_flag; - } else { - $old = array_shift( $args ); - $new = array_shift( $args ); - } + // Determine old and new values. + $old = null !== $old_flag ? $old_flag : array_shift( $args ); + $new = null !== $new_flag ? $new_flag : array_shift( $args ); // Validate that both old and new values are provided and not empty. if ( null === $old || null === $new || '' === $old || '' === $new ) { From 1cc247e35c044ed40ef1576cd4736082cb24d27a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 16:00:34 +0000 Subject: [PATCH 08/11] Fix test and add scenario for flexible flag/arg mixing - Correct test for mixed usage to use BOTH flags with positional args - Add test showing one flag can be mixed with one positional arg - This demonstrates the flexible approach allows --old='value' with positional new Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/search-replace.feature | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index d88d0a1f..e7fd1806 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1461,7 +1461,7 @@ Feature: Do global search/replace Scenario: Error when both flags and positional arguments provided Given a WP install - When I try `wp search-replace --old='flag-old' 'positional-old' 'positional-new'` + When I try `wp search-replace --old='flag-old' --new='flag-new' 'positional-arg'` Then STDERR should contain: """ Cannot use both positional arguments and --old/--new flags @@ -1512,3 +1512,26 @@ Feature: Do global search/replace """ -single-hyphen """ + + @require-mysql + Scenario: Allow mixing one flag with one positional argument + Given a WP install + And I run `wp post create --post_title="Test Post" --post_content="This is --old-content text" --porcelain` + Then save STDOUT as {POST_ID} + + When I run `wp search-replace --old='--old-content' 'new-content'` + Then STDOUT should contain: + """ + wp_posts + """ + And the return code should be 0 + + When I run `wp post get {POST_ID} --field=post_content` + Then STDOUT should contain: + """ + new-content + """ + And STDOUT should not contain: + """ + --old-content + """ From 4b2a653c0fbf85553ab7edf6a1099b174f51e9cc Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 19 Jan 2026 17:15:09 +0100 Subject: [PATCH 09/11] new value is allowed to be empty. --- src/Search_Replace_Command.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Search_Replace_Command.php b/src/Search_Replace_Command.php index 0335338e..88bc554d 100644 --- a/src/Search_Replace_Command.php +++ b/src/Search_Replace_Command.php @@ -290,12 +290,13 @@ public function __invoke( $args, $assoc_args ) { $new = null !== $new_flag ? $new_flag : array_shift( $args ); // Validate that both old and new values are provided and not empty. - if ( null === $old || null === $new || '' === $old || '' === $new ) { + if ( null === $old || null === $new || '' === $old ) { $missing = array(); if ( null === $old || '' === $old ) { $missing[] = ''; } - if ( null === $new || '' === $new ) { + // new value is allowed to be empty. + if ( null === $new ) { $missing[] = ''; } $error_msg = count( $missing ) === 2 From be22d0e7f63b12b7efae86175fb57a1c1e532a5e Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 19 Jan 2026 17:23:43 +0100 Subject: [PATCH 10/11] Update test --- features/search-replace.feature | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index e7fd1806..062a67e0 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1480,15 +1480,14 @@ Feature: Do global search/replace And the return code should be 1 @require-mysql - Scenario: Error when empty string provided via --new flag + Scenario: No error when empty string provided via --new flag Given a WP install When I try `wp search-replace --old='search' --new=''` - Then STDERR should contain: + Then STDERR should not contain: """ Please provide the argument """ - And the return code should be 1 @require-mysql Scenario: Search/replace string starting with single hyphen works with positional args From 4f82acb592452eb027fba71ba9d4542d08f7a80f Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 22 Jan 2026 10:28:26 +0100 Subject: [PATCH 11/11] Lint fix --- src/Search_Replace_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Search_Replace_Command.php b/src/Search_Replace_Command.php index 11f1b4aa..73e9b311 100644 --- a/src/Search_Replace_Command.php +++ b/src/Search_Replace_Command.php @@ -260,7 +260,7 @@ class Search_Replace_Command extends WP_CLI_Command { * * # Search/replace string containing hyphens * $ wp search-replace --old='--old-string' --new='new-string' - * + * * # Use precise mode for complex serialized data * $ wp search-replace 'oldurl.com' 'newurl.com' --precise *