updatecode.pm.inc

  1. 8.0.x commands/pm/updatecode.pm.inc
  2. 6.x commands/pm/updatecode.pm.inc
  3. 7.x commands/pm/updatecode.pm.inc
  4. 3.x commands/pm/updatecode.pm.inc
  5. 4.x commands/pm/updatecode.pm.inc
  6. 5.x commands/pm/updatecode.pm.inc
  7. master commands/pm/updatecode.pm.inc

Functions

Namesort descending Description
drush_pm_updatecode Command callback. Displays update status info and allows to update installed projects. Pass specific projects as arguments, otherwise we update all that have candidate releases.
drush_pm_updatecode_rollback
pm_get_release Get the a best release match for a requested update.
pm_project_filter
pm_release_recommended Set a release to a recommended version (if available), and set as updateable.
pm_update_complete Run the post-update hooks after updatecode is complete for one project.
pm_update_packages Update packages according to an array of releases, following interactive confirmation from the user.
pm_update_project Update one project -- a module, theme or Drupal core
_pm_update_core Update drupal core, following interactive confirmation from the user.
_pm_update_move_files Move some files from one location to another

File

commands/pm/updatecode.pm.inc
View source
  1. <?php
  2. /**
  3. * Command callback. Displays update status info and allows to update installed projects.
  4. * Pass specific projects as arguments, otherwise we update all that have candidate releases.
  5. *
  6. * This command prompts for confirmation before updating, so it is safe to run just to check on
  7. * In this case, say at the confirmation prompt.
  8. */
  9. function drush_pm_updatecode() {
  10. // We don't provide for other options here, so we supply an explicit path.
  11. drush_include_engine('update_info', 'drupal', NULL, DRUSH_BASE_PATH . '/commands/pm/update_info');
  12. // Get update status information.
  13. $projects = _pm_get_update_info();
  14. // Get specific requests
  15. $requests = func_get_args();
  16. // Parse out project name and version
  17. $requests = pm_parse_project_version($requests);
  18. // Preprocess releases
  19. if (!empty($requests)) {
  20. // Force update projects where a specific version is reqested
  21. foreach ($requests as $name => $request) {
  22. if (!isset($projects[$name])) {
  23. // Catch projects with no version data (common for CVS checkouts
  24. // if you don't have CVS deploy installed).
  25. $projects[$name] = array(
  26. 'title' => $name,
  27. 'existing_version' => 'Unknown',
  28. 'status'=> DRUSH_PM_NO_VERSION,
  29. );
  30. }
  31. else if (!empty($request['version'])) {
  32. // Match the requested release
  33. $release = pm_get_release($request, $projects[$name]);
  34. if (!$release) {
  35. $projects[$name]['status'] = DRUSH_PM_REQUESTED_NOT_FOUND;
  36. }
  37. else if ($release['version'] == $projects[$name]['existing_version']) {
  38. $projects[$name]['status'] = DRUSH_PM_REQUESTED_CURRENT;
  39. }
  40. else {
  41. $projects[$name]['status'] = DRUSH_PM_REQUESTED_UPDATE;
  42. }
  43. // Set the candidate version to the requested release
  44. $projects[$name]['candidate_version'] = $release['version'];
  45. }
  46. }
  47. }
  48. // Table headers.
  49. $rows[] = array(dt('Name'), dt('Installed version'), dt('Proposed version'), dt('Status'));
  50. // Process releases, notifying user of status and building a list of proposed updates
  51. $updateable = pm_project_filter($projects, $rows);
  52. // Pipe preparation
  53. if (drush_get_context('DRUSH_PIPE')) {
  54. $pipe = "";
  55. foreach($projects as $project){
  56. $pipe .= $project['name']. " ";
  57. $pipe .= $project['existing_version']. " ";
  58. $pipe .= $project['candidate_version']. " ";
  59. $pipe .= str_replace(' ', '-', pm_update_filter($project)). "\n";
  60. }
  61. drush_print_pipe($pipe);
  62. // Automatically curtail update process if in pipe mode
  63. $updateable = FALSE;
  64. }
  65. $last = pm_update_last_check();
  66. drush_print(dt('Update information last refreshed: ') . ($last ? format_date($last) : dt('Never')));
  67. drush_print();
  68. drush_print(dt("Update status information on all installed and enabled Drupal projects:"));
  69. drush_print_table($rows, TRUE);
  70. drush_print();
  71. // If specific project updates were requested then remove releases for all others
  72. if (!empty($requests)) {
  73. foreach ($updateable as $name => $project) {
  74. if (!isset($requests[$name])) {
  75. unset($updateable[$name]);
  76. }
  77. }
  78. }
  79. if (isset($updateable['drupal'])) {
  80. $drupal_project = $updateable['drupal'];
  81. unset($projects['drupal']);
  82. unset($updateable['drupal']);
  83. $module_list = array_keys($projects);
  84. // We can only upgrade drupal core if there are no non-core
  85. // modules enabled. _pm_update_core will disable the
  86. // modules passed in, and insure that they are enabled again
  87. // when we're done. However, each run of pm-updatecode will
  88. // update -either- core, or the non-core modules; never both.
  89. // This simplifies rollbacks.
  90. if (empty($updateable)) {
  91. return _pm_update_core($drupal_project, $module_list);
  92. }
  93. // If there are modules other than drupal core enabled, then go
  94. // ahead and print instructions on how to upgrade core. We will
  95. // also continue, allowing the user to upgrade any upgradable
  96. // modules if desired.
  97. else {
  98. drush_print(dt("NOTE: A code update for the Drupal core is available."));
  99. if (drush_get_context('DRUSH_PM_UPDATE_ALL', FALSE)) {
  100. drush_print(dt("Drupal core will be updated after all of the non-core modules are updated.\n"));
  101. }
  102. else {
  103. drush_print(dt("Drupal core cannot be updated at the same time that non-core modules are updated. In order to update Drupal core with this tool, first allow the update of the modules listed above to complet, and then run pm-updatecode again.\n"));
  104. }
  105. drush_set_context('DRUSH_PM_CORE_UPDATE_AVAILABLE', TRUE);
  106. }
  107. }
  108. if (empty($updateable)) {
  109. return drush_log(dt('No code updates available.'), 'ok');
  110. }
  111. // Offer to update to the identified releases
  112. return pm_update_packages($updateable);
  113. }
  114. /**
  115. * Update drupal core, following interactive confirmation from the user.
  116. *
  117. * @param $project
  118. * The drupal project information from the drupal.org update service,
  119. * copied from $projects['drupal']. @see drush_pm_updatecode.
  120. * @param $module_list
  121. * A list of the non-core modules that are enabled. These must be disabled
  122. * before core can be updated.
  123. */
  124. function _pm_update_core(&$project, $module_list = array()) {
  125. drush_include_engine('package_handler', drush_get_option('package-handler', 'wget'));
  126. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  127. drush_print(dt('Code updates will be made to drupal core.'));
  128. drush_print(dt("WARNING: Updating core will discard any modifications made to Drupal core files, most noteworthy among these are .htaccess and robots.txt. If you have made any modifications to these files, please back them up before updating so that you can re-create your modifications in the updated version of the file."));
  129. drush_print(dt("Note: Updating core can potentially break your site. It is NOT recommended to update production sites without prior testing."));
  130. if(!drush_confirm(dt('Do you really want to continue?'))) {
  131. drush_die('Aborting.');
  132. }
  133. // Create a directory 'core' if it does not already exist
  134. $project['path'] = 'drupal-' . $project['candidate_version'];
  135. $project['full_project_path'] = $drupal_root . '/' . $project['path'];
  136. if (!is_dir($project['full_project_path'])) {
  137. mkdir($project['full_project_path']);
  138. }
  139. // Create a list of files and folders that are user-customized or otherwise
  140. // not part of the update process
  141. $project['skip_list'] = array('backup', 'sites', $project['path']);
  142. // Move all files and folders in $drupal_root to the new 'core' directory
  143. // except for the items in the skip list
  144. _pm_update_move_files($drupal_root, $project['full_project_path'], $project['skip_list']);
  145. // Set a context variable to indicate that rollback should reverse
  146. // the _pm_update_move_files above.
  147. drush_set_context('DRUSH_PM_DRUPAL_CORE', $project);
  148. if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
  149. return FALSE;
  150. }
  151. // Make a list of every item at the root of core except 'sites'
  152. $items_to_test = drush_scan_directory($project['full_project_path'], '/.*/', array('.', '..', 'sites', '.svn'), 0, FALSE, 'basename', 0, TRUE);
  153. $project['base_project_path'] = dirname($project['full_project_path']);
  154. // Check we have a version control system, and it clears its pre-flight.
  155. if (!$version_control->pre_update($project, $items_to_test)) {
  156. return FALSE;
  157. }
  158. // Update core.
  159. if (pm_update_project($project, $version_control) === FALSE) {
  160. return FALSE;
  161. }
  162. // Take the updated files in the 'core' directory that have been updated,
  163. // and move all except for the items in the skip list back to
  164. // the drupal root
  165. _pm_update_move_files($project['full_project_path'], $drupal_root, $project['skip_list']);
  166. drush_delete_dir($project['full_project_path']);
  167. // If there is a backup target, then find items
  168. // in the backup target that do not exist at the
  169. // drupal root. These are to be moved back.
  170. if (array_key_exists('backup_target', $project)) {
  171. _pm_update_move_files($project['backup_target'], $drupal_root, $project['skip_list'], FALSE);
  172. _pm_update_move_files($project['backup_target'] . '/profiles', $drupal_root . '/profiles', array('default'), FALSE);
  173. }
  174. pm_update_complete($project, $version_control);
  175. return TRUE;
  176. }
  177. /**
  178. * Move some files from one location to another
  179. */
  180. function _pm_update_move_files($src_dir, $dest_dir, $skip_list, $remove_conflicts = TRUE) {
  181. $items_to_move = drush_scan_directory($src_dir, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'filename', 0, TRUE);
  182. foreach ($items_to_move as $filename => $info) {
  183. if ($remove_conflicts) {
  184. drush_delete_dir($dest_dir . '/' . basename($filename));
  185. }
  186. if (!file_exists($dest_dir . '/' . basename($filename))) {
  187. $move_result = rename($filename, $dest_dir . '/' . basename($filename));
  188. }
  189. }
  190. return TRUE;
  191. }
  192. /**
  193. * Update packages according to an array of releases, following interactive
  194. * confirmation from the user.
  195. *
  196. * @param $projects
  197. * An array of projects from the drupal.org update service, with an additional
  198. * array key candidate_version that specifies the version to be installed.
  199. */
  200. function pm_update_packages($projects) {
  201. drush_include_engine('package_handler', drush_get_option('package-handler', 'wget'));
  202. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  203. drush_print(dt('Code updates will be made to the following projects:'));
  204. foreach($projects as $project) {
  205. $print .= $project['title'] . " [" . $project['name'] . '-' . $project['candidate_version'] . "], ";
  206. }
  207. drush_print(substr($print, 0, strlen($print)-2));
  208. drush_print();
  209. drush_print(dt("Note: Updated projects can potentially break your site. It is NOT recommended to update production sites without prior testing."));
  210. drush_print(dt("Note: A backup of your package will be stored to backups directory if it is not managed by a supported version control system."));
  211. drush_print(dt('Note: If you have made any modifications to any file that belongs to one of these projects, you will have to migrate those modifications after updating.'));
  212. if(!drush_confirm(dt('Do you really want to continue?'))) {
  213. drush_die('Aborting.');
  214. }
  215. // Now we start the actual updating.
  216. foreach($projects as $project) {
  217. if (empty($project['path'])) {
  218. return drush_set_error('DRUSH_PM_UPDATING_NO_PROJECT_PATH', dt('The !project project path is not available, perhaps the !type is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'])));
  219. }
  220. drush_log(dt('Starting to update !project code at !dir...', array('!project' => $project['title'], '!dir' => $project['path'])));
  221. // Create the projects directory and base (parent) directory.
  222. $project['full_project_path'] = $drupal_root . '/' . $project['path'];
  223. // Check that the directory exists, and is where we expect it to be.
  224. if (stripos($project['path'], $project['project_type']) === FALSE || !is_dir($project['full_project_path'])) {
  225. return drush_set_error('DRUSH_PM_UPDATING_PATH_NOT_FOUND', dt('The !project directory could not be found within the !types directory at !full_project_path, perhaps the project is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'], '!full_project_path' => $project['full_project_path'])));
  226. }
  227. if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
  228. return FALSE;
  229. }
  230. $project['base_project_path'] = dirname($project['full_project_path']);
  231. // Check we have a version control system, and it clears its pre-flight.
  232. if (!$version_control->pre_update($project)) {
  233. return FALSE;
  234. }
  235. // Run update on one project
  236. if (pm_update_project($project, $version_control) === FALSE) {
  237. return FALSE;
  238. }
  239. pm_update_complete($project, $version_control);
  240. }
  241. // Clear the cache, since some projects could have moved around.
  242. drush_drupal_cache_clear_all();
  243. }
  244. /**
  245. * Update one project -- a module, theme or Drupal core
  246. *
  247. * @param $project
  248. * The project to upgrade. $project['full_project_path'] must be set
  249. * to the location where this project is stored.
  250. */
  251. function pm_update_project($project, $version_control) {
  252. // Add the project to a context so we can roll back if needed.
  253. $updated = drush_get_context('DRUSH_PM_UPDATED');
  254. $updated[] = $project;
  255. drush_set_context('DRUSH_PM_UPDATED', $updated);
  256. if (!package_handler_update_project($project, $project['releases'][$project['candidate_version']])) {
  257. return drush_set_error('DRUSH_PM_UPDATING_FAILED', dt('Updating project !project failed. Attempting to roll back to previously installed version.', array('!project' => $project['name'])));
  258. }
  259. return TRUE;
  260. }
  261. /**
  262. * Run the post-update hooks after updatecode is complete for one project.
  263. */
  264. function pm_update_complete($project, $version_control) {
  265. drush_print(dt('Project !project was updated successfully. Installed version is now !version.', array('!project' => $project['name'], '!version' => $project['candidate_version'])));
  266. drush_command_invoke_all('pm_post_update', $project['name'], $project['releases'][$project['candidate_version']]);
  267. $version_control->post_update($project);
  268. }
  269. function drush_pm_updatecode_rollback() {
  270. $projects = array_reverse(drush_get_context('DRUSH_PM_UPDATED', array()));
  271. foreach($projects as $project) {
  272. drush_log(dt('Rolling back update of !project code ...', array('!project' => $project['title'])));
  273. // Check we have a version control system, and it clears it's pre-flight.
  274. if (!$version_control = drush_pm_include_version_control($project['path'])) {
  275. return FALSE;
  276. }
  277. $version_control->rollback($project);
  278. }
  279. // Post rollback, we will do additional repair if the project is drupal core.
  280. $drupal_core = drush_get_context('DRUSH_PM_DRUPAL_CORE');
  281. if (isset($drupal_core)) {
  282. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  283. _pm_update_move_files($drupal_core['full_project_path'], $drupal_root, $drupal_core['skip_list']);
  284. drush_delete_dir($drupal_core['full_project_path']);
  285. }
  286. }
  287. function pm_project_filter(&$projects, &$rows) {
  288. $updateable = array();
  289. foreach ($projects as $key => $project) {
  290. if (empty($project['title'])) {
  291. continue;
  292. }
  293. switch($project['status']) {
  294. case DRUSH_PM_REQUESTED_UPDATE:
  295. $status = dt('Specified version available');
  296. $project['updateable'] = TRUE;
  297. break;
  298. case DRUSH_PM_REQUESTED_CURRENT:
  299. $status = dt('Specified version already installed');
  300. break;
  301. case DRUSH_PM_NO_VERSION:
  302. $status = dt('No version information found (if you have a CVS checkout you should install CVS Deploy module)');
  303. break;
  304. case DRUSH_PM_REQUESTED_NOT_FOUND:
  305. $status = dt('Specified version not found');
  306. break;
  307. default:
  308. $status = pm_update_filter($project);
  309. break;
  310. }
  311. // Persist candidate_version in $projects (plural).
  312. if (empty($project['candidate_version'])) {
  313. $projects[$key]['candidate_version'] = $project['existing_version']; // Default to no change
  314. }
  315. else {
  316. $projects[$key]['candidate_version'] = $project['candidate_version'];
  317. }
  318. if (!empty($project['updateable'])) {
  319. $updateable[$key] = $project;
  320. }
  321. $rows[] = array($project['title'], $project['existing_version'], $projects[$key]['candidate_version'], $status);
  322. }
  323. return $updateable;
  324. }
  325. /**
  326. * Set a release to a recommended version (if available), and set as updateable.
  327. */
  328. function pm_release_recommended(&$project) {
  329. if (isset($project['recommended'])) {
  330. $project['candidate_version'] = $project['recommended'];
  331. $project['updateable'] = TRUE;
  332. }
  333. }
  334. /**
  335. * Get the a best release match for a requested update.
  336. *
  337. * @param $request A information array for the requested project
  338. * @param $project A project information array for this project, as returned by an update service from pm_get_project_info()
  339. */
  340. function pm_get_release($request, $project) {
  341. $minor = '';
  342. $version_patch_changed = '';
  343. if ($request['version']) {
  344. // The user specified a specific version - try to find that exact version
  345. foreach($project['releases'] as $version => $release) {
  346. // Ignore unpublished releases.
  347. if ($release['status'] != 'published') {
  348. continue;
  349. }
  350. // Straight match
  351. if (!isset($recommended_version) && $release['version'] == $request['version']) {
  352. $recommended_version = $version;
  353. }
  354. }
  355. }
  356. else {
  357. // No version specified - try to find the best version we can
  358. foreach($project['releases'] as $version => $release) {
  359. // Ignore unpublished releases.
  360. if ($release['status'] != 'published') {
  361. continue;
  362. }
  363. // If we haven't found a recommended version yet, put the dev
  364. // version as recommended and hope it gets overwritten later.
  365. // Look for the 'latest version' if we haven't found it yet.
  366. // Latest version is defined as the most recent version for the
  367. // default major version.
  368. if (!isset($latest_version) && $release['version_major'] == $project['default_major']) {
  369. $latest_version = $version;
  370. }
  371. if (!isset($recommended_version) && $release['version_major'] == $project['default_major']) {
  372. if ($minor != $release['version_patch']) {
  373. $minor = $release['version_patch'];
  374. $version_patch_changed = $version;
  375. }
  376. if (empty($release['version_extra']) && $minor == $release['version_patch']) {
  377. $recommended_version = $version_patch_changed;
  378. }
  379. continue;
  380. }
  381. }
  382. }
  383. if (isset($recommended_version)) {
  384. return $project['releases'][$recommended_version];
  385. }
  386. else if (isset($latest_version)) {
  387. return $project['releases'][$latest_version];
  388. }
  389. else {
  390. return false;
  391. }
  392. }