diff --git a/versioncontrol_project_issue/plugins/event_processor/VersioncontrolEventProcessorGitIssueMapper.inc b/versioncontrol_project_issue/plugins/event_processor/VersioncontrolEventProcessorGitIssueMapper.inc new file mode 100644 index 0000000..d019d2a --- /dev/null +++ b/versioncontrol_project_issue/plugins/event_processor/VersioncontrolEventProcessorGitIssueMapper.inc @@ -0,0 +1,134 @@ + $event->getRepository()->repo_id))->fetchField(); + if (!$project_nid) { + // No project associated. + return; + } + // We are depending on getCommitInterval(), so check we are using the right + // synchronization plugin. + $synchronizer = $event->getRepository()->getSynchronizer(); + if (!method_exists($synchronizer, 'getCommitInterval')) { + watchdog('versioncontrol_project_issue', 'git_issue_mapper: Cannot use a repository synchronizer of type %type', array('%type' => get_class($synchronizer)), WATCHDOG_WARNING); + return; + } + $operation_nid_maps = array(); + foreach ($event as $ref) { + if ($ref->reftype != VERSIONCONTROL_GIT_REFTYPE_BRANCH) { + // Only process branch refs for now. + return; + } + if ($ref->ff != 1) { + // @fixme support non-ff. + continue; + } + if ($ref->eventDeletedMe()) { + // Nothing to do. + // We could try to figure out orphaned commits from label removal. Let's + // trust on hook_versioncontrol_entity_commit_delete() for now. + continue; + } + // Usual ff ref update. + $commit_hashes = $synchronizer->getCommitInterval($ref->old_sha1, $ref->new_sha1); + // @todo Try to deal with big pushes loading operations in batches. + $related_operations = $event->getRepository()->loadCommits(array(), array('revision' => $commit_hashes)); + foreach ($related_operations as $operation) { + $nids_on_message = $this->getIssuesFromMessage($operation->message); + if (count($nids_on_message) < 1) { + // Nothing to do. + continue; + } + // Make sure we are dealing with project issues. + $possible_issues = node_load_multiple($nids_on_message); + foreach ($possible_issues as $possible_issue) { + if (!project_issue_node_is_issue($possible_issue)) { + continue; + } + // Associate only if repository project is the same than issue + // project. + if (empty($possible_issue->field_project[LANGUAGE_NONE][0]['target_id'])) { + continue; + } + if ($possible_issue->field_project[LANGUAGE_NONE][0]['target_id'] != $project_nid) { + continue; + } + $operation_nid_maps[] = array( + 'nid' => $possible_issue->nid, + 'vc_op_id' => $operation->vc_op_id + ); + } + } + } + if (empty($operation_nid_maps)) { + // Nothing to do. + return; + } + // Insert mappings. + $insert_query = db_insert('versioncontrol_project_issue_operations')->fields(array('nid', 'vc_op_id')); + foreach ($operation_nid_maps as $operation_nid_map) { + $insert_query->values($operation_nid_map); + } + $insert_query->execute(); + } + + public function setRepository(VersioncontrolRepository $repository) { + $this->repository = $repository; + } + + public function setConfiguration($default_data) { + $this->messagePattern = !empty($default_data['message_pattern']) ? $default_data['message_pattern'] : ''; + } + + public function buildForm($default_data) { + $form = array(); + $form['event-processor-issue-mapper-pattern'] = array( + '#type' => 'textfield', + '#title' => t('Extract pattern'), + '#description' => t('A pattern to be used to extract the issue ids from the commit message. i.e. "/#(\d+)\b/"'), + '#default_value' => !empty($default_data['message_pattern']) ? $default_data['message_pattern'] : '', + ); + return $form; + } + + public function getFormData($form_state_values_data) { + return array( + 'message_pattern' => $form_state_values_data['event-processor-issue-mapper-pattern'], + ); + } + + public function submitForm(&$form, &$form_state) { + // Nothing special. + } + + /** + * Retrieve the issues from operation message. + */ + function getIssuesFromMessage($message) { + if (preg_match_all($this->messagePattern, $message, $matches)) { + return $matches[1]; + } + return array(); + } +} diff --git a/versioncontrol_project_issue/plugins/event_processor/git_issue_mapper.inc b/versioncontrol_project_issue/plugins/event_processor/git_issue_mapper.inc new file mode 100644 index 0000000..6eb17ae --- /dev/null +++ b/versioncontrol_project_issue/plugins/event_processor/git_issue_mapper.inc @@ -0,0 +1,14 @@ + 'git', + 'title' => t('Issue mapper for git repositories'), + 'handler' => array( + 'class' => 'VersioncontrolEventProcessorGitIssueMapper', + 'file' => 'VersioncontrolEventProcessorGitIssueMapper.inc', + 'path' => drupal_get_path('module', 'versioncontrol_project_issue') . '/plugins/event_processor', + ), +); diff --git a/versioncontrol_project_issue/versioncontrol_project_issue.info b/versioncontrol_project_issue/versioncontrol_project_issue.info new file mode 100644 index 0000000..aa965d5 --- /dev/null +++ b/versioncontrol_project_issue/versioncontrol_project_issue.info @@ -0,0 +1,6 @@ +name = "Version Control / Project issue integration" +description = "Expose some functionality that integrates project_issue and Version Control API." +package = Version Control +dependencies[] = versioncontrol_project +dependencies[] = project_issue +core = 7.x diff --git a/versioncontrol_project_issue/versioncontrol_project_issue.install b/versioncontrol_project_issue/versioncontrol_project_issue.install new file mode 100644 index 0000000..1d873d7 --- /dev/null +++ b/versioncontrol_project_issue/versioncontrol_project_issue.install @@ -0,0 +1,32 @@ + 'This table associates project issue nodes with versioncontrol operations.', + 'fields' => array( + 'nid' => array( + 'description' => 'Foreign key for the project issue({project_issues}.nid).', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'vc_op_id' => array( + 'description' => 'Foreign key for the operation({versioncontrol_operations}.vc_op_id).', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('nid', 'vc_op_id'), + ); + return $schema; +} diff --git a/versioncontrol_project_issue/versioncontrol_project_issue.module b/versioncontrol_project_issue/versioncontrol_project_issue.module new file mode 100644 index 0000000..28b2c55 --- /dev/null +++ b/versioncontrol_project_issue/versioncontrol_project_issue.module @@ -0,0 +1,150 @@ + $vc_op_ids) { + $repository = versioncontrol_project_repository_load($nodes[$nid]->field_project[LANGUAGE_NONE][0]['target_id']); + $nodes[$nid]->versioncontrol_project_issue['operations'] = $repository->loadCommits($vc_op_ids); + } +} + +/** + * Implements hook_node_update(). + * + * Change the mappings when a project issue is changed of project. + */ +function versioncontrol_project_issue_node_update($node) { + if (!project_issue_node_type_is_issue($node->type)) { + return; + } + $project_id = $node->field_project[LANGUAGE_NONE][0]['target_id']; + $original_project_id = $node->original->field_project[LANGUAGE_NONE][0]['target_id']; + if ($original_project_id == $project_id) { + // Nothing to do. + return; + } + // Project was changed, remove operation associations with the issue. + db_delete('versioncontrol_project_issue_operations')->condition('nid', $node->nid)->execute(); + // @todo Search all operations associated with the new project? (if decided, + // use a queue, since it can be time consuming). +} + +/** + * Implements hook_node_delete(). + */ +function versioncontrol_project_issue_node_delete($node) { + if (!project_issue_node_is_issue($node)) { + // Nothing to do. + return; + } + // Remove mappings on delete. + db_delete('versioncontrol_project_issue_operations')->condition('nid', $node->nid)->execute(); +} + +/** + * Implements hook_versioncontrol_entity_commit_delete(). + * + * Repository synchronization removes commits, event processor plugins are + * executed later, so we cannot react at that time. So we use this hook to + * remove relations from our table. + */ +function versioncontrol_project_issue_versioncontrol_entity_commit_delete(VersioncontrolOperation $operation) { + db_delete('versioncontrol_project_issue_operations')->condition('vc_op_id', $operation->vc_op_id)->execute(); +} + +/** + * Implements hook_versioncontrol_repository_pre_resync(). + * + * @fixme Move to ans specific git module. + * Best effort to preserve existing mappings. + * A full review will be too costly, specially if there are a lot of associated + * issues. + * @todo Maybe add an option to do full re-map re-parsing operation messages. + */ +function versioncontrol_project_issue_versioncontrol_repository_pre_resync(VersioncontrolRepository $repository, $bypass) { + if (empty($repository->project_nid)) { + // No project associated. + return; + } + + // Load all associated project issue nids. + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'node') + ->entityCondition('bundle', project_issue_issue_node_types()) + ->fieldCondition('field_release_project', 'target_id', $repository->project_nid); + $result = $query->execute(); + if (empty($result['node'])) { + // No issues associated. + return; + } + $issue_nids = array_keys($result['node']); + // Skip full operation loading, and do it manually for performance reasons. + $vc_op_ids_by_id = db_query('SELECT vco.vc_op_id, vco.revision FROM {versioncontrol_operations} vco JOIN {versioncontrol_project_projects} vcp WHERE vcp.repo_id = :repo_id', array(':repo_id' => $repository->project_nid))->fetchAllKeyed(); + $vc_op_ids_by_issue_nid = versioncontrol_project_issue_get_issue_operation_ids($issue_nids); + $commit_hashes_by_issue_nid = array(); + foreach ($vc_op_ids_by_issue_nid as $issue_nid => $vc_op_ids) { + foreach ($vc_op_ids as $vc_op_id) { + $commit_hashes_by_issue_nid[$issue_nid][] = $vc_op_ids_by_id[$vc_op_id]; + } + } + + // Only set the cache if there were results that'll need refreshing later + if (!empty($commit_hashes_by_issue_nid)) { + ctools_static('versioncontrol_project_issue_git_resync_recover_' . $repository->repo_id, $commit_hashes_by_issue_nid); + } +} + +/** + * Implements hook_versioncontrol_repository_post_resync(). + * + * @fixme Implement! + */ +function versioncontrol_release_versioncontrol_repository_post_resync(VersioncontrolRepository $repository, $bypass) { +} + +/** + * Helper to get the operation IDs. + * + * @param array $project_issue_nids + * List of project issue nids. + * + * @return array + * List of operation ids keyed by project issue node id. + */ +function versioncontrol_project_issue_get_issue_operation_ids($project_issue_nids) { + $vc_op_ids = array(); + $result = db_query('SELECT nid, vc_op_id FROM {versioncontrol_project_issue_operations} WHERE nid IN (:nids)', array(':nids' => $project_issue_nids)); + foreach ($result as $row) { + $vc_op_ids[$row->nid][] = $row->vc_op_id; + } + return $vc_op_ids; +}