5 * Drush PM drupal.org Git extension.
8 use Drush\Log\LogLevel;
11 * Validate this package handler can run.
13 function package_handler_validate() {
14 // Check git command exists. Disable possible output.
15 $debug = drush_get_context('DRUSH_DEBUG');
16 drush_set_context('DRUSH_DEBUG', FALSE);
18 // We need to check for a git executable and then make sure version is >=1.7
19 // (avoid drush_shell_exec because we want to run this even in --simulated mode.)
20 $success = exec('git --version', $git);
21 $git_version_array = explode(" ", $git[0]);
22 $git_version = $git_version_array[2];
24 drush_set_context('DRUSH_DEBUG', $debug);
26 return drush_set_error('DRUSH_SHELL_COMMAND_NOT_FOUND', dt('git executable not found.'));
27 } elseif ($git_version < '1.7') {
28 return drush_set_error('GIT_VERSION_UNSUPPORTED', dt('Your git version !git_version is not supported; please upgrade to git 1.7 or later.', array('!git_version' => $git_version)));
30 // Check git_deploy is enabled. Only for bootstrapped sites.
31 if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
32 drush_include_engine('drupal', 'environment');
33 if (!drush_get_option('gitinfofile') && !drush_module_exists('git_deploy')) {
34 drush_log(dt('git package handler needs git_deploy module enabled to work properly.'), LogLevel::WARNING);
45 * The project array with name, base and full (final) paths.
47 * The release details array from drupal.org.
49 function package_handler_download_project(&$request, $release) {
50 if ($username = drush_get_option('gitusername')) {
51 // Uses SSH, which enables pushing changes back to git.drupal.org.
52 $repository = $username . '@git.drupal.org:project/' . $request['name'] . '.git';
55 $repository = 'git://git.drupal.org/project/' . $request['name'] . '.git';
57 $request['repository'] = $repository;
58 $tag = $release['tag'];
60 // If the --cache option was given, create a new git reference cache of the
61 // remote repository, or update the existing cache to fetch recent changes.
62 if (drush_get_option('cache') && ($cachedir = drush_directory_cache())) {
63 $gitcache = $cachedir . '/git';
64 $projectcache = $gitcache . '/' . $request['name'] . '.git';
65 drush_mkdir($gitcache);
66 // Setup a new cache, if we don't have this project yet.
67 if (!file_exists($projectcache)) {
68 // --mirror works similar to --bare, but retrieves all tags, local
69 // branches, remote branches, and any other refs (notes, stashes, etc).
70 // @see http://stackoverflow.com/questions/3959924
71 $command = 'git clone --mirror';
72 if (drush_get_context('DRUSH_VERBOSE')) {
73 $command .= ' --verbose --progress';
76 drush_shell_cd_and_exec($gitcache, $command, $repository, $request['name'] . '.git');
78 // If we already have this project, update it to speed up subsequent clones.
80 // A --mirror clone is fully synchronized with `git remote update` instead
81 // of `git fetch --all`.
82 // @see http://stackoverflow.com/questions/6150188
83 drush_shell_cd_and_exec($projectcache, 'git remote update');
85 $gitcache = $projectcache;
88 // Clone the repo into a temporary path.
89 $clone_path = drush_tempdir();
91 $command = 'git clone';
92 $command .= ' ' . drush_get_option('gitcloneparams');
93 if (drush_get_option('cache')) {
94 $command .= ' --reference ' . drush_escapeshellarg($gitcache);
96 if (drush_get_context('DRUSH_VERBOSE')) {
97 $command .= ' --verbose --progress';
99 $command .= ' ' . drush_escapeshellarg($repository);
100 $command .= ' ' . drush_escapeshellarg($clone_path);
101 if (!drush_shell_exec($command)) {
102 return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to clone project !name from git.drupal.org.', array('!name' => $request['name'])));
105 // Check if the 'tag' from the release feed is a tag or a branch.
106 // If the tag exists, git will return it
107 if (!drush_shell_cd_and_exec($clone_path, 'git tag -l ' . drush_escapeshellarg($tag))) {
108 return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to clone project !name from git.drupal.org.', array('!name' => $request['name'])));
110 $output = drush_shell_exec_output();
112 if (isset($output[0]) && ($output[0] == $tag)) {
113 // If we want a tag, simply checkout it. The checkout will end up in
114 // "detached head" state.
115 $command = 'git checkout ' . drush_get_option('gitcheckoutparams');
116 $command .= ' ' . drush_escapeshellarg($tag);
117 if (!drush_shell_cd_and_exec($clone_path, $command)) {
118 return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
122 // Else, we want to checkout a branch.
123 // First check if we are not already in the correct branch.
124 if (!drush_shell_cd_and_exec($clone_path, 'git symbolic-ref HEAD')) {
125 return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
127 $output = drush_shell_exec_output();
128 $current_branch = preg_replace('@^refs/heads/@', '', $output[0]);
130 // If we are not on the correct branch already, switch to the correct one.
131 if ($current_branch != $tag) {
132 $command = 'git checkout';
133 $command .= ' ' . drush_get_option('gitcheckoutparams');
134 $command .= ' --track ' . drush_escapeshellarg('origin/' . $tag) . ' -b ' . drush_escapeshellarg($tag);
135 if (!drush_shell_cd_and_exec($clone_path, $command)) {
136 return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
145 * Update a project (so far, only modules are supported).
148 * The project array with name, base and full (final) paths.
150 * The release details array from drupal.org.
152 function package_handler_update_project($request, $release) {
153 drush_log('Updating project ' . $request['name'] . ' ...');
156 if ((!empty($release['version_extra'])) && ($release['version_extra'] == 'dev')) {
157 // Update the branch of the development repository.
158 $commands[] = 'git pull';
159 $commands[] = drush_get_option('gitpullparams');
162 // Use a stable repository.
163 $commands[] = 'git fetch';
164 $commands[] = drush_get_option('gitfetchparams');
166 $commands[] = 'git checkout';
167 $commands[] = drush_get_option('gitcheckoutparams');
168 $commands[] = $release['version'];
171 if (!drush_shell_cd_and_exec($request['full_project_path'], implode(' ', $commands))) {
172 return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to update ' . $request['name'] . ' from git.drupal.org.');
179 * Post download action.
181 * This action take place once the project is placed in its final location.
183 * Here we add the project as a git submodule.
185 function package_handler_post_download($project, $release) {
186 if (drush_get_option('gitsubmodule', FALSE)) {
187 // Obtain the superproject path, then add as submodule.
188 if (drush_shell_cd_and_exec(dirname($project['full_project_path']), 'git rev-parse --show-toplevel')) {
189 $output = drush_shell_exec_output();
190 $superproject = $output[0];
191 // Add the downloaded project as a submodule of its git superproject.
193 $command[] = 'git submodule add';
194 $command[] = drush_get_option('gitsubmoduleaddparams');
195 $command[] = $project['repository'];
196 // We need the submodule relative path.
197 $command[] = substr(realpath($project['full_project_path']), strlen(realpath($superproject)) + 1);
198 if (!drush_shell_cd_and_exec($superproject, implode(' ', $command))) {
199 return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to add !name as a git submodule of !super.', array('!name' => $project['name'], '!super' => $superproject)));
203 return drush_set_error('DRUSH_PM_GIT_SUBMODULE_PROBLEMS', dt('Unable to create !project as a git submodule: !dir is not in a Git repository.', array('!project' => $project['name'], '!dir' => dirname($project['full_project_path']))));
207 if (drush_get_option('gitinfofile', FALSE)) {
209 if (preg_match('/^(.+).x-dev$/', $release['version'], $matches)) {
210 $full_version = drush_pm_git_drupalorg_compute_rebuild_version($project['full_project_path'], $matches[1]);
213 $full_version = $release['version'];
215 if (drush_shell_cd_and_exec(dirname($project['full_project_path']), 'git log -1 --pretty=format:%ct')) {
216 $output = drush_shell_exec_output();
217 $datestamp = $output[0];
222 drush_pm_inject_info_file_metadata($project['full_project_path'], $project['name'], $full_version, $datestamp);
228 * Helper function to compute the rebulid version string for a project.
230 * This does some magic in Git to find the latest release tag along
231 * the branch we're packaging from, count the number of commits since
232 * then, and use that to construct this fancy alternate version string
233 * which is useful for the version-specific dependency support in Drupal
236 * NOTE: A similar function lives in git_deploy and in the drupal.org
237 * packaging script (see DrupalorgProjectPackageRelease.class.php inside
238 * drupalorg/drupalorg_project/plugins/release_packager). Any changes to the
239 * actual logic in here should probably be reflected in the other places.
241 * @param string $project_dir
242 * The full path to the root directory of the project to operate on.
243 * @param string $branch
244 * The branch that we're using for -dev. This should only include the
245 * core version, the dash, and the branch's major version (eg. '7.x-2').
248 * The full 'rebuild version string' in the given Git checkout.
250 function drush_pm_git_drupalorg_compute_rebuild_version($project_dir, $branch) {
251 $rebuild_version = '';
252 $branch_preg = preg_quote($branch);
254 if (drush_shell_cd_and_exec($project_dir, 'git describe --tags')) {
255 $shell_output = drush_shell_exec_output();
256 $last_tag = $shell_output[0];
257 // Make sure the tag starts as Drupal formatted (for eg.
258 // 7.x-1.0-alpha1) and if we are on a proper branch (ie. not master)
259 // then it's on that branch.
260 if (preg_match('/^(?<drupalversion>' . $branch_preg . '\.\d+(?:-[^-]+)?)(?<gitextra>-(?<numberofcommits>\d+-)g[0-9a-f]{7})?$/', $last_tag, $matches)) {
261 // If we found additional git metadata (in particular, number of commits)
262 // then use that info to build the version string.
263 if (isset($matches['gitextra'])) {
264 $rebuild_version = $matches['drupalversion'] . '+' . $matches['numberofcommits'] . 'dev';
266 // Otherwise, the branch tip is pointing to the same commit as the
267 // last tag on the branch, in which case we use the prior tag and
268 // add '+0-dev' to indicate we're still on a -dev branch.
270 $rebuild_version = $last_tag . '+0-dev';
274 return $rebuild_version;