git_deploy.module in Git Deploy 7
Same filename and directory in other branches
Adds project, version and date information to projects checked out with Git.
This module provides metadata about modules installed via Git for Drupal's updates listing. As it is intended to provide the same functionality as CVS Deploy but for Git modules, this module is modeled after CVS Deploy, which was written by Derek Wright (dww).
The intention of this module is to support the commit tagging styles displayed at http://git.drupalcode.org/, which is a picture of how Drupal modules will look after conversion to Git. This tagging convention is incompatible with the CVS tagging convention.
File
git_deploy.moduleView source
<?php
/**
* @file
* Adds project, version and date information to projects checked out with Git.
*
* This module provides metadata about modules installed via Git for Drupal's
* updates listing. As it is intended to provide the same functionality as CVS
* Deploy but for Git modules, this module is modeled after CVS Deploy, which
* was written by Derek Wright (dww).
*
* The intention of this module is to support the commit tagging styles
* displayed at http://git.drupalcode.org/, which is a picture of how Drupal
* modules will look after conversion to Git. This tagging convention is
* incompatible with the CVS tagging convention.
*/
/**
* Force git_deploy to be loaded when checking update requirements.
*
* @see git_deploy_boot()
*/
if (basename($_SERVER['PHP_SELF']) == 'update.php' || function_exists('update_main')) {
$GLOBALS['module_list']['git_deploy']['filename'] = __FILE__;
}
/**
* Implements hook_boot().
*/
function git_deploy_boot() {
// This function doesn't do anything, but because it exists, Git Deploy gets
// loaded before update.php resets the module list. That allows us to make
// sure Git Deploy is in the new module list.
}
/**
* Implements hook_system_info_alter().
*/
function git_deploy_system_info_alter(&$info, $file, $type) {
// Use drupal_static() so we can free up memory from static variables later.
$projects =& drupal_static(__FUNCTION__ . ':projects', array());
$available =& drupal_static(__FUNCTION__ . ':available');
$update =& drupal_static(__FUNCTION__ . ':update', array());
// Include the glip library.
if (module_exists('libraries')) {
require_once libraries_get_path('glip') . '/lib/glip.php';
}
else {
require_once variable_get('git_deploy_glip_path', 'sites/all/libraries/glip') . '/lib/glip.php';
}
// Core has hard-coded version numbers, so we need to verify them. Otherwise,
// a valid version number indicates that this project was not installed with
// Git.
$is_core = isset($info['package']) && strpos($info['package'], 'Core') === 0;
if (empty($info['hidden']) && (empty($info['version']) || ($is_core ? strstr($info['version'], '-dev') == '-dev' : $info['version'] == VERSION || !preg_match('/^7\\.x-\\d+\\..+/', $info['version'])))) {
// Verify that we are in a Git repository.
$path_components = explode('/', preg_replace('/^' . preg_quote(DRUPAL_ROOT . '/', '/') . '/', '', realpath($file->uri), 1));
$path = array_pop($path_components);
while (!empty($path_components) && !file_exists(implode('/', $path_components) . '/.git')) {
$path = array_pop($path_components) . "/{$path}";
}
$path_components[] = '.git';
$directory = implode('/', $path_components);
// Handle submodules.
if (is_file($directory)) {
$gitdir = str_replace('gitdir: ', '', trim(current(file($directory))));
$directory = preg_replace('/^' . preg_quote(DRUPAL_ROOT . '/', '/') . '/', '', realpath(dirname($directory) . "/{$gitdir}"), 1);
}
if (file_exists($directory)) {
// Only check Git once per repository.
if (!isset($projects[$directory])) {
$projects[$directory] = array();
// Ensure file is in repository.
$git = new Git($directory);
if (@$git
->getObject($git
->revParse())
->getTree()
->find($path)) {
// Get Git configuration.
$git_config = parse_ini_file($directory . '/config', TRUE);
// Get upstream info.
$upstream = _git_deploy_get_upstream($git, $is_core ? '7' : '7.x-*', $git_config);
if ($is_core) {
$project_name = 'drupal';
}
elseif (isset($upstream['remote']) && $git_config) {
// Find the project name based on fetch URL.
$remote = isset($git_config["remote {$upstream['remote']}"]) ? "remote {$upstream['remote']}" : current(preg_grep('/^remote\\s.+/', array_keys($git_config)));
if (!empty($remote)) {
$project_name = basename($git_config[$remote]['url'], '.git');
$projects[$directory]['project'] = $project_name;
}
}
// Set project datestamp.
if (isset($upstream['datestamp'])) {
$projects[$directory]['datestamp'] = $upstream['datestamp'];
// The '_info_file_ctime' should always get the latest value.
if (empty($info['_info_file_ctime'])) {
$projects[$directory]['_info_file_ctime'] = $upstream['datestamp'];
}
else {
$projects[$directory]['_info_file_ctime'] = max($info['_info_file_ctime'], $upstream['datestamp']);
}
}
// Set version from tag.
if (isset($upstream['tag'])) {
$projects[$directory]['version'] = $upstream['tag'];
}
elseif (isset($upstream['branch'])) {
if ($upstream['branch'] != 'master') {
$projects[$directory]['version'] = "{$upstream['branch']}-dev";
}
if (module_exists('update') && ($upstream['synced'] || $upstream['branch'] == 'master')) {
if (!isset($available)) {
// We only get this from the cache because an update status
// query is supposed to done only after processing all enabled
// modules and themes.
$available = _update_get_cached_available_releases();
}
if (!empty($available[$project_name]['releases'])) {
if ($upstream['branch'] == 'master') {
// If there's available update_status data, we can use the
// version string the release node pointing to HEAD really
// has.
foreach ($available[$project_name]['releases'] as $release) {
if (isset($release['tag']) && $release['tag'] == 'HEAD') {
$projects[$directory]['version'] = $release['version'];
break;
}
}
}
if ($upstream['synced']) {
// This project's datestamp needs to be synced with upstream
// release.
$version = $projects[$directory]['version'];
if (!isset($available[$project_name]['releases'][$version]) || $projects[$directory]['_info_file_ctime'] > $available[$project_name]['last_fetch']) {
// We need to update available release data for this
// project.
$update[] = $project_name;
}
else {
_git_deploy_datestamp_sync($projects[$directory], $available[$project_name]['releases'][$version]);
}
}
}
}
}
}
}
$info = $projects[$directory] + $info;
}
}
}
/**
* Gets upstream info.
*
* @param Git $git
* A Git instance.
* @param string $pattern
* Pattern for matching branch names, without trailing ".x". Must not include
* repository name. Also used to check for release tags.
* @param array|false $git_config
* Git configuration if found.
*
* @return string[]
* Array with the following keys, if found:
* - branch: Best matching remote branch.
* - remote: Remote repository containing best matching branch.
* - tag: Release tag from last common commit in matching branch.
* - datestamp: Unix timestamp of last common commit.
*/
function _git_deploy_get_upstream(Git $git, $pattern = '*', $git_config = FALSE) {
$pattern = str_replace(array(
'.',
'*',
), array(
'\\.',
'\\d+',
), $pattern);
$upstream = array();
// List references in descending version order.
$refs = $git
->getrefs();
uksort($refs, 'version_compare');
$refs = array_reverse($refs);
// List remote branches matching pattern. Make special exception for master.
// If origin exists, don't check any other remote repositories.
$remotes = preg_grep("/^refs\\/remotes\\/origin\\/(?:{$pattern}\\.x|master)\$/", array_keys($refs));
if (empty($remotes)) {
$remotes = preg_grep("/^refs\\/remotes\\/.+\\/(?:{$pattern}\\.x|master)\$/", array_keys($refs));
}
$remotes = array_intersect_key($refs, array_flip($remotes));
if (!empty($remotes)) {
list(, , $upstream['remote']) = explode('/', key($remotes), 4);
// List tags matching pattern.
$tags = array_intersect_key($refs, array_flip(preg_grep("/^refs\\/tags\\/{$pattern}\\.\\d+.*/", array_keys($refs))));
// Check for tag on current commit.
$githash = _git_deploy_tag($git, $tags, array(
$git
->revParse(),
), $upstream);
if (!isset($upstream['tag'])) {
// Check if we are tracking a valid remote branch.
$head = trim(current(file($git->dir . '/HEAD')));
if (strpos($head, 'ref: refs/heads/') === 0) {
// Get local branch name.
$head = str_replace('ref: refs/heads/', '', $head);
if (!empty($git_config) && isset($git_config["branch {$head}"]['remote']) && isset($git_config["branch {$head}"]['merge'])) {
// Get remote branch name.
$ref = str_replace('refs/heads/', 'refs/remotes/' . $git_config["branch {$head}"]['remote'] . '/', $git_config["branch {$head}"]['merge']);
if (isset($remotes[$ref])) {
$local = _git_deploy_base($git, array(
$ref => $remotes[$ref],
), $githash, $upstream);
// If most recent common commit is not the current commit, check for
// tag on most recent common commit.
if (reset($local) != $githash) {
$githash = _git_deploy_tag($git, $tags, $local, $upstream);
}
}
}
}
// If we are not tracking a valid remote branch, find best best matching
// remote branch.
if (!isset($upstream['branch'])) {
$local = _git_deploy_base($git, $remotes, $githash, $upstream);
// If most recent common commit is not the current commit, check for tag
// on most recent common commit.
if (reset($local) != $githash) {
$githash = _git_deploy_tag($git, $tags, $local, $upstream);
}
}
}
// Find the timestamp for the current commit.
$upstream['datestamp'] = @$git
->getObject($githash)->committer->time;
}
return $upstream;
}
/**
* Checks for most recent local commit in a set of remote branches.
*
* @param Git $git
* A Git instance.
* @param string[] $remotes
* Remote branch commit hashes keyed by reference, in descending order.
* @param string $githash
* Local commit hash.
* @param array $upstream
* Things we need to know to determine status.
*
* @return string[]
* Local history starting with most recent common commit hash.
*/
function _git_deploy_base(Git $git, array $remotes, $githash, array &$upstream) {
$local_queue = array(
@$git
->getObject($githash),
);
$local = $remote = array();
foreach ($remotes as $ref => $hash) {
if ($hash == $githash) {
// If remote branch matches local branch, it is the best match.
$local = array(
$githash,
);
list(, , $upstream['remote'], $upstream['branch']) = explode('/', $ref, 4);
// Local history contains last remote commit.
$upstream['synced'] = TRUE;
break;
}
elseif (in_array($hash, $remote)) {
// Last remote branch was more recent. For performance, stop looking.
break;
}
elseif (in_array($hash, $local)) {
// This remote branch is more recent.
$githash = $hash;
list(, , $upstream['remote'], $upstream['branch']) = explode('/', $ref, 4);
// Local history contains last remote commit.
$upstream['synced'] = TRUE;
}
else {
// Find most recent common commit.
$remote_queue = array(
@$git
->getObject($hash),
);
$remote = array();
// See if remote commit has been found in local history.
while ($commit = array_pop($remote_queue)) {
// Add remote commit to remote history.
array_unshift($remote, $commit
->getName());
if (!in_array($commit
->getName(), $local)) {
// Add parent commits to remote queue to be checked.
foreach ($commit->parents as $parent) {
// Only check parent if not already in remote history.
if (!in_array($parent, $remote)) {
$remote_queue[] = @$git
->getObject($parent);
}
}
// See if local commit has been found in remote history.
$commit = array_pop($local_queue);
// Add local commit to local history.
array_unshift($local, $commit
->getName());
if (!in_array($commit
->getName(), $remote)) {
// Add parent commits to local queue to be checked.
foreach ($commit->parents as $parent) {
// Only check parent if not already in local history.
if (!in_array($parent, $local)) {
$local_queue[] = @$git
->getObject($parent);
}
}
}
elseif (!isset($last_commit) || in_array($last_commit, $remote)) {
// Found most recent common commit in local history.
list(, , $upstream['remote'], $upstream['branch']) = explode('/', $ref, 4);
$last_commit = $githash = $commit
->getName();
break;
}
}
elseif (!isset($last_commit) || in_array($last_commit, $remote)) {
// Found most recent common commit in remote history.
list(, , $upstream['remote'], $upstream['branch']) = explode('/', $ref, 4);
$last_commit = $githash = $commit
->getName();
break;
}
}
// If we did not find a common commit, check rest of remote queue.
if (!in_array($githash, $remote)) {
while ($commit = array_pop($remote_queue)) {
// Add remote commit to remote history.
array_unshift($remote, $commit
->getName());
if (!in_array($commit
->getName(), $local)) {
// Add parent commits to remote queue to be checked.
foreach ($commit->parents as $parent) {
// Only check parent if not already in remote history.
if (!in_array($parent, $remote)) {
$remote_queue[] = @$git
->getObject($parent);
}
}
}
elseif (!isset($last_commit) || in_array($last_commit, $remote)) {
// Found most recent common commit in remote history.
list(, , $upstream['remote'], $upstream['branch']) = explode('/', $ref, 4);
$last_commit = $githash = $commit
->getName();
break;
}
}
}
elseif (!in_array($githash, $local)) {
while ($commit = array_pop($local_queue)) {
// Add local commit to local history.
array_unshift($local, $commit
->getName());
if (!in_array($commit
->getName(), $remote)) {
// Add parent commits to local queue to be checked.
foreach ($commit->parents as $parent) {
// Only check parent if not already in local history.
if (!in_array($parent, $local)) {
$local_queue[] = @$git
->getObject($parent);
}
}
}
elseif (!isset($last_commit) || in_array($last_commit, $remote)) {
// Found most recent common commit in local history.
list(, , $upstream['remote'], $upstream['branch']) = explode('/', $ref, 4);
$last_commit = $githash = $commit
->getName();
break;
}
}
}
// Look for last commit in local history for update status.
$upstream['synced'] = in_array($hash, $local);
}
}
return array_slice($local, array_search($githash, $local));
}
/**
* Checks for most recent tag in local history.
*
* @param Git $git
* A Git instance.
* @param string[] $tags
* Tag hashes keyed by reference, in descending order.
* @param string[] $local
* Local history starting with most recent common commit hash.
* @param array $upstream
* Things we need to know to determine status.
*/
function _git_deploy_tag(Git $git, array $tags, $local, array &$upstream) {
$githash = array_shift($local);
// Check for tags that do not exist on a remote branch.
$ref = key(array_intersect($tags, $local));
if (!empty($ref)) {
$upstream['tag'] = str_replace('refs/tags/', '', $ref);
$githash = $tags[$ref];
}
elseif (in_array($githash, $tags)) {
$upstream['tag'] = str_replace('refs/tags/', '', array_search($githash, $tags));
}
else {
// Check for annotated tag.
foreach ($tags as $ref => $hash) {
$tag = @$git
->getObject($hash);
if ($tag
->getType() == Git::OBJ_TAG) {
$hash = $tag->object;
// Check for tags that do not exist on a remote branch.
if (in_array($hash, $local)) {
$upstream['tag'] = str_replace('refs/tags/', '', $ref);
$githash = $hash;
break;
}
elseif ($hash == $githash) {
$upstream['tag'] = $tag->tag;
break;
}
}
}
}
return $githash;
}
/**
* Implements hook_update_projects_alter().
*/
function git_deploy_update_projects_alter(&$projects) {
// Get projects whose datestamps need to be synced with upstream releases.
$update = drupal_static('git_deploy_system_info_alter:update', array());
// Free up memory.
drupal_static_reset('git_deploy_system_info_alter:projects');
drupal_static_reset('git_deploy_system_info_alter:available');
drupal_static_reset('git_deploy_system_info_alter:update');
if (!empty($update)) {
// Fetch updated release data.
module_load_include('inc', 'update', 'update.fetch');
foreach ($update as $project_name) {
_update_process_fetch_task($projects[$project_name]);
}
$available = _update_get_cached_available_releases();
foreach ($update as $project_name) {
$project =& $projects[$project_name];
$version = $project['info']['version'];
if (isset($available[$project_name]['releases'][$version])) {
_git_deploy_datestamp_sync($project, $available[$project_name]['releases'][$version]);
}
}
}
}
/**
* Syncs the project datestamp to the release datestamp.
*
* @param array $project
* Project data. Datestamp will be synced if up to date with release.
* @param array $release
* Release data.
*/
function _git_deploy_datestamp_sync(array &$project, array $release) {
// We need to compare commit time to release time because our remote tracking
// branch could be out of date. Allow a 12 hour time difference between
// release and last commit, because dev releases are packaged only twice a
// day. Add a 100-second buffer to account for packaging time.
if ($project['datestamp'] + 43200 + 100 > $release['date']) {
// A dev release for the latest commit may be created later than an official
// release, so use release time only if it is later than the commit time.
$project['datestamp'] = max($release['date'], $project['datestamp']);
}
}
Functions
Name![]() |
Description |
---|---|
git_deploy_boot | Implements hook_boot(). |
git_deploy_system_info_alter | Implements hook_system_info_alter(). |
git_deploy_update_projects_alter | Implements hook_update_projects_alter(). |
_git_deploy_base | Checks for most recent local commit in a set of remote branches. |
_git_deploy_datestamp_sync | Syncs the project datestamp to the release datestamp. |
_git_deploy_get_upstream | Gets upstream info. |
_git_deploy_tag | Checks for most recent tag in local history. |