diff --git a/checkout/drush/provision_git_checkout.drush.inc b/checkout/drush/provision_git_checkout.drush.inc
index a0aa1c5..42c8d02 100644
--- a/checkout/drush/provision_git_checkout.drush.inc
+++ b/checkout/drush/provision_git_checkout.drush.inc
@@ -4,6 +4,10 @@
* Implementation of hook_drush_command().
*/
function provision_git_checkout_drush_command() {
+
+ $hooks = provision_git_hooks();
+ $available_hooks = implode(', ', array_keys($hooks));
+
$items['provision-git-checkout'] = array(
'description' => 'Git checkout a branch or tag in a specified location.',
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
@@ -11,8 +15,10 @@ function provision_git_checkout_drush_command() {
'git_ref' => dt('Parameter for the git checkout command'),
),
'options' => array(
- 'new_branch' => "Optional: If the branch doesn't exist, create it. Equivalent to git checkout -b branchname"
- )
+ 'new_branch' => "Optional: If the branch doesn't exist, create it. Equivalent to git checkout -b branchname",
+ 'hooks' => "Override the hooks to run after git checkout. Separate with a comma. Available hooks include ". $available_hooks
+
+ ),
);
return $items;
}
@@ -77,6 +83,10 @@ function drush_provision_git_checkout($git_ref = '') {
* Implements drush_hook_post_COMMAND().
*/
function drush_provision_git_checkout_post_provision_git_checkout() {
+
+ // Run all hooks on updated sites
+ provision_git_run_git_hooks();
+
// Re-verify the site, this corrects the file permission when necessary.
$options = array();
$target = d()->uri;
diff --git a/checkout/hosting_git_checkout.module b/checkout/hosting_git_checkout.module
index 4ff255f..14d07a6 100644
--- a/checkout/hosting_git_checkout.module
+++ b/checkout/hosting_git_checkout.module
@@ -74,6 +74,73 @@ function hosting_task_git_checkout_form($node) {
'#weight' => '-1',
'#default_value' => TRUE,
);
+ $form['skip_hooks'] = array(
+ '#title' => t('Skip git hooks'),
+ '#description' => t("Do not run git hooks after running git checkout.") . ' ' . t('Warning: This may result in an unstable site if a database update is required') . '',
+ '#type' => 'checkbox',
+ '#weight' => '0',
+ '#default_value' => FALSE,
+ '#access' => variable_get('hosting_git_allow_skip_hooks', FALSE),
+ );
+ $form['git_hooks'] = array(
+ '#title' => t('Git Hooks'),
+ '#weight' => '1',
+ '#type' => 'checkboxes',
+ '#default_value' => $node->git['git_hooks'],
+ '#access' => variable_get('hosting_git_allow_task_override_hooks', FALSE),
+ '#options' => hosting_git_get_hook_options(),
+ '#states' => array(
+ 'invisible' => array(
+ ':input[name="parameters[skip_hooks]"]' => array('checked' => TRUE),
+ ),
+ ),
+ '#element_validate' => array(
+ 'hosting_git_hooks_implode'
+ )
+ );
+
+ $form['hooks'] = array(
+ '#type' => 'container',
+ '#states' => array(
+ 'invisible' => array(
+ ':input[name="parameters[skip_hooks]"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ $hooks = module_invoke_all('hosting_git_hooks');
+ foreach ($hooks as $name => $hook) {
+ $name = ucfirst($name);
+ $items[] = " " . $hook['description'];
+ }
+ $form['hooks']['hooks_display'] = array(
+ '#type' => 'item',
+ '#title' => t('Git Hooks'),
+ '#markup' => theme('item_list', array('items' => $items)),
+ '#access' => !variable_get('hosting_git_allow_task_override_hooks', FALSE),
+ );
return $form;
}
+
+/**
+ * Element validate for the hooks field. Implode the string.
+ */
+function hosting_git_hooks_implode($element, &$form_state) {
+ if (variable_get('hosting_git_allow_task_override_hooks', FALSE)) {
+ $value = implode(',', $element['#value']);
+ if (empty($value)) {
+ $value = 'none';
+ }
+ }
+ else {
+ $value = NULL;
+ }
+
+ // if skip hooks is set, unset values for this element.
+ if ($form_state['values']['parameters']['skip_hooks']) {
+ $value = NULL;
+ }
+
+ form_set_value($element, $value, $form_state);
+}
\ No newline at end of file
diff --git a/drush/Provision/Service/git.php b/drush/Provision/Service/git.php
index aaf310d..b916047 100644
--- a/drush/Provision/Service/git.php
+++ b/drush/Provision/Service/git.php
@@ -19,6 +19,7 @@ class Provision_Service_git extends Provision_Service {
$context->setProperty('repo_url');
$context->setProperty('deploy_from_git');
$context->setProperty('git_ref');
+ $context->setProperty('git_hooks');
}
}
diff --git a/drush/provision_git.drush.inc b/drush/provision_git.drush.inc
index 37c88ae..f782e48 100644
--- a/drush/provision_git.drush.inc
+++ b/drush/provision_git.drush.inc
@@ -498,3 +498,136 @@ function provision_git_provision_services() {
provision_git_register_autoload();
return array('git' => NULL);
}
+
+/**
+ * Load all available git hooks.
+ * @return mixed
+ */
+function provision_git_hooks() {
+ $hooks = drush_command_invoke_all('provision_git_hooks');
+ return $hooks;
+}
+
+/**
+ * Implements hook_provision_git_hooks();
+ * @return array
+ */
+function provision_git_provision_git_hooks() {
+ return array(
+ 'update' => 'provision-update',
+ 'cache' => 'provision-flush_cache',
+ 'registry' => 'provision-rebuild_registry',
+ 'revert' => 'provision-features_revert_all',
+ );
+}
+
+/**
+ * Advertise what hooks are available to be called after a git pull or checkout.
+ *
+ * @return
+ * An associative array of type => drush command.
+ *
+ * @see provision.service.inc
+ */
+function hook_provision_git_hooks() {
+ return array(
+ 'update' => 'provision-update',
+ );
+}
+
+/**
+ * Run all drush commands listed in the 'git_hooks' property of the drush alias.
+ *
+ * If run when a platform is active,
+ */
+function provision_git_run_git_hooks() {
+
+ if (drush_get_option('skip-hooks', FALSE)) {
+ drush_log('Skipping git hooks, as requested. Be aware the site might need database updates.', 'ok');
+ return;
+ }
+
+ // Run post-git-pull hooks.
+ $sites = array();
+ if (d()->type == 'site') {
+ $sites[] = d()->name;
+ }
+ elseif (d()->type == 'platform') {
+
+ // Get a list of all that use this platform.
+ $aliases_files = _drush_sitealias_find_alias_files();
+ $aliases = array();
+ foreach ($aliases_files as $filename) {
+ if ((@include $filename) === FALSE) {
+ drush_log(dt('Cannot open alias file "!alias", ignoring.', array('!alias' => realpath($filename))), LogLevel::BOOTSTRAP);
+ continue;
+ }
+ }
+
+ foreach ($aliases as $alias_name => $alias) {
+ // If the alias is a site and platform is a match, load it into our sites list.
+ if (isset($alias['context_type']) && $alias['context_type'] == 'site' && $alias['platform'] == d()->name) {
+
+ // Use the URI as the key to prevent duplicate sites. Project aliases get included.
+ // I'm using URI as the value as well because DevShop writes additional aliases which are copies. We want to be sure to use the Aegir-generated alias name
+ $sites[$alias['uri']] = $alias['uri'];
+ }
+ }
+
+ drush_log(dt('Found %num sites on this platform: %sites', array(
+ '%num' => count($sites),
+ '%sites' => implode(', ', $sites),
+ )), 'ok');
+ }
+
+ drush_log(dt('Running git hooks for %num sites.', array(
+ '%num' => count($sites),
+ )), 'ok');
+
+ $hook_commands = provision_git_hooks();
+ foreach ($sites as $site) {
+ // @TODO: Gotta figure out a way to put this in the Git service.
+ if (is_array(d($site)->git_hooks)) {
+
+ // Allow overriding what hooks to run on the command line.
+ $hooks = drush_get_option('hooks', d($site)->git_hooks);
+ if (is_string($hooks)) {
+ $hooks = explode(',', $hooks);
+ }
+ $hooks = array_intersect($hooks, array_keys($hook_commands));
+
+ if (empty($hooks)) {
+ drush_log(dt('No hooks to invoke on site %site. Check drush context.', array(
+ '%site' => $site,
+ )), 'ok');
+ }
+ elseif (drush_get_option('hooks')) {
+ drush_log(dt('hooks option detected: Running %num git hooks on site %site: %hooks.', array(
+ '%num' => count($hooks),
+ '%hooks' => implode(', ', $hooks),
+ '%site' => $site,
+ )), 'ok');
+ }
+ else {
+ drush_log(dt('Using default git hooks for the site %site: %hooks.', array(
+ '%num' => count($hooks),
+ '%hooks' => implode(', ', $hooks),
+ '%site' => $site,
+ )), 'ok');
+ }
+
+ foreach ($hooks as $hook) {
+ drush_log(dt('Invoking git hook "%hook" on site %site: %command', array(
+ '%site' => $site,
+ '%hook' => $hook,
+ '%command' => $hook_commands[$hook],
+ )), 'ok');
+ provision_backend_invoke($site, $hook_commands[$hook]);
+ }
+
+ if (empty(d($site)->git_hooks)) {
+ drush_log(dt('No hooks to invoke. Check drush context.'), 'ok');
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/hosting_git.admin.inc b/hosting_git.admin.inc
index feee185..d39b9f3 100644
--- a/hosting_git.admin.inc
+++ b/hosting_git.admin.inc
@@ -24,5 +24,19 @@ function hosting_git_settings_form() {
'#title' => t('Allow deploying platforms from git repositories.'),
'#default_value' => variable_get('hosting_git_allow_deploy_platform', TRUE),
);
+ $form['hooks'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Git Hooks'),
+ );
+ $form['hooks']['hosting_git_allow_skip_hooks'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow users to skip git hooks when running Git Checkout or Git Pull tasks.'),
+ '#default_value' => variable_get('hosting_git_allow_skip_hooks', FALSE),
+ );
+ $form['hooks']['hosting_git_allow_task_override_hooks'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow users to configure what git hooks are run when running Git Checkout or Git Pull tasks.'),
+ '#default_value' => variable_get('hosting_git_allow_task_override_hooks', FALSE),
+ );
return system_settings_form($form);
}
diff --git a/hosting_git.drush.inc b/hosting_git.drush.inc
index b1b85b6..e84a69b 100644
--- a/hosting_git.drush.inc
+++ b/hosting_git.drush.inc
@@ -44,6 +44,14 @@ function drush_hosting_git_pre_hosting_task($task) {
}
}
+ // --skip-hooks option.
+ $task->options['skip-hooks'] = $task->task_args['skip_hooks'];
+
+ // --commands option. We pass a list of drush commands so we don't need a duplicate hook_hosting_git_hooks() in provision.
+ if (!empty($task->task_args['git_hooks'])) {
+ $task->options['hooks'] = $task->task_args['git_hooks'];
+ }
+
// Force the repository path.
// Otherwise git climbs the tree to find a platform dir under git control.
$task->options['force_repo_path_as_toplevel'] = TRUE;
@@ -71,6 +79,11 @@ function hosting_git_hosting_platform_context_options(&$task) {
*/
function hosting_git_hosting_site_context_options(&$task) {
hosting_git_hosting_platform_context_options($task);
+
+ // Transform the list of selected hooks to pass the command to the context_option.
+ foreach (array_filter($task->ref->git['git_hooks']) as $hook_name) {
+ $task->context_options['git_hooks'][] = $hook_name;
+ }
}
/**
@@ -81,6 +94,7 @@ function hosting_git_drush_context_import($context, &$node) {
$node->git['repo_url'] = $context->repo_url;
$node->git['git_ref'] = $context->git_ref;
$node->git['repo_path'] = $context->repo_path;
+ $node->git['git_hooks'] = $context->git_hooks;
}
}
@@ -104,3 +118,58 @@ function hosting_git_post_hosting_verify_task($task, $data) {
}
}
}
+
+/**
+ * Implements hook_post_hosting_TASK_TYPE_task().
+ *
+ * When git checkout occurs, code may change, so run verify tasks.
+ */
+function hosting_git_post_hosting_git_checkout_task($task, $data) {
+ hosting_git_run_verify_tasks($task);
+}
+
+/**
+ * Implements hook_post_hosting_TASK_TYPE_task().
+ *
+ * When git pull occurs, code may change, so run verify tasks.
+ */
+function hosting_git_post_hosting_git_pull_task($task, $data) {
+ hosting_git_run_verify_tasks($task);
+}
+
+/**
+ * Helper for post-checkout and post-pull tasks to trigger verify tasks for sites and platform.
+ * @param $task
+ */
+function hosting_git_run_verify_tasks($task){
+ if (isset($task->ref->nid)) {
+ hosting_add_task($task->ref->nid, 'verify');
+ drush_log(dt('Verify task queued for %name', array(
+ '%name' => $task->ref->title,
+ )), 'ok');
+
+ // If task was on a site, queue up a verify for it's platform.
+ if ($task->ref->type == 'site') {
+ hosting_add_task($task->ref->platform, 'verify');
+ drush_log(dt('Verify task queued for platform %nid', array(
+ '%nid' => $task->ref->platform,
+ )), 'ok');
+ }
+ // If task was on a platform, queue up a verify for all sites on the platform.
+ elseif ($task->ref->type == 'platform') {
+
+ $sites = db_select('hosting_site', s)
+ ->fields('s', array('nid'))
+ ->condition('platform', $task->ref->nid)
+ ->execute()
+ ->fetchAllKeyed(0, 0);
+
+ foreach ($sites as $site) {
+ hosting_add_task($site, 'verify');
+ drush_log(dt('Verify task queued for site %nid', array(
+ '%nid' => $site,
+ )), 'ok');
+ }
+ }
+ }
+}
diff --git a/hosting_git.install b/hosting_git.install
index b8c141c..567e127 100644
--- a/hosting_git.install
+++ b/hosting_git.install
@@ -47,6 +47,13 @@ function hosting_git_schema() {
'not null' => TRUE,
'default' => '',
),
+ 'git_hooks' => array(
+ 'description' => 'Serialized list of commands to run after a git checkout or git pull.',
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ ),
),
'primary key' => array('nid'),
);
@@ -73,3 +80,16 @@ function hosting_git_update_7301 () {
'default' => '',
));
}
+
+/**
+ * Add git_hooks fields.
+ */
+function hosting_git_update_7303 () {
+ db_add_field('hosting_git', 'git_hooks', array(
+ 'description' => 'Serialized list of commands to run after a git checkout or git pull.',
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ ));
+}
diff --git a/hosting_git.module b/hosting_git.module
index a8087d7..909cf17 100644
--- a/hosting_git.module
+++ b/hosting_git.module
@@ -100,7 +100,7 @@ function hosting_git_form_alter(&$form, &$form_state, $form_id) {
'#default_value' => isset($node->git['git_ref']) ? $node->git['git_ref'] : 'master',
);
}
- else {
+ elseif (!empty($node->git['repo_url'])) {
// Display it.
$form['git']['repo_url_display'] = array(
'#type' => 'item',
@@ -131,6 +131,21 @@ function hosting_git_form_alter(&$form, &$form_state, $form_id) {
);
}
+ // Git Hooks field
+ $options = hosting_git_get_hook_options();
+ if (empty($node->nid) && empty($node->git['git_hooks'])) {
+ $node->git['git_hooks'] = array('update', 'cache');
+ }
+ if (count($options)) {
+ $form['git']['git_hooks'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Git Hooks'),
+ '#options' => $options,
+ '#description' => t('These hooks will be run after every Git Pull or Git Checkout.'),
+ '#default_value' => $node->git['git_hooks'],
+ );
+ }
+
// Default collapse one fieldset based on the current node.
if (empty($node->frommakefile['makefile'])) {
$form['frommakefile']['#collapsed'] = TRUE;
@@ -138,6 +153,12 @@ function hosting_git_form_alter(&$form, &$form_state, $form_id) {
elseif (empty($node->git['repo_url'])) {
$form['git']['#collapsed'] = TRUE;
}
+
+ // Skip Git forms for existing sites without a repo URL.
+ // Adding one later is currently not supported.
+ if (isset($node->nid) && $node->verified && empty($node->git['repo_url'])) {
+ $form['git']['#access'] = FALSE;
+ }
}
}
@@ -181,6 +202,7 @@ function hosting_git_node_update($node) {
'repo_path' => $node->git['repo_path'],
'repo_docroot' => $node->git['repo_docroot'],
'git_ref' => $node->git['git_ref'],
+ 'git_hooks' => serialize($node->git['git_hooks']),
))
->execute();
}
@@ -209,6 +231,7 @@ function hosting_git_node_load($nodes, $types) {
$node->git['git_ref'] = $result->git_ref;
$node->git['repo_path'] = $result->repo_path;
$node->git['repo_docroot'] = $result->repo_docroot;
+ $node->git['git_hooks'] = unserialize($result->git_hooks);
// If platform repo path is empty, then load the publish_path in it's place.
if ( $node->type == 'platform' && empty($node->git['repo_path'])) {
@@ -245,6 +268,10 @@ function _hosting_git_node_load_defaults(&$node) {
if (!isset($node->git['git_ref'])) {
$node->git['git_ref'] = 'master';
}
+
+ if (!isset($node->git['git_hooks'])) {
+ $node->git['git_hooks'] = array('update', 'cache');
+ }
}
/**
@@ -350,3 +377,48 @@ function _hosting_git_site_or_platform_enabled($node) {
)
);
}
+
+/**
+ * Retrive a list of available Git Hooks ready for a Forms API checkboxes element.
+ */
+function hosting_git_get_hook_options() {
+ $options = array();
+ $hooks = module_invoke_all('hosting_git_hooks');
+ foreach ($hooks as $name => $hook) {
+ $options[$name] = $hook['description'];
+ }
+ return $options;
+}
+
+/**
+ * Implements hook_hosting_git_hooks()
+ */
+function hosting_git_hosting_git_hooks() {
+ return array(
+ 'update' => array(
+ 'description' => t('Run database updates'),
+ ),
+ 'cache' => array(
+ 'description' => t('Flush all caches'),
+ ),
+ 'registry' => array(
+ 'description' => t('Rebuild Registry'),
+ ),
+ 'revert' => array(
+ 'description' => t('Revert all features'),
+ ),
+ );
+}
+
+/**
+ * Return am array of arrays,
+ *
+ * @api
+ */
+function hook_hosting_git_hooks() {
+ return array(
+ 'cache' => array(
+ 'description' => t('Flush all caches'),
+ ),
+ );
+}
\ No newline at end of file
diff --git a/pull/drush/provision_git_pull.drush.inc b/pull/drush/provision_git_pull.drush.inc
index ae6bb69..f2ab20d 100644
--- a/pull/drush/provision_git_pull.drush.inc
+++ b/pull/drush/provision_git_pull.drush.inc
@@ -4,6 +4,10 @@
* Implementation of hook_drush_command().
*/
function provision_git_pull_drush_command() {
+
+ $hooks = provision_git_hooks();
+ $available_hooks = implode(', ', array_keys($hooks));
+
$items['provision-git-pull'] = array(
'description' => 'Executes "git pull".',
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
@@ -11,6 +15,8 @@ function provision_git_pull_drush_command() {
'reset' => 'Resets any working tree changes that would block a rebase. USE WITH CAUTION',
'repo_path' => 'Optional: force a repository path. Defaults to the site or platform dir.',
'force_repo_path_as_toplevel' => 'Stop Git from looking in parent directories. Defaults to FALSE.',
+ 'skip-hooks' => "Do not run the site's git hooks.",
+ 'hooks' => "Override the hooks to run after git pull. Separate with a comma. Available hooks include ". $available_hooks
),
'aliases' => array('pull'),
);
@@ -60,7 +66,11 @@ function drush_provision_git_pull() {
* Implements drush_hook_post_COMMAND().
*/
function drush_provision_git_pull_post_provision_git_pull() {
- // Re-verify the site, this corrects the file permission when necessary.
+
+ // Run all hooks on updated sites
+ provision_git_run_git_hooks();
+
+ // Re-verify the site or platform, this corrects the file permission when necessary.
$options = array();
$target = d()->uri;
provision_backend_invoke($target, 'provision-verify', array(), $options);
diff --git a/pull/hosting_git_pull.module b/pull/hosting_git_pull.module
index 877bf1f..52c7fca 100644
--- a/pull/hosting_git_pull.module
+++ b/pull/hosting_git_pull.module
@@ -114,6 +114,52 @@ function hosting_task_git_pull_form($node) {
'#default_value' => TRUE,
);
+ $form['skip_hooks'] = array(
+ '#title' => t('Skip git hooks'),
+ '#description' => t("Do not run git hooks after running git checkout.") . ' ' . t('Warning: This may result in an unstable site if a database update is required') . '',
+ '#weight' => '0',
+ '#type' => 'checkbox',
+ '#weight' => '-1',
+ '#default_value' => FALSE,
+ '#access' => variable_get('hosting_git_allow_skip_hooks', FALSE),
+ );
+ $form['git_hooks'] = array(
+ '#title' => t('Git Hooks'),
+ '#weight' => '1',
+ '#type' => 'checkboxes',
+ '#default_value' => $node->git['git_hooks'],
+ '#access' => variable_get('hosting_git_allow_task_override_hooks', FALSE),
+ '#options' => hosting_git_get_hook_options(),
+ '#states' => array(
+ 'invisible' => array(
+ ':input[name="parameters[skip_hooks]"]' => array('checked' => TRUE),
+ ),
+ ),
+ '#element_validate' => array(
+ 'hosting_git_hooks_implode'
+ )
+ );
+
+ $form['hooks'] = array(
+ '#type' => 'container',
+ '#states' => array(
+ 'invisible' => array(
+ ':input[name="parameters[skip_hooks]"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ $hooks = module_invoke_all('hosting_git_hooks');
+ foreach ($hooks as $name => $hook) {
+ $name = ucfirst($name);
+ $items[] = " " . $hook['description'];
+ }
+ $form['hooks']['hooks_display'] = array(
+ '#type' => 'item',
+ '#title' => t('Git Hooks'),
+ '#markup' => theme('item_list', array('items' => $items)),
+ '#access' => !variable_get('hosting_git_allow_task_override_hooks', FALSE),
+ );
return $form;
}