WordPress.org

Making WordPress.org

Ticket #3867: patch-upgrader.php

File patch-upgrader.php, 9.1 KB (added by pento, 8 months ago)

Experimental patch refresher script

Line 
1<?php
2
3define( 'DEBUG_LEVEL', 1 );
4define( 'UPLOAD_PATCH', false );
5
6define( 'WPORG_USER', '' );
7define( 'WPORG_PASSWORD', '' );
8
9// The massive first auto-fixer commit.
10$firstFixerCommit = '8f95800d52c1736d651ae6e259f90ad4a0db2c3f';
11
12// Subsequent fixer commits.
13$fixerCommits = array(
14        // Upgrade WPCS to 1.0.0.
15        'a75d153eeeac9154c2576697adf721027e1d073e',
16);
17
18/*
19 * When applying patches, certain strings in the patch will cause the value of the -p option
20 * to change. As most patches are made with SVN, we default to 0, and change for other patch styles.
21 */
22$pLevels = array(
23        1 => array(
24                'diff --git a/',
25                '--- /www/wp-',
26        ),
27);
28
29/*
30 * Sometimes patches are uploaded that don't apply to the base directory, so we need to change
31 * the --directory option to a new target.
32 */
33$targets = array(
34        'src' => array(
35                'Index: wp-',
36                'diff --git wp-',
37                'diff --git a/wp-',
38                '--- /www/wp-',
39        ),
40        'src/wp-includes' => array(
41                'Index: feed',
42        ),
43);
44
45if ( ! file_exists( '../working/ticket-data.csv' ) ) {
46        echo "Downloading ticket data...\n";
47
48        shell_exec( 'curl https://core.trac.wordpress.org/report/1?sort=ticket&asc=1&format=csv > ../working/ticket-data.csv' );
49}
50
51// Make sure master is up to date
52$output = shell_exec( 'git checkout -f master' );
53if ( DEBUG_LEVEL ) {
54        echo $output;
55}
56$output = shell_exec( 'git pull origin master' );
57if ( DEBUG_LEVEL ) {
58        echo $output;
59}
60
61$ticketHandle = fopen( '../working/ticket-data.csv', 'r' );
62
63while ( ( $ticketData = fgetcsv( $ticketHandle ) ) !== FALSE ) {
64        $ticket = $ticketData[0];
65        if ( ! is_numeric( $ticket ) ) {
66                continue;
67        }
68
69        $output = shell_exec( 'git checkout -f master' );
70        if ( DEBUG_LEVEL ) {
71                echo $output;
72        }
73
74        $output = shell_exec( 'git clean -fd' );
75        if ( DEBUG_LEVEL ) {
76                echo $output;
77        }
78
79        $output = shell_exec( 'git reset --hard origin/master' );
80        if ( DEBUG_LEVEL ) {
81                echo $output;
82        }
83
84        if ( file_exists( "../working/$ticket-automerged.diff" ) ) {
85                continue;
86        }
87
88        if ( ! file_exists( "../working/$ticket.diff" ) ) {
89                $ticketHtml = file_get_contents( "https://core.trac.wordpress.org/ticket/$ticket" );
90
91                $matches = array();
92                preg_match_all( "|<a href=\"/(attachment/ticket/$ticket/[^\"]*)\" title=\"View attachment\">.*from=([^&]*)|", $ticketHtml, $matches );
93
94                $newestDiff = '';
95                $newestDiffTime = 0;
96                for ( $ii = 0; $ii < count( $matches[0] ); $ii++ ) {
97                        if ( ! preg_match( '/.(diff|patch)$/', $matches[1][ $ii ] ) ) {
98                                continue;
99                        }
100
101                        $time = strtotime( urldecode( $matches[2][ $ii ] ) );
102                        if ( $time > $newestDiffTime ) {
103                                $newestDiff = $matches[1][ $ii ];
104                                $newestDiffTime = $time;
105                        }
106                }
107
108                if ( ! $newestDiffTime || $newestDiffTime >= strtotime( '2017-12-01 00:00:00' ) ) {
109                        continue;
110                }
111
112                shell_exec( "curl https://core.trac.wordpress.org/raw-$newestDiff > ../working/$ticket.diff" );
113        }
114
115        // git branch at the auto-fixer commit.
116        echo "Creating branch for Ticket #$ticket\n";
117        $output = shell_exec( "git branch -f $ticket $firstFixerCommit" );
118        if ( DEBUG_LEVEL ) {
119                echo $output;
120        }
121        $output = shell_exec( "git checkout $ticket" );
122        if ( DEBUG_LEVEL ) {
123                echo $output;
124        }
125
126        $diff = file_get_contents( "../working/$ticket.diff" );
127        $pLevel = 0;
128        foreach ( $pLevels as $level => $testStrings ) {
129                foreach ( $testStrings as $testString ) {
130                        if ( strpos( $diff, $testString ) !== false ) {
131                                $pLevel = $level;
132                                break 2;
133                        }
134                }
135        }
136
137        $target = '';
138        foreach ( $targets as $dir => $testStrings ) {
139                foreach ( $testStrings as $testString ) {
140                        if ( strpos( $diff, $testString ) !== false ) {
141                                $target = "--directory $dir";
142                                break 2;
143                        }
144                }
145        }
146
147        // Find files changed in the patch.
148        echo "Finding files that will be changed by this patch:\n";
149        $fileString = shell_exec( "git apply --numstat -p$pLevel ../working/$ticket.diff $target | awk '{print $3}'" );
150        echo $fileString;
151
152        $files = explode( "\n", $fileString );
153        $fileParams = implode( ' ', $files );
154
155        // Undo changed files to the change before the auto-fixer commit, if those files exist.
156        $existingFiles = array_filter( $files, 'file_exists' );
157        if ( $existingFiles ) {
158                echo "Reverting patch files to previous commit.\n";
159                $output = shell_exec( "git checkout $firstFixerCommit~1 -- " . implode( ' ', $existingFiles ) );
160                if ( DEBUG_LEVEL ) {
161                        echo $output;
162                }
163        }
164
165        // Apply the patch file.
166        echo "Applying patch.\n";
167        $output = shell_exec( "git apply -p$pLevel ../working/$ticket.diff $target 2>&1" );
168        if ( DEBUG_LEVEL ) {
169                echo $output;
170        }
171
172        if ( strpos( $output, 'error: ' ) !== false ) {
173                continue;
174        }
175
176        // Install original WPCS/PHPCS versions (source: https://github.com/WordPress/wordpress-develop/pull/8/files#diff-1da2c7edc898c70e5a79a9997c98cecc).
177        echo "Installing composer dependences.\n";
178        shell_exec( 'rm -rf vendor' );
179        $originalDependences = array(
180                'squizlabs/PHP_CodeSniffer:dev-master#506feeeb6753b9904a165366dd78077b2879adb7',
181                'wp-coding-standards/wpcs:dev-feature/new-multi-line-comment-formatting-sniffs#ce4d719296ebbecd01f57bc3729833e89b39ad99',
182                'dealerdirect/phpcodesniffer-composer-installer:0.4.3',
183        );
184        $output = shell_exec( 'composer require ' . implode( ' ', $originalDependences ) );
185        if ( DEBUG_LEVEL ) {
186                echo $output;
187        }
188
189        // Grab the original phpcs.xml.dist, which wasn't included in the firt auto-fixer commit.
190        $output = shell_exec( 'git checkout 81a48c2924bb1c1cd8ae5752397766f133950e18 -- phpcs.xml.dist' );
191        if ( DEBUG_LEVEL ) {
192                echo $output;
193        }
194
195        // Run phpcbf on patch files.
196        echo "Running code formatter on the patch files.\n";
197        $output = shell_exec( "vendor/bin/phpcbf $fileParams" );
198        if ( DEBUG_LEVEL ) {
199                echo $output;
200        }
201
202        // Remove the PHPCS/composer files that we had to add.
203        echo shell_exec( 'git rm -f phpcs.xml.dist' );
204        $output = shell_exec( 'rm composer.json composer.lock' );
205        if ( DEBUG_LEVEL ) {
206                echo $output;
207        }
208
209        // Commit the changed files.
210        echo "Committing formatted patch files.\n";
211        $output = shell_exec( "git add $fileParams" );
212        if ( DEBUG_LEVEL ) {
213                echo $output;
214        }
215        $output = shell_exec( 'git commit -m "Apply phpcbf to patch files"' );
216        if ( DEBUG_LEVEL ) {
217                echo $output;
218        }
219
220        foreach ( $fixerCommits as $commit ) {
221                // Merge up to the commit before the next fixer commit
222                // For merge conflicts, assume ours (with the patch) is correct
223                echo "Merging up to the commit before $commit\n";
224                $output = shell_exec( "git merge --strategy-option ours $commit~1 -m 'Merge changes before fixer commit $commit'" );
225                if ( DEBUG_LEVEL > 1 ) {
226                        echo $output;
227                }
228
229                $mergeCommit = trim( shell_exec( 'git rev-parse HEAD' ) );
230
231                // Merge the next fixer commit. Assume that we're correct for merge conflicts.
232                echo "Merging fixer commit $commit\n";
233                $output = shell_exec( "git merge --strategy-option ours $commit -m 'Merge fixer commit git merge $commit'" );
234                if ( DEBUG_LEVEL > 1 ) {
235                        echo $output;
236                }
237
238                // Upgrade composer dependences.
239                echo "Upgrading composer dependences\n";
240                shell_exec( 'rm -rf vendor' );
241                $output = shell_exec( 'composer install' );
242                if ( DEBUG_LEVEL ) {
243                        echo $output;
244                }
245
246                // Revert the patched files to the previous commit.
247                echo "Reverting patch files to previous commit.\n";
248                $output = shell_exec( "git checkout $mergeCommit -- $fileParams" );
249                if ( DEBUG_LEVEL ) {
250                        echo $output;
251                }
252
253                // Run phpcbf on patch files.
254                echo "Running code formatter on the patch files.\n";
255                $output = shell_exec( "vendor/bin/phpcbf $fileParams" );
256                if ( DEBUG_LEVEL ) {
257                        echo $output;
258                }
259
260                // Commit the changed files.
261                echo "Committing formatted patch files.\n";
262                $output = shell_exec( "git add $fileParams" );
263                if ( DEBUG_LEVEL ) {
264                        echo $output;
265                }
266                $output = shell_exec( 'git commit -m "Apply phpcbf to patch files"' );
267                if ( DEBUG_LEVEL ) {
268                        echo $output;
269                }
270        }
271
272        // Merge the rest of master.
273        echo "Merging the rest of master.\n";
274        $output = shell_exec( 'git merge --strategy-option ours master -m "Merge the rest of master"' );
275        if ( DEBUG_LEVEL > 1 ) {
276                echo $output;
277        }
278
279        // Create patch.
280        $output = shell_exec( "git diff master..$ticket > ../working/$ticket-automerged.diff" );
281        if ( DEBUG_LEVEL ) {
282                echo $output;
283        }
284
285        // Delete branch
286        if ( ! DEBUG_LEVEL ) {
287                shell_exec( 'git checkout master' );
288                shell_exec( "git branch -D $ticket" );
289        }
290
291        if ( UPLOAD_PATCH ) {
292                $diffContent = base64_encode( file_get_contents( "../working/$ticket-automerged.diff" ) );
293
294                $attachmentRequest = "<?xml version=\"1.0\"? >
295                <methodCall>
296                        <methodName>ticket.putAttachment</methodName>
297                        <params>
298                                <param><int>$ticket</int></param>
299                                <param><string>$ticket.diff</string></param>
300                                <param><string>Auto-refreshed patch</string></param>
301                                <param><base64>$diffContent</base64></param>
302                                <param><boolean>0</boolean></param>
303                        </params>
304                </methodCall>";
305
306                $conn = curl_init( 'https://core.trac.wordpress.org/login/xmlrpc' );
307
308                if ( DEBUG_LEVEL ) {
309                        curl_setopt( $conn, CURLOPT_VERBOSE, 1 );
310                }
311
312                curl_setopt( $conn, CURLOPT_RETURNTRANSFER, 1 );
313                curl_setopt( $conn, CURLOPT_USERAGENT, 'Patch Auto Refresher' );
314                curl_setopt( $conn, CURLOPT_HTTPHEADER, array( 'Content-Type: application/xml; charset=utf-8' ) );
315                curl_setopt( $conn, CURLOPT_POST, 1 );
316                curl_setopt( $conn, CURLOPT_POSTFIELDS, $attachmentRequest );
317
318                curl_setopt( $conn, CURLOPT_USERPWD, WPORG_USER . ':' . WPORG_PASSWORD );
319
320                $response = curl_exec( $conn );
321                if ( DEBUG_LEVEL ) {
322                        echo $response;
323                }
324        }
325}