diff --git a/README.txt b/README.txt index 98d181e..a44a087 100644 --- a/README.txt +++ b/README.txt @@ -6,3 +6,12 @@ An alternative to using git_deploy is to install modules and themes using the --gitinfofile". This performs a git clone and checkout and then inserts the desired version information into the .info file. The "drush make" command will automatically write packaging information without additional options. + +If you use this module for several sites on the same machine, you can make use of the +shared file cache by adding this or something like it to your settings file: + +$conf['git_deploy_file_cache_directory'] = '/tmp/git_deploy'; + +The git status is also saved in a (site-specific) cache bin called +cache_git_deploy, in case you want to use an alternative backend for your +particular instance. diff --git a/git_deploy.install b/git_deploy.install index eb66c85..5123c71 100644 --- a/git_deploy.install +++ b/git_deploy.install @@ -1,13 +1,54 @@ t('git binary'), - 'value' => t('The git binary is present and executable'), + $requirements['git_deploy_git_binary'] = array( + 'title' => t('Git binary'), + 'value' => t('The Git binary is present and executable'), 'severity' => function_exists('exec') && exec('git') ? REQUIREMENT_OK : REQUIREMENT_ERROR, ); + + if ($directory = variable_get('git_deploy_file_cache_directory')) { + $requirements['git_deploy_file_cache_directory'] = array( + 'title' => t('Git deploy - Shared cache directory'), + 'value' => t('The shared Git deploy temporary directory is exists and writable.'), + 'severity' => is_dir($directory) && is_writable($directory) ? REQUIREMENT_OK : REQUIREMENT_ERROR, + ); + } } + return $requirements; } + +/** + * Implements hook_uninstall(). + */ +function git_deploy_uninstall() { + variable_del('git_deploy_file_cache_directory'); +} + +/** + * Implements hook_schema(). + */ +function git_deploy_schema() { + $table = drupal_get_schema_unprocessed('system', 'cache'); + $table['description'] = 'Cache table for git deploy to store exec command results.'; + $schema = array('cache_git_deploy' => $table); + + return $schema; +} + +/** + * Add a separate cache table. + */ +function git_deploy_update_7001() { + if (!db_table_exists('cache_git_deploy')) { + $table = drupal_get_schema_unprocessed('system', 'cache'); + $table['description'] = 'Cache table for git deploy to store exec command results.'; + db_create_table('cache_git_deploy', $table); + } +} diff --git a/git_deploy.module b/git_deploy.module index e35ff7c..bf469dd 100644 --- a/git_deploy.module +++ b/git_deploy.module @@ -2,24 +2,26 @@ /** * @file - * * This module add versioning information to projects checked out of git. */ /** - * Implement hook_system_info_alter() to provide metadata to drupal from git. + * Implement hook_system_info_alter(). * - * We support populating $info['version'] and $info['project']. + * Provide metadata to drupal from git. We support populating $info['version'], + * $info['project'] and $info['datestamp']. * - * @param $info + * @param array $info * The module/theme info array we're altering. - * @param $file + * @param object $file * An object describing the filesystem location of the module/theme. - * @param $type + * @param string $type * Can be module or theme. */ function git_deploy_system_info_alter(&$info, $file, $type = NULL) { - if (empty($info['version'])) { + static $additions = array(); + + if (!array_key_exists($file->uri, $additions) && empty($info['version'])) { $directory = dirname($file->uri); // Check whether this belongs to core. Speed optimization. if (substr($directory, 0, strlen($type)) != $type) { @@ -29,59 +31,184 @@ function git_deploy_system_info_alter(&$info, $file, $type = NULL) { $git_dir = "$directory/.git"; // Theoretically /.git could exist. if ($directory && file_exists($git_dir)) { - $git = "git --git-dir $git_dir"; - // Find first the project name based on fetch URL. - // Eat error messages. >& is valid on Windows, too. Also, $output does - // not need initialization because it's taken by reference. - exec("$git remote show -n origin 2>&1", $output); - if ($fetch_url = preg_grep('/^\s*Fetch URL:/', $output)) { - $fetch_url = current($fetch_url); - $project_name = substr($fetch_url, strrpos($fetch_url, '/') + 1); - if (substr($project_name, -4) == '.git') { - $project_name = substr($project_name, 0, -4); - } - $info['project'] = $project_name; + // Git executable with basic parameters. + $git = 'git --git-dir ' . escapeshellarg($git_dir); + + // Commit hash is required for the cache id. + exec("$git rev-parse --verify HEAD 2>&1", $head_hash); + + // Cannot live more than one project (profile, module, theme, + // theme_engine) with same name in one Drupal instance, but this cache + // id is usable for more Drupal instance too. + $cache_id = "git_deploy:{$file->name}:{$head_hash[0]}"; + + if ($cache = _git_deploy_cache_get($cache_id)) { + $additions[$file->uri] = $cache->data; } - // Try to fill in branch and tag. - exec("$git rev-parse --abbrev-ref HEAD 2>&1", $branch); - $tag_found = FALSE; - if ($branch) { - $branch = $branch[0]; - // Any Drupal-formatted branch. - $branch_preg = '\d+\.x-\d+\.'; - if (preg_match('/^' . $branch_preg . 'x$/', $branch)) { - $info['version'] = $branch . '-dev'; - // Nail down the core and the major version now that we know - // what they are. - $branch_preg = preg_quote(substr($branch, 0, -1)); + else { + // Find first the project name based on fetch URL. + // Eat error messages. >& is valid on Windows, too. Also, $output does + // not need initialization because it's taken by reference. + exec("$git remote show -n origin 2>&1", $output); + if ($fetch_url = preg_grep('/^\s*Fetch URL:/', $output)) { + $fetch_url = current($fetch_url); + $project_name = substr($fetch_url, strrpos($fetch_url, '/') + 1); + if (substr($project_name, -4) == '.git') { + $project_name = substr($project_name, 0, -4); + } + $additions[$file->uri]['project'] = $project_name; } - // Now try to find a tag. - exec("$git rev-list --topo-order --max-count=1 HEAD 2>&1", $last_tag_hash); - if ($last_tag_hash) { - exec("$git describe --tags $last_tag_hash[0] 2>&1", $last_tag); - if ($last_tag) { - $last_tag = $last_tag[0]; - // Make sure the tag starts as Drupal formatted (for eg. - // 7.x-1.0-alpha1) and if we are on a proper branch (ie. not - // master) then it's on that branch. - if (preg_match('/^(' . $branch_preg . '\d+(?:-[^-]+)?)(-(\d+-)g[0-9a-f]{7})?$/', $last_tag, $matches)) { - $tag_found = TRUE; - $info['version'] = isset($matches[2]) ? $matches[1] . '.' . $matches[3] . 'dev' : $last_tag; + + // Try to fill in branch and tag. + exec("$git rev-parse --abbrev-ref HEAD 2>&1", $branch); + $tag_found = FALSE; + $last_tag = ''; + if ($branch) { + $branch = $branch[0]; + // Any Drupal-formatted branch. + $branch_preg = '\d+\.x-\d+\.'; + if (preg_match('/^' . $branch_preg . 'x$/', $branch)) { + $additions[$file->uri]['version'] = $branch . '-dev'; + // Nail down the core and the major version now that we know + // what they are. + $branch_preg = preg_quote(substr($branch, 0, -1)); + } + // Now try to find a tag. + exec("$git rev-list --topo-order --max-count=1 HEAD 2>&1", $last_tag_hash); + if ($last_tag_hash) { + exec("$git describe --tags $last_tag_hash[0] 2>&1", $last_tag); + if ($last_tag) { + $last_tag = $last_tag[0]; + // Make sure the tag starts as Drupal formatted (for eg. + // 7.x-1.0-alpha1) and if we are on a proper branch (ie. not + // master) then it's on that branch. + if (preg_match('/^(' . $branch_preg . '\d+(?:-[^-]+)?)(-(\d+-)g[0-9a-f]{7})?$/', $last_tag, $matches)) { + $tag_found = TRUE; + $additions[$file->uri]['version'] = isset($matches[2]) ? $matches[1] . '.' . $matches[3] . 'dev' : $last_tag; + } } } } - } - if (!$tag_found) { - $last_tag = ''; - } - // The git log -1 command always succeeds and if we are not on a - // tag this will happen to return the time of the last commit which - // is exactly what we wanted. - exec("$git log -1 --pretty=format:%at $last_tag 2>&1", $datestamp); - if ($datestamp && is_numeric($datestamp[0])) { - $info['datestamp'] = $datestamp[0]; + + if (!$tag_found) { + $last_tag = ''; + } + // The git log -1 command always succeeds and if we are not on a + // tag this will happen to return the time of the last commit which + // is exactly what we wanted. + exec("$git log -1 --pretty=format:%at $last_tag 2>&1", $datestamp); + if ($datestamp && is_numeric($datestamp[0])) { + $additions[$file->uri]['datestamp'] = $datestamp[0]; + } + + // Save the values into cache. + _git_deploy_cache_set($cache_id, $additions[$file->uri]); } } } } + + if (array_key_exists($file->uri, $additions)) { + foreach ($additions[$file->uri] as $key => $value) { + $info[$key] = $value; + } + } +} + +/** + * Returns cache object from the cache_git_deploy cache bin. + * + * @param string $cache_id + * Cache identifier. + * + * @return object|bool + * Cache object or FALSE. + */ +function _git_deploy_cache_get($cache_id) { + $cache_bin = 'cache_git_deploy'; + $cache_object = cache_get($cache_id, $cache_bin); + if (empty($cache_object)) { + $cache_data = _git_deploy_file_cache_read($cache_id); + if ($cache_data) { + cache_set($cache_id, $cache_data, $cache_bin); + $cache_object = cache_get($cache_id, $cache_bin); + } + } + + return $cache_object; +} + +/** + * Save $info additions into cache_git_deploy cache bin. + * + * @param string $cache_id + * Cache identifier. + * @param array $cache_data + */ +function _git_deploy_cache_set($cache_id, $cache_data) { + cache_set($cache_id, $cache_data, 'cache_git_deploy'); + _git_deploy_file_cache_write($cache_id, $cache_data); +} + +/** + * Convert cache id to file name. + * + * @param string $cache_id + * Cache identifier. + * + * @return string + * File name. + */ +function _git_deploy_file_cache_name($cache_id) { + return str_replace(array(':', '/'), '-', $cache_id); +} + +/** + * Read cache data from the shared file based cache. + * + * @param string $cache_id + * Cache identifier. + * + * @return array|null + * Cache data. System info additions. + */ +function _git_deploy_file_cache_read($cache_id) { + if ($directory = variable_get('git_deploy_file_cache_directory', '')) { + $file_name = str_replace(array(':', '/'), '-', $cache_id); + if (is_readable("$directory/$file_name") && ($cache_data = @file_get_contents("$directory/$file_name"))) { + return unserialize($cache_data); + } + } + + return NULL; +} + +/** + * Write cache data into the shared file based cache. + * + * @param string $cache_id + * Cache identifier. + * @param array $cache_data + * System info additions. + * + * @return int|bool + * Returns the number of bytes that were written to the file, or FALSE on + * failure. + */ +function _git_deploy_file_cache_write($cache_id, $cache_data) { + if ($directory = variable_get('git_deploy_file_cache_directory', '')) { + $file_name = _git_deploy_file_cache_name($cache_id); + if (is_dir($directory) || file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) { + return file_put_contents("$directory/$file_name", serialize($cache_data)); + } + } + + return FALSE; +} + +/** + * Implements hook_flush_caches(). + */ +function git_deploy_flush_caches() { + return array('cache_git_deploy'); }