Skip to content

Commit b0eeefa

Browse files
authored
Merge branch 'main' into copilot/add-progress-bar-indicator
2 parents 077285b + ee115fd commit b0eeefa

File tree

9 files changed

+246
-16
lines changed

9 files changed

+246
-16
lines changed

.github/workflows/code-quality.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ on:
66
branches:
77
- main
88
- master
9+
schedule:
10+
- cron: '17 2 * * *' # Run every day on a seemly random time.
911

1012
jobs:
1113
code-quality:

.github/workflows/copilot-setup-steps.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@ jobs:
1717

1818
steps:
1919
- name: Checkout code
20-
uses: actions/checkout@v6
20+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
2121

2222
- name: Check existence of composer.json file
2323
id: check_composer_file
24-
uses: andstor/file-existence-action@v3
24+
uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3
2525
with:
2626
files: "composer.json"
2727

2828
- name: Set up PHP environment
2929
if: steps.check_composer_file.outputs.files_exists == 'true'
30-
uses: shivammathur/setup-php@v2
30+
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2
3131
with:
3232
php-version: 'latest'
3333
ini-values: zend.assertions=1, error_reporting=-1, display_errors=On
@@ -38,7 +38,7 @@ jobs:
3838

3939
- name: Install Composer dependencies & cache dependencies
4040
if: steps.check_composer_file.outputs.files_exists == 'true'
41-
uses: ramsey/composer-install@v3
41+
uses: ramsey/composer-install@a35c6ebd3d08125aaf8852dff361e686a1a67947 # v3
4242
env:
4343
COMPOSER_ROOT_VERSION: dev-${{ github.event.repository.default_branch }}
4444
with:

.github/workflows/issue-triage.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ name: Issue and PR Triage
1313
required: false
1414
type: string
1515

16+
permissions:
17+
issues: write
18+
pull-requests: write
19+
actions: write
20+
contents: read
21+
models: read
22+
1623
jobs:
1724
issue-triage:
1825
uses: wp-cli/.github/.github/workflows/reusable-issue-triage.yml@main

.github/workflows/regenerate-readme.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ on:
1010
- "features/**"
1111
- "README.md"
1212

13+
permissions:
14+
contents: write
15+
pull-requests: write
16+
1317
jobs:
1418
regenerate-readme:
1519
uses: wp-cli/.github/.github/workflows/reusable-regenerate-readme.yml@main

.github/workflows/welcome-new-contributors.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ on:
77
- main
88
- master
99

10+
permissions:
11+
pull-requests: write
12+
1013
jobs:
1114
welcome:
1215
uses: wp-cli/.github/.github/workflows/reusable-welcome-new-contributors.yml@main

.typos.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[default]
2+
extend-ignore-re = [
3+
"(?Rm)^.*(#|//)\\s*spellchecker:disable-line$",
4+
"(?s)(#|//)\\s*spellchecker:off.*?\\n\\s*(#|//)\\s*spellchecker:on",
5+
"(#|//)\\s*spellchecker:ignore-next-line\\n.*"
6+
]

README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contr
1010
## Using
1111

1212
~~~
13-
wp search-replace <old> <new> [<table>...] [--dry-run] [--network] [--all-tables-with-prefix] [--all-tables] [--export[=<file>]] [--export_insert_size=<rows>] [--skip-tables=<tables>] [--skip-columns=<columns>] [--include-columns=<columns>] [--precise] [--recurse-objects] [--verbose] [--regex] [--regex-flags=<regex-flags>] [--regex-delimiter=<regex-delimiter>] [--regex-limit=<regex-limit>] [--format=<format>] [--report] [--report-changed-only] [--log[=<file>]] [--before_context=<num>] [--after_context=<num>]
13+
wp search-replace [<old>] [<new>] [<table>...] [--old=<value>] [--new=<value>] [--dry-run] [--network] [--all-tables-with-prefix] [--all-tables] [--export[=<file>]] [--export_insert_size=<rows>] [--skip-tables=<tables>] [--skip-columns=<columns>] [--include-columns=<columns>] [--precise] [--recurse-objects] [--verbose] [--regex] [--regex-flags=<regex-flags>] [--regex-delimiter=<regex-delimiter>] [--regex-limit=<regex-limit>] [--format=<format>] [--report] [--report-changed-only] [--log[=<file>]] [--before_context=<num>] [--after_context=<num>]
1414
~~~
1515

1616
Searches through all rows in a selection of tables and replaces
@@ -25,16 +25,24 @@ change primary key values.
2525

2626
**OPTIONS**
2727

28-
<old>
28+
[<old>]
2929
A string to search for within the database.
3030

31-
<new>
31+
[<new>]
3232
Replace instances of the first string with this new string.
3333

3434
[<table>...]
3535
List of database tables to restrict the replacement to. Wildcards are
3636
supported, e.g. `'wp_*options'` or `'wp_post*'`.
3737

38+
[--old=<value>]
39+
An alternative way to specify the search string. Use this when the
40+
search string starts with '--' (e.g., --old='--some-text').
41+
42+
[--new=<value>]
43+
An alternative way to specify the replacement string. Use this when the
44+
replacement string starts with '--' (e.g., --new='--other-text').
45+
3846
[--dry-run]
3947
Run the entire search/replace operation and show report, but don't save
4048
changes to the database.
@@ -74,8 +82,10 @@ change primary key values.
7482
specify multiple columns.
7583

7684
[--precise]
77-
Force the use of PHP (instead of SQL) which is more thorough,
78-
but slower.
85+
Force the use of PHP (instead of SQL) for all columns. By default, the command
86+
uses fast SQL queries, but automatically switches to PHP for columns containing
87+
serialized data. Use this flag to ensure PHP processes all columns, which is
88+
slower but handles complex serialized data structures more reliably.
7989

8090
[--recurse-objects]
8191
Enable recursing into objects to replace strings. Defaults to true;
@@ -139,6 +149,12 @@ change primary key values.
139149
# Search/replace to a SQL file without transforming the database
140150
$ wp search-replace foo bar --export=database.sql
141151

152+
# Search/replace string containing hyphens
153+
$ wp search-replace --old='--old-string' --new='new-string'
154+
155+
# Use precise mode for complex serialized data
156+
$ wp search-replace 'oldurl.com' 'newurl.com' --precise
157+
142158
# Bash script: Search/replace production to development url (multisite compatible)
143159
#!/bin/bash
144160
if $(wp --url=http://example.com core is-installed --network); then

features/search-replace.feature

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,28 @@ Feature: Do global search/replace
13931393
Success: Made 0 replacements.
13941394
"""
13951395

1396+
Scenario: Search/replace strings starting with hyphens using --old and --new flags
1397+
Given a WP install
1398+
And I run `wp post create --post_title="Test Post" --post_content="This is --old-content and more text" --porcelain`
1399+
Then save STDOUT as {POST_ID}
1400+
1401+
When I run `wp search-replace --old='--old-content' --new='--new-content'`
1402+
Then STDOUT should contain:
1403+
"""
1404+
wp_posts
1405+
"""
1406+
And the return code should be 0
1407+
1408+
When I run `wp post get {POST_ID} --field=post_content`
1409+
Then STDOUT should contain:
1410+
"""
1411+
--new-content
1412+
"""
1413+
And STDOUT should not contain:
1414+
"""
1415+
--old-content
1416+
"""
1417+
13961418
@require-mysql
13971419
Scenario: Progress bar shows when not in verbose mode
13981420
Given a WP install
@@ -1419,3 +1441,122 @@ Feature: Do global search/replace
14191441
"""
14201442
Updating
14211443
"""
1444+
1445+
@require-mysql
1446+
Scenario: Error when neither positional args nor flags provided
1447+
Given a WP install
1448+
1449+
When I try `wp search-replace`
1450+
Then STDERR should contain:
1451+
"""
1452+
Please provide both <old> and <new> arguments
1453+
"""
1454+
And STDERR should contain:
1455+
"""
1456+
--old
1457+
"""
1458+
And STDERR should contain:
1459+
"""
1460+
--new
1461+
"""
1462+
And the return code should be 1
1463+
1464+
@require-mysql
1465+
Scenario: Error when only --old flag provided without --new
1466+
Given a WP install
1467+
1468+
When I try `wp search-replace --old='test-value'`
1469+
Then STDERR should contain:
1470+
"""
1471+
Please provide the <new> argument
1472+
"""
1473+
And the return code should be 1
1474+
1475+
@require-mysql
1476+
Scenario: Error when only --new flag provided without --old
1477+
Given a WP install
1478+
1479+
When I try `wp search-replace --new='test-value'`
1480+
Then STDERR should contain:
1481+
"""
1482+
Please provide the <old> argument
1483+
"""
1484+
And the return code should be 1
1485+
1486+
@require-mysql
1487+
Scenario: Error when both flags and positional arguments provided
1488+
Given a WP install
1489+
1490+
When I try `wp search-replace --old='flag-old' --new='flag-new' 'positional-arg'`
1491+
Then STDERR should contain:
1492+
"""
1493+
Cannot use both positional arguments and --old/--new flags
1494+
"""
1495+
And the return code should be 1
1496+
1497+
@require-mysql
1498+
Scenario: Error when empty string provided via --old flag
1499+
Given a WP install
1500+
1501+
When I try `wp search-replace --old='' --new='replacement'`
1502+
Then STDERR should contain:
1503+
"""
1504+
Please provide the <old> argument
1505+
"""
1506+
And the return code should be 1
1507+
1508+
@require-mysql
1509+
Scenario: No error when empty string provided via --new flag
1510+
Given a WP install
1511+
1512+
When I try `wp search-replace --old='search' --new=''`
1513+
Then STDERR should not contain:
1514+
"""
1515+
Please provide the <new> argument
1516+
"""
1517+
1518+
@require-mysql
1519+
Scenario: Search/replace string starting with single hyphen works with positional args
1520+
Given a WP install
1521+
And I run `wp post create --post_title="Test Post" --post_content="This is -single-hyphen content" --porcelain`
1522+
Then save STDOUT as {POST_ID}
1523+
1524+
When I run `wp search-replace '-single-hyphen' '-replaced-hyphen'`
1525+
Then STDOUT should contain:
1526+
"""
1527+
wp_posts
1528+
"""
1529+
And the return code should be 0
1530+
1531+
When I run `wp post get {POST_ID} --field=post_content`
1532+
Then STDOUT should contain:
1533+
"""
1534+
-replaced-hyphen
1535+
"""
1536+
And STDOUT should not contain:
1537+
"""
1538+
-single-hyphen
1539+
"""
1540+
1541+
@require-mysql
1542+
Scenario: Allow mixing one flag with one positional argument
1543+
Given a WP install
1544+
And I run `wp post create --post_title="Test Post" --post_content="This is --old-content text" --porcelain`
1545+
Then save STDOUT as {POST_ID}
1546+
1547+
When I run `wp search-replace --old='--old-content' 'new-content'`
1548+
Then STDOUT should contain:
1549+
"""
1550+
wp_posts
1551+
"""
1552+
And the return code should be 0
1553+
1554+
When I run `wp post get {POST_ID} --field=post_content`
1555+
Then STDOUT should contain:
1556+
"""
1557+
new-content
1558+
"""
1559+
And STDOUT should not contain:
1560+
"""
1561+
--old-content
1562+
"""

src/Search_Replace_Command.php

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,16 +134,24 @@ class Search_Replace_Command extends WP_CLI_Command {
134134
*
135135
* ## OPTIONS
136136
*
137-
* <old>
137+
* [<old>]
138138
* : A string to search for within the database.
139139
*
140-
* <new>
140+
* [<new>]
141141
* : Replace instances of the first string with this new string.
142142
*
143143
* [<table>...]
144144
* : List of database tables to restrict the replacement to. Wildcards are
145145
* supported, e.g. `'wp_*options'` or `'wp_post*'`.
146146
*
147+
* [--old=<value>]
148+
* : An alternative way to specify the search string. Use this when the
149+
* search string starts with '--' (e.g., --old='--some-text').
150+
*
151+
* [--new=<value>]
152+
* : An alternative way to specify the replacement string. Use this when the
153+
* replacement string starts with '--' (e.g., --new='--other-text').
154+
*
147155
* [--dry-run]
148156
* : Run the entire search/replace operation and show report, but don't save
149157
* changes to the database.
@@ -183,8 +191,10 @@ class Search_Replace_Command extends WP_CLI_Command {
183191
* specify multiple columns.
184192
*
185193
* [--precise]
186-
* : Force the use of PHP (instead of SQL) which is more thorough,
187-
* but slower.
194+
* : Force the use of PHP (instead of SQL) for all columns. By default, the command
195+
* uses fast SQL queries, but automatically switches to PHP for columns containing
196+
* serialized data. Use this flag to ensure PHP processes all columns, which is
197+
* slower but handles complex serialized data structures more reliably.
188198
*
189199
* [--recurse-objects]
190200
* : Enable recursing into objects to replace strings. Defaults to true;
@@ -248,6 +258,12 @@ class Search_Replace_Command extends WP_CLI_Command {
248258
* # Search/replace to a SQL file without transforming the database
249259
* $ wp search-replace foo bar --export=database.sql
250260
*
261+
* # Search/replace string containing hyphens
262+
* $ wp search-replace --old='--old-string' --new='new-string'
263+
*
264+
* # Use precise mode for complex serialized data
265+
* $ wp search-replace 'oldurl.com' 'newurl.com' --precise
266+
*
251267
* # Bash script: Search/replace production to development url (multisite compatible)
252268
* #!/bin/bash
253269
* if $(wp --url=http://example.com core is-installed --network); then
@@ -257,12 +273,47 @@ class Search_Replace_Command extends WP_CLI_Command {
257273
* fi
258274
*
259275
* @param array<string> $args Positional arguments.
260-
* @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.
276+
* @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.
261277
*/
262278
public function __invoke( $args, $assoc_args ) {
263279
global $wpdb;
264-
$old = array_shift( $args );
265-
$new = array_shift( $args );
280+
281+
// Support --old and --new flags as an alternative to positional arguments.
282+
// This allows users to search/replace strings that start with '--'.
283+
$old_flag = Utils\get_flag_value( $assoc_args, 'old' );
284+
$new_flag = Utils\get_flag_value( $assoc_args, 'new' );
285+
286+
// Check if both flags and positional arguments are provided.
287+
$both_flags_provided = null !== $old_flag && null !== $new_flag;
288+
$has_positional_args = ! empty( $args );
289+
if ( $both_flags_provided && $has_positional_args ) {
290+
WP_CLI::error( 'Cannot use both positional arguments and --old/--new flags. Please use one method or the other.' );
291+
}
292+
293+
// Determine old and new values.
294+
$old = null !== $old_flag ? $old_flag : array_shift( $args );
295+
$new = null !== $new_flag ? $new_flag : array_shift( $args );
296+
297+
// Validate that both old and new values are provided and not empty.
298+
if ( null === $old || null === $new || '' === $old ) {
299+
$missing = array();
300+
if ( null === $old || '' === $old ) {
301+
$missing[] = '<old>';
302+
}
303+
// new value is allowed to be empty.
304+
if ( null === $new ) {
305+
$missing[] = '<new>';
306+
}
307+
$error_msg = count( $missing ) === 2
308+
? 'Please provide both <old> and <new> arguments.'
309+
: sprintf( 'Please provide the %s argument.', $missing[0] );
310+
311+
$error_msg .= "\n\nNote: If your search or replacement string starts with '--', use the flag syntax instead:"
312+
. "\n wp search-replace --old='--text' --new='replacement'";
313+
314+
WP_CLI::error( $error_msg );
315+
}
316+
266317
$total = 0;
267318
$report = array();
268319
$this->dry_run = Utils\get_flag_value( $assoc_args, 'dry-run', false );

0 commit comments

Comments
 (0)