Function to disable and uninstall modules after deploying via Git

Public

Get raw version
php
  1. /**
  2.  * Disable and uninstall a module even if its files have been removed from Git.
  3.  *
  4.  * This checks out the old module files from Git temporarily so that the module
  5.  * can be safely disabled and uninstalled. It assumes that all the Drupal
  6.  * modules are tracked in Git, and that the Git repository's root directory is
  7.  * the same as Drupal's root.
  8.  *
  9.  * @param string $module_name
  10.  * The machine name of the module.
  11.  * @param bool $include_dependents
  12.  * If TRUE, dependent modules will automatically be included when disabling
  13.  * and uninstalling. Defaults to FALSE, because it is quicker that way, and
  14.  * because if you forget about any dependencies you won't want to disable them
  15.  * accidentally on deploy. Note that if you set or leave this as FALSE, you
  16.  * will need to call this function for all dependents in the right order.
  17.  * @param string $path
  18.  * The path to the directory containing the module's files (default:
  19.  * 'sites/all/modules/' . $module_name).
  20.  *
  21.  * @return bool
  22.  * TRUE if the module has been successfully disabled and uninstalled; TRUE if
  23.  * the module was already disabled before this function was run; FALSE
  24.  * otherwise.
  25.  */
  26. function deploy_purge_removed_module($module_name, $include_dependents = FALSE, $path = NULL) {
  27. // If the module was already disabled, we don't need to do anything.
  28. // Use db_query() instead of module_exists(), because the latter will usually
  29. // return FALSE merely because the module's files do not exist.
  30. $enabled = db_query("SELECT status FROM {system} WHERE name = :module AND type = 'module'", array(
  31. ':module' => $module_name,
  32. ))->fetchField();
  33. if (!$enabled) {
  34. return TRUE;
  35. }
  36.  
  37. // Make certain $path is within the root.
  38. if (strpos($path, '/') === 0 || strpos($path, '..') !== FALSE) {
  39. trigger_error('Can only act on files within DRUPAL_ROOT', E_USER_ERROR);
  40. return FALSE;
  41. }
  42.  
  43. // Get the path to the module relative to Drupal's root.
  44. if ($path === NULL) {
  45. $path = 'sites/all/modules/' . $module_name;
  46. }
  47. $drupal_path = $path;
  48. // Get the absolute path to the module.
  49. $absolute_path = DRUPAL_ROOT . '/' . $drupal_path;
  50.  
  51. // If the module's files do not exist, get them from the Git history.
  52. if (!file_exists($absolute_path)) {
  53. // Change the PHP working directory so Git commands work. N.B. this assumes
  54. // that the Drupal root is the same as the Git repository root.
  55. $chdir = chdir(DRUPAL_ROOT);
  56. if (!$chdir) {
  57. trigger_error('Failed to chdir to DRUPAL_ROOT', E_USER_ERROR);
  58. return FALSE;
  59. }
  60.  
  61. // Find the second-to-last commit which references the files.
  62. $history = shell_exec('git log --skip=1 -- ' . escapeshellarg($drupal_path));
  63. if (!$history) {
  64. trigger_error('Cannot read git history for removed module ' . $module_name, E_USER_ERROR);
  65. return FALSE;
  66. }
  67. preg_match('/^commit\s*([a-f0-9]+)\n/', $history, $matches);
  68. if (empty($matches[1])) {
  69. trigger_error('Cannot find any previous commits containing the files for removed module ' . $module_name, E_USER_ERROR);
  70. return FALSE;
  71. }
  72. $first_hash = $matches[1];
  73.  
  74. // Check out the files from that commit.
  75. $files_individually_checked_out = TRUE;
  76. shell_exec('git checkout ' . escapeshellarg($first_hash) . ' ' . escapeshellarg($drupal_path));
  77.  
  78. // Check that the files now exist.
  79. if (!file_exists($absolute_path)) {
  80. trigger_error('Files still do not exist for removed module ' . $module_name, E_USER_ERROR);
  81. return FALSE;
  82. }
  83. }
  84.  
  85. // Disable.
  86. if (module_exists($module_name)) {
  87. module_disable(array($module_name), $include_dependents);
  88. drupal_flush_all_caches();
  89. }
  90.  
  91. // Uninstall.
  92. $should_uninstall = module_load_install($module_name);
  93. if ($should_uninstall) {
  94. drupal_uninstall_modules(array($module_name), $include_dependents);
  95. }
  96.  
  97. // Remove the files that were added earlier.
  98. if ($files_individually_checked_out) {
  99. shell_exec('git reset HEAD ' . escapeshellarg($absolute_path));
  100. shell_exec('rm -r ' . escapeshellarg($absolute_path));
  101. }
  102. return TRUE;
  103. }

Comments

korius_bk's picture

Awesome code that is often needed. But did you have any problems with running git checkout? It's not checking out the files needed and it's because of the permissions.

This means that you have to set .git folder and sites/all/modules folder (or whatever it is) to a www-data group. This way it was working for me. Is this the same at your side, or did you find a way to solve this?