diff --git a/README.md b/README.md index 3684ec2..67f5eaf 100644 --- a/README.md +++ b/README.md @@ -55,20 +55,27 @@ Commands, in the order they'll be useful: ### Patching ### - drush bandaid-patch [project path] + drush bandaid-patch [project path] Project path is the path to the project you want to patch. Optional if you are issuing the command from the module's or projects's directory. Example: - drush bandaid-patch https://drupal.org/files/issues/panels-new-pane-alter-1985980-5.patch sites/all/modules/contrib/panels + drush bandaid-patch https://www.drupal.org/node/1985980#comment-8596585 sites/all/modules/contrib/panels -Will patch the module with the given patch, and if successful, ask for -the URL of the issue, and pop up your editor for a reason for patching -(to remind your future you why you did this in the first place). This -information will be written to a .yml file next to the module -directory. You can edit the yaml file if the need be, but be aware -that it's used by the following commands. +Will patch the module with the patch from the fifth comment (cid +8596585), and if successful pop up your editor for a reason for +patching (to remind your future you why you did this in the first +place). This information will be written to a .yml file next to the +module directory. You can edit the yaml file if the need be, but be +aware that it's used by the following commands. + +For issue urls, the "home" of the patch is automatically set to the +issue url, for urls pointing directly to the patch, it will ask the +you. + +If supplied an issue url that doesn't point to a specific comment, +it'll list the found patches and ask which to use. If you don't like the interactive questions, these can be supplied with the `--home` and `--reason` options. diff --git a/bandaid.drush.inc b/bandaid.drush.inc index 88eb0a7..b4915aa 100755 --- a/bandaid.drush.inc +++ b/bandaid.drush.inc @@ -14,6 +14,7 @@ require 'bandaid.inc'; use Bandaid\Git; use Bandaid\BandaidError; use Symfony\Component\Yaml\Yaml; +use Goutte\Client; define('BANDAID_MINIMUM_PHP', '5.3.0'); // Cache for a week. Patches shouldn't change. define('BANDAID_CACHE_LIFETIME_DEFAULT', 604800); @@ -161,6 +162,51 @@ function drush_bandaid_patch($patch = NULL, $project = NULL) { } chdir($project['dir']); + if (preg_match('{drupal.org/node/\\d+(#comment-(\\d+))?}', $patch, $rx)) { + drush_log(dt("Looks like a Drupal issue url, looking for patches."), 'status'); + $patches = _bandaid_get_patches_from_issue($patch); + if (empty($patches)) { + throw new BandaidError('NO_PATCH', dt('No patches found on issue.')); + } + + // If the URL points to a comment, try to find that specific patch. + if (!empty($rx[2])) { + if (isset($patches[$rx[2]])) { + $selected_patch = $patches[$rx[2]]; + } + else { + throw new BandaidError('NO_PATCH', dt('No patch found on comment @cid.', array('@cid' => $rx[2]))); + } + } + else { + if (count($patches) > 1) { + $options = array(); + foreach ($patches as $issue_patch) { + $options[$issue_patch['cid']] = '#' . $issue_patch['num'] . ' ' . $issue_patch['href']; + } + $selected = drush_choice($options, dt("Please select patch.")); + if (isset($patches[$selected])) { + $selected_patch = $patches[$selected]; + } + else { + return drush_user_abort(); + } + } + else { + drush_log(dt("Found one patch."), 'status'); + $selected_patch = reset($patches); + } + } + + if ($selected_patch) { + // Use the issue URL as home, if it is not already set. + if (!drush_get_option('home', NULL)) { + drush_set_option('home', $patch); + } + $patch = $selected_patch['href']; + } + } + // @todo this was wholesomely copied and adjusted from _bandaid_patch, need // to refactor things together again. $filename = _bandaid_download_file($patch); @@ -919,3 +965,45 @@ function _bandaid_overwrite_project($work_dir, $project_dir) { } drush_register_file_for_deletion($project_dir . '.old'); } + +/** + * Extract patch urls from a Drupal issue URL. + */ +function _bandaid_get_patches_from_issue($url) { + + // $patches = array(); + $client = new Client(); + $crawler = $client->request('GET', $url); + $patches = $crawler->filter('#extended-file-field-table-field-issue-files tr')->each(function ($row) use (&$patches) { + $cid = 0; + $num = 'node'; + $x = $row->filter('td.extended-file-field-table-cid a'); + if (count($x)) { + if (preg_match('/^\\s*#(\\d+)\\s*$/', $x->text(), $rx)) { + $num = $rx[1]; + } + if (preg_match('/#comment-(\\d+)$/', $x->attr('href'), $rx)) { + $cid = $rx[1]; + } + } + $hrefs = $row->filter('td.extended-file-field-table-filename a')->each(function ($node){ + return $node->attr('href'); + }); + if ($hrefs[0]) { + return array( + 'num' => $num, + 'cid' => $cid, + 'href' => $hrefs[0], + ); + } + }); + $return = array(); + // Index the patches. + foreach ($patches as $patch) { + if (!empty($patch['num'])) { + $return[$patch['cid']] = $patch; + } + } + ksort($return); + return $return; +} diff --git a/composer.json b/composer.json index 3fff006..ed445c2 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ ], "require": { "php": ">=5.3.0", - "symfony/yaml": "~2.2" + "symfony/yaml": "~2.2", + "fabpot/goutte": "1.0.6" } } diff --git a/tests/bandaidTest.php b/tests/bandaidTest.php index fa5daf9..b51bb61 100644 --- a/tests/bandaidTest.php +++ b/tests/bandaidTest.php @@ -526,3 +526,66 @@ public function testVersionParsing() { } } } + +class BandaidIssuePatchesCase extends UnitUnishTestCase { + /** + * Setup. Load command file. + */ + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + require_once dirname(__DIR__) . '/bandaid.drush.inc'; + // Trigger loading of vendor libs. + bandaid_drush_command(); + } + /** + * Test that parsing patches from issue URLs work. + */ + public function testIssueParsing() { + $tests = array( + 'https://www.drupal.org/node/2242071' => array( + 0 => array( + 'num' => 'node', + 'cid' => 0, + 'href' => 'https://www.drupal.org/files/issues/add-git-check-ignore-option.patch', + ), + 8792979 => array( + 'num' => '2', + 'cid' => '8792979', + 'href' => 'https://www.drupal.org/files/issues/add-git-check-ignore-options-with-drush-scan-directory.patch', + ), + 8793767 => array( + 'num' => '3', + 'cid' => '8793767', + 'href' => 'https://www.drupal.org/files/issues/drush_situs-git-check-ignore-2242071-3.patch', + ), + 8795059 => array( + 'num' => '9', + 'cid' => '8795059', + 'href' => 'https://www.drupal.org/files/issues/drush_situs-git-check-ignore-2242071-9.patch', + ), + ), + + 'https://www.drupal.org/node/1433906' => array( + 0 => array( + 'num' => 'node', + 'cid' => 0, + 'href' => 'https://www.drupal.org/files/devel-support.patch', + ), + ), + + 'https://www.drupal.org/node/2133205' => array( + 8164173 => array( + 'num' => '1', + 'cid' => '8164173', + 'href' => 'https://www.drupal.org/files/issues/drush_situs-Also_ignore_README-2133205-1.patch', + ), + ), + ); + + foreach ($tests as $url => $parsed) { + $res = _bandaid_get_patches_from_issue($url); + $this->assertEquals($parsed, $res); + } + + } +}