6 * Hacked drush command.
8 * Enables drush support for the Hacked! module.
12 * Implementation of hook_drush_help().
14 function hacked_drush_help($section) {
16 case 'drush:hacked-list-projects':
17 return dt('List projects and their hacked/unhacked status.');
18 case 'drush:hacked-details':
19 return dt('Show details of the files in one project, and the hacked/unhacked status of those files.');
20 case 'drush:hacked-diff':
21 return dt('Output a unified diff of the specified project.');
27 * Implementation of hook_drush_command().
29 * @See drush_parse_command() for a list of recognized keys.
32 * An associative array describing your command(s).
34 function hacked_drush_command() {
37 $items['hacked-list-projects'] = array(
38 'description' => "List all projects that can be analysed by Hacked! ",
39 'drupal dependencies' => array('hacked'),
41 'force-rebuild' => 'Rebuild the Hacked! report instead of getting a cached version.'
43 'aliases' => array('hlp'),
46 $items['hacked-lock-modified'] = array(
47 'description' => "Lock all projects that Hacked! detects are modified, so that drush pm-updatecode will not touch them. (drush-4.x+ only)",
48 'drupal dependencies' => array('hacked'),
51 $items['hacked-details'] = array(
52 'description' => "Show the Hacked! report about a specific project.",
53 'drupal dependencies' => array('hacked'),
55 'project' => 'The machine name of the project to report on.',
58 'include-unchanged' => 'Show the files that are unchanged too.',
60 'aliases' => array('hd'),
63 $items['hacked-diff'] = array(
64 'description' => "Output a unified diff of the project specified.",
65 'drupal dependencies' => array('hacked'),
67 'project' => 'The machine name of the project to report on.',
70 'diff-options' => 'Command line options to pass through to the diff command.'
78 * Compute the report data for hacked.
80 * WARNING: This function can invoke a batch process and end your current page.
81 * So you'll want to be very careful if you call this!
83 function hacked_calculate_project_data_drush($projects, $force = FALSE, $redirect = NULL) {
84 include_once DRUPAL_ROOT . '/includes/batch.inc';
86 // Try to get the report form cache if we can.
87 $cache = cache_get('hacked:drush:full-report', HACKED_CACHE_TABLE);
88 if (!empty($cache->data) && !$force) {
92 // Enter a batch to build the report.
93 $operations = array();
94 foreach ($projects as $project) {
95 $operations[] = array(
96 'hacked_build_report_batch',
97 array($project['name']),
102 'operations' => $operations,
103 'finished' => 'hacked_build_report_batch_finished_drush',
104 'file' => drupal_get_path('module', 'hacked') . '/hacked.report.inc',
105 'title' => t('Building report'),
108 drush_print('Rebuilding Hacked! report');
110 $batch =& batch_get();
111 $batch['progressive'] = FALSE;
112 drush_backend_batch_process();
113 drush_print('Done.');
115 // Now we can get the data from the cache.
116 $cache = cache_get('hacked:drush:full-report', HACKED_CACHE_TABLE);
117 if (!empty($cache->data)) {
123 * Completion callback for the report batch.
125 function hacked_build_report_batch_finished_drush($success, $results, $operations) {
128 usort($results['report'], '_hacked_project_report_sort_by_status');
130 cache_set('hacked:drush:full-report', $results['report'], HACKED_CACHE_TABLE, strtotime('+1 day'));
135 * Drush command callback that shows the listing of changed/unchanged projects.
137 function drush_hacked_list_projects() {
140 module_load_include('inc', 'update', 'update.report');
141 if ($available = update_get_available(TRUE)) {
142 module_load_include('inc', 'update', 'update.compare');
143 $data = update_calculate_project_data($available);
144 $force_rebuild = drush_get_option('force-rebuild', FALSE);
145 $projects = hacked_calculate_project_data_drush($data, $force_rebuild);
146 // Now print the data using drush:
155 foreach ($projects as $project) {
159 $project['existing_version']
162 // Now add the status:
163 switch ($project['status']) {
164 case HACKED_STATUS_UNHACKED:
165 $row[] = dt('Unchanged');
167 case HACKED_STATUS_HACKED:
168 $row[] = t('Changed');
170 case HACKED_STATUS_UNCHECKED:
172 $row[] = t('Unchecked');
176 $row[] = $project['counts']['different'];
177 $row[] = $project['counts']['missing'];
182 drush_print_table($rows, TRUE);
189 * Lock all of the modified files so that pm-updatecode will not
192 function drush_hacked_lock_modified() {
193 $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
194 module_load_include('inc', 'update', 'update.report');
195 if (isset($drupal_root) && ($available = update_get_available(TRUE))) {
196 module_load_include('inc', 'update', 'update.compare');
197 $data = update_calculate_project_data($available);
198 $projects = hacked_calculate_project_data_drush($data, TRUE);
200 foreach ($projects as $project) {
202 empty($project['title']) ? $project['name'] : $project['title'],
204 $project['existing_version']
207 // Lock the file if it is not already locked.
208 switch ($project['status']) {
209 case HACKED_STATUS_HACKED:
211 $project_ob = hacked_project_load($project['project_name']);
212 $lockfile = $project_ob->file_get_location('local', '.drush-lock-update');
213 if (!file_exists($lockfile)) {
214 drush_op('file_put_contents', $lockfile, dt("Locked: modified."));
215 drush_print(dt("Locked: @project", array('@project' => $project['name'])));
218 drush_print(dt("@project is modified and already locked", array('@project' => $project['name'])));
221 case HACKED_STATUS_UNHACKED:
222 case HACKED_STATUS_UNCHECKED:
231 * Add a --lock-modified flag to pm-updatecode
233 function drush_hacked_pre_pm_updatecode() {
234 if (drush_get_option('lock-modified')) {
235 drush_print(dt('Hacked! is checking for modified projects...'));
236 drush_hacked_lock_modified();
237 drush_print(dt('Hacked! modification check complete.'));
242 * Add --lock-modified to the pm-updatecode and pm-update help
244 function hacked_drush_help_alter(&$command) {
245 if (($command['command'] == 'pm-updatecode') || ($command['command'] == 'pm-update')) {
246 $command['sub-options']['--lock']['--lock-modified'] = "Lock any project that Hacked! determines is modified.";
251 * Validate hook for the hacked_details drush command.
253 function drush_hacked_details_validate($short_name = '') {
254 return drush_hacked_drush_command_validate($short_name);
258 * Validate hook for the hacked drush commands that need a project.
260 function drush_hacked_drush_command_validate($short_name = '') {
261 if (empty($short_name)) {
262 return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('A valid project must be specified', array('@project' => $short_name)));
265 $project = hacked_project_load($short_name);
266 $project->identify_project();
267 if (!$project->project_identified) {
268 return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('Could not find project: @project', array('@project' => $short_name)));
274 * Drush command callback that shows the list of changes/unchanged files in a project.
276 * You may specify the --include-unchanged option to show unchanged files too,
277 * otherwise just the changed and deleted files are shown.
279 function drush_hacked_details($short_name) {
280 $project = hacked_project_load($short_name);
281 $report = $project->compute_details();
283 drush_print(dt('Details for project: @name', array('@name' => $project->title())));
284 drush_print(dt('Total files: @total_files, files changed: @changed_files, deleted files: @deleted_files', array(
285 '@total_files' => count($report['files']),
286 '@changed_files' => $report['counts']['different'],
287 '@deleted_files' => $report['counts']['missing'],
291 drush_print(dt('Detailed results:'));
293 arsort($report['files']);
299 $show_unchanged = drush_get_option('include-unchanged', FALSE);
300 foreach ($report['files'] as $file => $status) {
301 if (!$show_unchanged && $status == HACKED_STATUS_UNHACKED) {
307 // Now add the status:
309 case HACKED_STATUS_UNHACKED:
310 $row[] = dt('Unchanged');
312 case HACKED_STATUS_HACKED:
313 $row[] = t('Changed');
315 case HACKED_STATUS_DELETED:
316 $row[] = t('Deleted');
318 case HACKED_STATUS_UNCHECKED:
320 $row[] = t('Unchecked');
329 drush_print_table($rows, TRUE);
335 * Validate hook for the hacked_diff drush command.
337 function drush_hacked_diff_validate($short_name = '') {
338 return drush_hacked_drush_command_validate($short_name);
342 * Drush command callback that shows the list of changes/unchanged files in a project.
344 * You may specify the --include-unchanged option to show unchanged files too,
345 * otherwise just the changed and deleted files are shown.
347 function drush_hacked_diff($short_name) {
348 $project = hacked_project_load($short_name);
350 $local_location = $project->file_get_location('local', '');
351 $clean_location = $project->file_get_location('remote', '');
353 // If the hasher is our ignore line endings one, then ignore line endings.
354 if (variable_get('hacked_selected_file_hasher', HACKED_DEFAULT_FILE_HASHER) == 'hacked_ignore_line_endings') {
355 $default_options = '-uprb';
358 $default_options = '-upr';
361 $diff_options = drush_get_option('diff-options', $default_options);
362 drush_shell_exec("diff $diff_options $clean_location $local_location");
364 $lines = drush_shell_exec_output();
365 $local_location_trim = dirname($local_location . '/dummy.file') . '/';
366 $clean_location_trim = dirname($clean_location . '/dummy.file') . '/';
367 foreach ($lines as $line) {
368 if (strpos($line, '+++') === 0) {
369 $line = str_replace($local_location_trim, '', $line);
371 if (strpos($line, '---') === 0) {
372 $line = str_replace($clean_location_trim, '', $line);
374 if (strpos($line, 'diff -upr') === 0) {
375 $line = str_replace($clean_location_trim, 'a/', $line);
376 $line = str_replace($local_location_trim, 'b/', $line);