From 0f913413c91805dfd02b24022517e39b0673072e Mon Sep 17 00:00:00 2001 From: Andreas Hennings Date: Thu, 28 Jul 2016 22:53:20 +0200 Subject: Docblock types --- git_deploy.module | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git_deploy.module b/git_deploy.module index e35ff7c..1a6bfa4 100644 --- a/git_deploy.module +++ b/git_deploy.module @@ -11,11 +11,11 @@ * * We support populating $info['version'] and $info['project']. * - * @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) { -- 1.9.1 From 1bc0a5f4a16753f21b7cba5ef8c514e73b0c3c24 Mon Sep 17 00:00:00 2001 From: Andreas Hennings Date: Thu, 28 Jul 2016 22:55:39 +0200 Subject: Refactor and split git_deploy_system_info_alter(). --- git_deploy.module | 212 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 151 insertions(+), 61 deletions(-) diff --git a/git_deploy.module b/git_deploy.module index 1a6bfa4..b02937a 100644 --- a/git_deploy.module +++ b/git_deploy.module @@ -19,69 +19,159 @@ * Can be module or theme. */ function git_deploy_system_info_alter(&$info, $file, $type = NULL) { - if (empty($info['version'])) { - $directory = dirname($file->uri); - // Check whether this belongs to core. Speed optimization. - if (substr($directory, 0, strlen($type)) != $type) { - while ($directory && !file_exists("$directory/.git")) { - $directory = substr($directory, 0, strrpos($directory, '/')); - } - $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; - } - // 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)); - } - // 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; - } - } - } - } - 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 (!empty($info['version'])) { + return; + } + + // Check whether this belongs to core. Speed optimization. + if (0 === strpos($file->uri, $type)) { + return; + } + + $directory = dirname($file->uri); + + while (TRUE) { + if (!$directory || '.' === $directory) { + return; + } + if (file_exists("$directory/.git")) { + break; + } + $directory = dirname($directory); + } + + $info = _git_deploy_get_info_from_git_dir("$directory/.git") + $info; +} + +/** + * Extracts enhancements to a module info array from a module's git repository. + * + * @param string $git_dir + * E.g. 'sites/all/modules/git/views/.git' + * + * @return array + * E.g. ['project' => 'views', 'version' => '3.x-dev', 'datestamp' => ..] + */ +function _git_deploy_get_info_from_git_dir($git_dir) { + static $cache = array(); + return isset($cache[$git_dir]) + ? $cache[$git_dir] + : $cache[$git_dir] = _git_deploy_read_info_from_git_dir($git_dir); +} + +/** + * @param string $git_dir + * E.g. 'sites/all/modules/git/views/.git' + * + * @return array + */ +function _git_deploy_read_info_from_git_dir($git_dir) { + + $info = array(); + + // 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. + if (NULL !== $fetch_url = _git_deploy_exec_grep($git_dir, 'remote show -n origin', '/^\s*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; + } + + // Try to fill in branch and tag. + $last_tag = ''; + if (NULL !== $branch = _git_deploy_exec_get_line($git_dir, 'rev-parse --abbrev-ref HEAD')) { + + // 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)); + } + + // Now try to find a tag. + if (NULL !== $last_tag_hash = _git_deploy_exec_get_line($git_dir, 'rev-list --topo-order --max-count=1 HEAD')) { + if (NULL !== $last_tag_candidate = _git_deploy_exec_get_line($git_dir, "describe --tags $last_tag_hash")) { + // 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_candidate, $matches)) { + $info['version'] = isset($matches[2]) + ? $matches[1] . '.' . $matches[3] . 'dev' + : $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. + if (NULL !== $datestamp = _git_deploy_exec_get_line($git_dir, "log -1 --pretty=format:%at $last_tag")) { + if (is_numeric($datestamp)) { + $info['datestamp'] = $datestamp; + } + } + + return $info; +} + +/** + * Executes a git command, and returns the first line of the output. + * + * @param string $git_dir + * E.g. 'status' for 'git status'. + * @param string $cmd + * The git directory for + * + * @return string|null + * The line of the output, or NULL if output was empty. + */ +function _git_deploy_exec_get_line($git_dir, $cmd) { + $output_lines = _git_deploy_exec_get_all($git_dir, $cmd); + return isset($output_lines[0]) ? $output_lines[0] : NULL; +} + +/** + * Executes a git command, and returns the (first) line of the output that + * matches the regex pattern. + * + * @param string $git_dir + * @param string $cmd + * E.g. 'status' for 'git status'. + * @param string $preg_grep_regex + * + * @return string|null + * Line of the output that matches the pattern, or NULL if no match. + */ +function _git_deploy_exec_grep($git_dir, $cmd, $preg_grep_regex) { + + $output_lines = _git_deploy_exec_get_all($git_dir, $cmd); + + if (array() !== $output_lines) { + if (array() !== $matches = preg_grep('/^\s*Fetch URL:/', $output_lines)) { + return current($matches); + } + } + + return NULL; +} + +/** + * Executes a git command, and returns all lines of the output. + * + * @param string $git_dir + * @param string $cmd + * E.g. 'status' for 'git status'. + * + * @return string[] + * Lines of the output, as provided by exec(). + */ +function _git_deploy_exec_get_all($git_dir, $cmd) { + exec("git --git-dir $git_dir $cmd 2>&1", $output_lines); + return $output_lines; } -- 1.9.1 From 425a91847f86aa4f6aa8bea8ea01c8257f21ec53 Mon Sep 17 00:00:00 2001 From: Andreas Hennings Date: Thu, 28 Jul 2016 22:57:12 +0200 Subject: Use escapeshellarg() --- git_deploy.module | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git_deploy.module b/git_deploy.module index b02937a..f598ffb 100644 --- a/git_deploy.module +++ b/git_deploy.module @@ -172,6 +172,7 @@ function _git_deploy_exec_grep($git_dir, $cmd, $preg_grep_regex) { * Lines of the output, as provided by exec(). */ function _git_deploy_exec_get_all($git_dir, $cmd) { - exec("git --git-dir $git_dir $cmd 2>&1", $output_lines); + $git_dir_safe = escapeshellarg($git_dir); + exec("git --git-dir $git_dir_safe $cmd 2>&1", $output_lines); return $output_lines; } -- 1.9.1 From 07f49931b376b1d9f5be037f5984a06e8485c942 Mon Sep 17 00:00:00 2001 From: Andreas Hennings Date: Fri, 29 Jul 2016 00:24:17 +0200 Subject: Change doc comment on git_deploy_system_info_alter() --- git_deploy.module | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git_deploy.module b/git_deploy.module index f598ffb..7c6e83b 100644 --- a/git_deploy.module +++ b/git_deploy.module @@ -7,9 +7,10 @@ */ /** - * Implement hook_system_info_alter() to provide metadata to drupal from git. + * Implements 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 array $info * The module/theme info array we're altering. -- 1.9.1 From fc293beeb89e60f34b206051e42bd5e5f10b155a Mon Sep 17 00:00:00 2001 From: Andreas Hennings Date: Fri, 29 Jul 2016 00:27:15 +0200 Subject: Introduce _git_deploy_get_info_from_directory() to replace _git_deploy_get_info_from_git_dir(). Static cache per directory instead of per git dir. --- git_deploy.module | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/git_deploy.module b/git_deploy.module index 7c6e83b..0a4023d 100644 --- a/git_deploy.module +++ b/git_deploy.module @@ -29,35 +29,29 @@ function git_deploy_system_info_alter(&$info, $file, $type = NULL) { return; } - $directory = dirname($file->uri); - - while (TRUE) { - if (!$directory || '.' === $directory) { - return; - } - if (file_exists("$directory/.git")) { - break; - } - $directory = dirname($directory); + if (array() !== $extracted_info = _git_deploy_get_info_from_directory(dirname($file->uri))) { + $info = $extracted_info + $info; } - - $info = _git_deploy_get_info_from_git_dir("$directory/.git") + $info; } /** - * Extracts enhancements to a module info array from a module's git repository. - * - * @param string $git_dir - * E.g. 'sites/all/modules/git/views/.git' + * @param string $directory + * E.g. 'sites/all/modules/contrib/views' * * @return array - * E.g. ['project' => 'views', 'version' => '3.x-dev', 'datestamp' => ..] */ -function _git_deploy_get_info_from_git_dir($git_dir) { - static $cache = array(); - return isset($cache[$git_dir]) - ? $cache[$git_dir] - : $cache[$git_dir] = _git_deploy_read_info_from_git_dir($git_dir); +function _git_deploy_get_info_from_directory($directory) { + static $static = array('.' => array(), '' => array()); + + if (isset($static[$directory])) { + return $static[$directory]; + } + + if (file_exists("$directory/.git/HEAD")) { + return $static[$directory] = _git_deploy_read_info_from_git_dir("$directory/.git"); + } + + return $static[$directory] = _git_deploy_get_info_from_directory(dirname($directory)); } /** -- 1.9.1 From 43b04751e2317035a11fd4319b622d1c125a4c64 Mon Sep 17 00:00:00 2001 From: Andreas Hennings Date: Fri, 29 Jul 2016 00:34:20 +0200 Subject: Introduce persistent cache, as in #1511112-48 --- README.txt | 10 +++ git_deploy.inc | 1 + git_deploy.install | 74 ++++++++++++++++++++- git_deploy.module | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 268 insertions(+), 4 deletions(-) create mode 100644 git_deploy.inc diff --git a/README.txt b/README.txt index 98d181e..c4ca752 100644 --- a/README.txt +++ b/README.txt @@ -6,3 +6,13 @@ 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 this 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.inc b/git_deploy.inc new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/git_deploy.inc @@ -0,0 +1 @@ + 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_enable(). + */ +function git_deploy_enable() { + $weight = db_select('system', 's') + ->fields('s', array('weight')) + ->condition('s.type', 'module') + ->condition('s.name', 'git_deploy', '<>') + ->orderBy('s.weight') + ->orderBy('s.name') + ->range(0, 1) + ->execute() + ->fetchColumn(); + + db_update('system') + ->fields(array('weight' => $weight - 1)) + ->condition('type', 'module') + ->condition('name', 'git_deploy') + ->execute(); +} + +/** + * 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 0a4023d..58cd46a 100644 --- a/git_deploy.module +++ b/git_deploy.module @@ -48,13 +48,38 @@ function _git_deploy_get_info_from_directory($directory) { } if (file_exists("$directory/.git/HEAD")) { - return $static[$directory] = _git_deploy_read_info_from_git_dir("$directory/.git"); + return $static[$directory] = _git_deploy_get_info_from_git_dir("$directory/.git"); } return $static[$directory] = _git_deploy_get_info_from_directory(dirname($directory)); } /** + * Extracts enhancements to a module info array from a module's git repository. + * + * @param string $git_dir + * E.g. 'sites/all/modules/git/views/.git' + * + * @return array + * E.g. ['project' => 'views', 'version' => '3.x-dev', 'datestamp' => ..] + */ +function _git_deploy_get_info_from_git_dir($git_dir) { + + $head_hash = trim(file_get_contents($git_dir . '/HEAD')); + $cache_id = "git_deploy:$git_dir:$head_hash"; + + if ($cache = _git_deploy_cache_get($cache_id)) { + return $cache->data; + } + + $info = _git_deploy_read_info_from_git_dir($git_dir); + + _git_deploy_cache_set($cache_id, $info); + + return $info; +} + +/** * @param string $git_dir * E.g. 'sites/all/modules/git/views/.git' * @@ -171,3 +196,163 @@ function _git_deploy_exec_get_all($git_dir, $cmd) { exec("git --git-dir $git_dir_safe $cmd 2>&1", $output_lines); return $output_lines; } + +/** + * Returns cache object from the cache_git_deploy cache bin. + * + * If the cache data is not exists in the local cache bin, then attempt to read + * from the shared file cache. + * + * @param string $cache_id + * Cache identifier. + * + * @return object|false + * 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 and file cache. + * + * @param string $cache_id + * Cache identifier. + * @param array $cache_data + * Array of system info additions. + */ +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. + * + * WebServer writable files can not be trusted and sending untrusted data + * through unserialize() is dangerous. + * + * @see _git_deploy_file_cache_write() + * @see _git_deploy_file_cache_unserialize() + * + * @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 _git_deploy_file_cache_unserialize($cache_data); + } + } + + return NULL; +} + +/** + * Write cache data into the shared file based cache. + * + * @see _git_deploy_file_cache_read() + * + * @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_path = $directory . '/' . _git_deploy_file_cache_name($cache_id); + if (file_exists($file_path)) { + return TRUE; + } + + $is_directory = (is_dir($directory) || file_prepare_directory($directory, FILE_CREATE_DIRECTORY)); + $is_writable = (!file_exists($file_path) || is_writable($file_path)); + if ($is_directory && $is_writable) { + return @file_put_contents($file_path, _git_deploy_file_cache_serialize($cache_data)); + } + } + + return FALSE; +} + +/** + * Prepare cache data to write into a shared file. + * + * @see _git_deploy_file_cache_unserialize() + * + * @param array $cache_data + * System info additions. + * + * @return string + * Serialized cache data. + */ +function _git_deploy_file_cache_serialize($cache_data) { + $text = ''; + foreach ($cache_data as $key => $value) { + $text .= "$key=$value\n"; + } + + return $text; +} + +/** + * Parse the serialized cache data. + * + * @see _git_deploy_file_cache_serialize() + * + * @param string $text + * Serialized cache data. + * + * @return array + * System info additions. + */ +function _git_deploy_file_cache_unserialize($text) { + $cache_data = array(); + $keys = array('version', 'project', 'datetime'); + foreach (explode("\n", trim($text)) as $line) { + list($key, $value) = explode('=', $line); + if (in_array($key, $keys)) { + $cache_data[$key] = check_plain($value); + } + } + + return $cache_data; +} + +/** + * Implements hook_flush_caches(). + */ +function git_deploy_flush_caches() { + return array('cache_git_deploy'); +} -- 1.9.1