Commit 4bf1215c authored by Neil Drumm's avatar Neil Drumm 👋
Browse files

Issue #3266212 by drumm: Use GitLab API call to list tags/branches for creating releases

parent c29690c2
Loading
Loading
Loading
Loading
+273 −0
Original line number Diff line number Diff line
@@ -1025,6 +1025,279 @@ function drupalorg_field_access($op, $field, $entity_type, $entity, $account) {
  return TRUE;
}

/**
 * Implements hook_field_widget_info().
 */
function drupalorg_field_widget_info() {
  return [
    'drupalorg_vcs_label' => [
      'label' => t('VCS label selection'),
      'field types' => ['text'],
      'behaviors' => [
        'default value' => FIELD_BEHAVIOR_NONE,
      ],
    ],
  ];
}

/**
 * Implements hook_field_widget_form().
 */
function drupalorg_field_widget_form($form, $form_state, $field, $instance, $langcode, $items, $delta, $element) {
  if ($instance['widget']['type'] === 'drupalorg_vcs_label') {
    if (empty($element['#entity']->nid)) {
      // New release, make a select menu of options.
      $element['#type'] = 'select';
      $project = node_load(project_release_get_release_project_nid($element['#entity']));
      $repository = versioncontrol_project_repository_load($project->nid);
      $pager = $repository->getApiPager();
      $repositories_api = $repository->getApiClient()->api('repositories');

      // Get existing releases.
      $existing_releases = [];
      $releases = (new EntityFieldQuery())
        ->entityCondition('entity_type', 'node')
        ->entityCondition('bundle', project_release_release_node_types())
        ->fieldCondition('field_release_project', 'target_id', $project->nid)
        ->execute();
      if (isset($releases['node'])) {
        foreach (node_load_multiple(array_keys($releases['node'])) as $release) {
          $vcs_label = $release->field_release_vcs_label[LANGUAGE_NONE][0]['value'];
          $existing_releases[$vcs_label] = $vcs_label;
        }
      }

      try {
        // Get branches & tags not associated with an existing release.
        $potential_branches = array_diff_key(array_column($pager->fetchAll($repositories_api, 'branches', [$repository->gitlab_project_id, ['per_page' => 100]]), 'name', 'name'), $existing_releases);
        $potential_tags = array_diff_key(array_column($pager->fetchAll($repositories_api, 'tags', [$repository->gitlab_project_id, ['per_page' => 100]]), 'name', 'name'), $existing_releases);
      }
      catch (Exception $e) {
        form_set_error($element['#field_name'], t('Unable to fetch Git branches and tags!'));
        $element['#options'] = [];
        return ['value' => $element];
      }

      if ($project->type === 'project_core') {
        foreach ($potential_branches as $label => &$value) {
          // Core branches can be any of these:
          // 4.7.x
          // 7.x
          // 8.1.x
          // 10.x
          if (preg_match('/^(\d+)(\.(\d+))?\.x$/', $label, $matches)) {
            $value = [
              'major' => $matches[1],
            ];
            if (isset($matches[3]) && ($matches[1] <= 4 || $matches[1] >= 8)) {
              $value['minor'] = $matches[3];
            }
            // The whole thing should match the API compatibility term, for 7 and lower.
            if ($value['major'] <= 7) {
              $value['api'] = $label;
            }
            // Only use 8.x for 8. 9 and later no longer have API compatibility.
            elseif ($value['major'] == 8) {
              $value['api'] = $value['major'] . '.x';
            }
          }
          else {
            // Unrecognized core branch format.
            unset($potential_branches[$label]);
          }
        }
        foreach ($potential_tags as $label => &$value) {
          // Core versions can be any of the following:
          // 4.7.0-beta3
          // 4.7.0
          // 6.18
          // 7.0-alpha3
          // 7.0
          if (preg_match('/^(\d+)\.(\d+)(\.(\d+))?(-((alpha|beta|rc)\d+))?$/', $label, $matches)) {
            // Starting with version 5 through version 8.0 alphas, we only had 2
            // digits, major, and minor.
            $value = [
              'major' => $matches[1],
              'minor' => $matches[2],
            ];
            if (($value['major'] >= 5 && $value['major'] <= 7) || ($value['major'] == 8 && $matches[4] == '')) {
              $value['api'] = $value['major'] . '.x';
            }
            // Prior to 5 and from 8 onward we have all 3: major, minor and patch.
            else {
              // Match 4 contains the patch level without the leading '.'.
              if (isset($matches[4])) {
                $value['patch'] = $matches[4];
              }
              if ($value['major'] <= 4) {
                // For version 4 and prior, the API compatibility prefix had 3 components
                // e.g., 4.4.x-1.0
                $value['api'] = $value['major'] . '.' . $value['minor'] . '.x';
              }
              elseif ($value['major'] <= 8) {
                // For version 5 through 8, the API compatibility prefix has 2
                // components e.g., 8.x-1.0. 9 and later no longer have API
                // compatibility.
                $value['api'] = $value['major'] . '.x';
              }
            }
            // Match 6 contains the version extra without the leading '-'.
            if (!empty($matches[6])) {
              $value['extra'] = $matches[6];
            }
          }
          else {
            // Unrecognized core tag format.
            unset($potential_tags[$label]);
          }
        }
      }
      else {
        foreach ($potential_branches as $label => &$value) {
          // Contrib branches can be any of these:
          // 4.7.x-1.x
          // 5.x-2.x
          // 6.x-10.x
          // 3.x
          // 3.1.x
          if (preg_match('/^(?<api_term>\d+(?:\.\d+)?\.x)-(?<major>\d+)\.x$/', $label, $matches)) {
            $value = [
              'major' => $matches['major'],
              'api' => $matches['api_term'],
            ];
          }
          elseif (preg_match('/^(?<major>\d+)(?:\.(?<minor>\d+))?\.x$/', $label, $matches)) {
            $value = [
              'major' => $matches['major'],
            ];
            if (isset($matches['minor'])) {
              $value['minor'] = $matches['minor'];
            }
          }
          else {
            // Unrecognized non-core branch format.
            unset($potential_branches[$label]);
          }
        }
        foreach ($potential_tags as $label => &$value) {
          // Contrib versions can be any of the following:
          // 4.7.x-1.0-beta2
          // 4.7.x-1.0
          // 6.x-1.0-alpha1
          // 6.x-1.3
          // 6.x-10.0-alpha3
          // 3.0.0
          // 3.0.1-rc2
          if (preg_match('/^(?<api_term>\d+(?:\.\d+)?\.x)-(?<major>\d+)\.(?<minor>\d+)(-(?<extra>(?:alpha|beta|rc)\d+))?$/', $label, $matches)) {
            $value = [
              'major' => $matches['major'],
              'minor' => $matches['minor'],
              'api' => $matches['api_term'],
            ];
            if (!empty($matches['extra'])) {
              $value['extra'] = $matches['extra'];
            }
          }
          elseif (preg_match('/^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(-(?<extra>(?:alpha|beta|rc)\d+))?$/', $label, $matches)) {
            $value = [
              'major' => $matches['major'],
              'minor' => $matches['minor'],
              'patch' => $matches['patch'],
            ];
            if (!empty($matches['extra'])) {
              $value['extra'] = $matches['extra'];
            }
          }
          else {
            // Unrecognized non-core tag format.
            unset($potential_tags[$label]);
          }
        }
      }
      foreach ($potential_branches as $label => &$value) {
        // Set the least significant regular component to 'x', and
        // extra to 'dev' for branches.
        if (isset($value['minor'])) {
          $value['patch'] = 'x';
        }
        else {
          $value['minor'] = 'x';
        }
        $value['extra'] = 'dev';
      }
      _drupalorg_process_vcs_label_options($potential_branches, $element['#entity']);
      _drupalorg_process_vcs_label_options($potential_tags, $element['#entity']);
      $element['#options'] = ['' => t('- Select -')];
      if (!empty($potential_branches)) {
        $element['#options'][t('Branches')] = array_column($potential_branches, 'caption', 'label');
      }
      if (!empty($potential_tags)) {
        $element['#options'][t('Tags')] = array_column($potential_tags, 'caption', 'label');
      }
      $element['#drupalorg_version_data'] = array_merge($potential_branches, $potential_tags);

      if (count($element['#options']) <= 1) {
        // There are no branhes or tags available to make a release.
        $msg = '<strong>' . t('No valid branches or tags found.') . '</strong>';
        $msg .= '<p>' . t('Your release needs to have a tag or branch that follows the <a href="http://drupal.org/node/1015226">naming conventions</a> and have no other release attached to it.') . '</p>';
        $msg .= '<p>' . t('Branches are for dev releases; tags are for alpha, beta, and stable releases etc.') . '</p>';
        $msg .= '<p>' . t('Valid release <strong>branch</strong> examples:') . '</p>';
        $msg .= theme('item_list', ['items' => ['1.0.x', '1.3.x', '8.x-1.x', '7.x-1.x']]);
        $msg .= '<p>' . t('Valid release <strong>tag</strong> examples:') . '</p>';
        $msg .= theme('item_list', ['items' => ['1.0.2', '1.0.2-alpha4', '1.2.1-beta6', '8.x-1.2', '8.x-2.0-alpha2', '8.x-2.0-beta3']]);
        $msg .= '<p>' . t('For more help, see <a href="http://drupal.org/node/1066342">Creating a Tag or Branch in Git</a> or the <a href="@git_instructions_url">Git instructions</a> page for your project.', ['@git_instructions_url' => url('node/' . $project->nid . '/git-instructions')]) . '</p>';
        $msg .= '<p>' . t('Once you have added a valid branch or tag, <a href="@retry">try again</a>.', ['@retry' => url('node/add/project-release/' . $project->nid)]) . '</p>';
        drupal_set_message($msg, 'warning');
      }
    }
  }

  return ['value' => $element];
}

/**
 * Helper function, adds metadata to VCS label options.
 */
function _drupalorg_process_vcs_label_options(array &$labels, stdClass $release) {
  $api_vocabulary = taxonomy_vocabulary_load(variable_get('project_release_api_vocabulary', ''))->machine_name;
  foreach ($labels as $label => &$value) {
    // Check for API terms.
    if (isset($value['api'])) {
      if ($api_tids = array_keys(taxonomy_get_term_by_name($value['api'], $api_vocabulary))) {
        list($value['api_tid']) = $api_tids;
      }
      else {
        // API term was not valid, so the version string is not valid.
        unset($labels[$label]);
        continue;
      }
    }
    // Add version string.
    $value['version'] = '';
    foreach (project_release_get_version_format($release) as $part) {
      if (is_array($part)) {
        if (isset($value[$part['label']])) {
          if ($value['version'] !== '') {
            $value['version'] .= $part['delimiter'];
          }
          $value['version'] .= $value[$part['label']];
        }
      }
      else {
        $value['version'] .= $part;
      }
    }
    // Add caption.
    if ($label !== $value['version']) {
      $value['caption'] = t('!label (!version)', ['!label' => $label, '!version' => $value['version']]);
    }
    else {
      $value['caption'] = $label;
    }
    $value['label'] = $label;
  }
}

/**
 * Implements hook_field_widget_WIDGET_TYPE_form_alter().
 */
+103 −37

File changed.

Preview size limit exceeded, changes collapsed.

+0 −20
Original line number Diff line number Diff line
@@ -187,26 +187,6 @@ function drupalorg_versioncontrol_project_promote_sandbox($project) {
  }
}

/**
 * Implements hook_form_alter().
 */
function drupalorg_versioncontrol_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'project_release_node_form' && !empty($form['error']) && !empty($form['retry'])) {
    $project = node_load(project_release_get_release_project_nid($form['#node']));
    $msg = '<strong>' . t('No valid branches or tags found.') . '</strong>';
    $msg .= '<p>' . t('Your release needs to have a tag or branch that follows the <a href="http://drupal.org/node/1015226">naming conventions</a> and have no other release attached to it.') . '</p>';
    $msg .= '<p>' . t('Branches are for dev releases; tags are for alpha, beta, and stable releases etc.') . '</p>';
    $msg .= '<p>' . t('Valid release <strong>branch</strong> examples:') . '</p>';
    $msg .= theme('item_list', ['items' => ['1.0.x', '1.3.x', '8.x-1.x', '7.x-1.x']]);
    $msg .= '<p>' . t('Valid release <strong>tag</strong> examples:') . '</p>';
    $msg .= theme('item_list', ['items' => ['1.0.2', '1.0.2-alpha4', '1.2.1-beta6', '8.x-1.2', '8.x-2.0-alpha2', '8.x-2.0-beta3']]);
    $msg .= '<p>' . t('For more help, see <a href="http://drupal.org/node/1066342">Creating a Tag or Branch in Git</a> or the <a href="@git_instructions_url">Git instructions</a> page for your project.', array('@git_instructions_url' => url('node/' . $project->nid . '/git-instructions'))) . '</p>';
    $msg .= '<p>' . t('Once you have added a valid branch or tag, <a href="@retry">try again</a>.', array('@retry' => url('node/add/project-release/' . $project->nid))) . '</p>';
    $form['error']['#markup'] = $msg;
    unset($form['retry']);
  }
}

/**
 * Implements hook_versioncontrol_project_issue_git_issue_notification_project_issue_nids_alter().
 */
+20 −23
Original line number Diff line number Diff line
@@ -9818,11 +9818,7 @@ function drupalorg_projects_field_default_field_instances() {
  // Exported field_instance: 'node-project_release-field_release_build_type'.
  $field_instances['node-project_release-field_release_build_type'] = array(
    'bundle' => 'project_release',
    'default_value' => array(
      0 => array(
        'value' => 'static',
      ),
    ),
    'default_value' => array(),
    'deleted' => 0,
    'description' => 'How is this release built? Can be either \'Static\' if the files associated with it are built once and remain unchanged (e.g. an official release from a tag), or \'Dynamic\' if the files are regularly rebuilt and updated (e.g. a development snapshot rebuilt from the end of a branch).',
    'display' => array(
@@ -9878,9 +9874,9 @@ function drupalorg_projects_field_default_field_instances() {
    ),
    'widget' => array(
      'active' => 1,
      'module' => 'options',
      'module' => 'field_extrawidgets',
      'settings' => array(),
      'type' => 'options_select',
      'type' => 'field_extrawidgets_hidden',
      'weight' => 6,
    ),
  );
@@ -10274,9 +10270,9 @@ function drupalorg_projects_field_default_field_instances() {
  // Exported field_instance: 'node-project_release-field_release_vcs_label'.
  $field_instances['node-project_release-field_release_vcs_label'] = array(
    'bundle' => 'project_release',
    'default_value' => NULL,
    'default_value' => array(),
    'deleted' => 0,
    'description' => 'The label (branch or tag) in a version control system that represents this release.',
    'description' => 'Select the Git branch or tag for this release.',
    'display' => array(
      'default' => array(
        'label' => 'hidden',
@@ -10325,27 +10321,25 @@ function drupalorg_projects_field_default_field_instances() {
    ),
    'entity_type' => 'node',
    'field_name' => 'field_release_vcs_label',
    'label' => 'VCS Label',
    'required' => 0,
    'label' => 'Release branch or tag',
    'required' => 1,
    'settings' => array(
      'text_processing' => 0,
      'user_register_form' => FALSE,
    ),
    'widget' => array(
      'active' => 1,
      'module' => 'text',
      'settings' => array(
        'size' => 60,
      ),
      'type' => 'text_textfield',
      'weight' => 5,
      'active' => 0,
      'module' => 'drupalorg',
      'settings' => array(),
      'type' => 'drupalorg_vcs_label',
      'weight' => -1,
    ),
  );

  // Exported field_instance: 'node-project_release-field_release_version'.
  $field_instances['node-project_release-field_release_version'] = array(
    'bundle' => 'project_release',
    'default_value' => NULL,
    'default_value' => array(),
    'deleted' => 0,
    'description' => '',
    'display' => array(
@@ -10402,11 +10396,14 @@ function drupalorg_projects_field_default_field_instances() {
    ),
    'widget' => array(
      'active' => 1,
      'module' => 'text',
      'module' => 'field_extrawidgets',
      'settings' => array(
        'display_empty' => 0,
        'formatter' => '',
        'formatter_settings' => array(),
        'size' => 60,
      ),
      'type' => 'text_textfield',
      'type' => 'field_extrawidgets_hidden',
      'weight' => 3,
    ),
  );
@@ -16582,6 +16579,7 @@ function drupalorg_projects_field_default_field_instances() {
  t('Project type');
  t('Recommended');
  t('Release Category');
  t('Release branch or tag');
  t('Release file');
  t('Release file SHA-1 hash');
  t('Release file SHA-256 hash');
@@ -16592,6 +16590,7 @@ function drupalorg_projects_field_default_field_instances() {
  t('Replaced by');
  t('Screenshots');
  t('Security advisory coverage');
  t('Select the Git branch or tag for this release.');
  t('Short description');
  t('Short name');
  t('Show in project’s download table when branch is supported');
@@ -16605,12 +16604,10 @@ function drupalorg_projects_field_default_field_instances() {
<li>Fund the port of this project at [link]</li>
<li>Use this project instead: [link]</li>
</ul>');
  t('The label (branch or tag) in a version control system that represents this release.');
  t('Update status');
  t('Use this field to indicate that this project\'s primary purpose is to provide enhancement for one or more other projects, and thus forms part of an ecosystem around it. For example, if your project provides plugins for Views, or adds functionality to Organic Groups, and so on. Projects that are referred to here will gain a list of \'ecosystem modules\' on their project page, allowing users to easily find modules to extend their functionality.');
  t('Used to classify different aspects of a project, eg. a software project might have <em>Code</em>, <em>User interface</em>, and <em>Documentation</em> components.');
  t('Used to set the initial default component for new project issues.');
  t('VCS Label');
  t('Version');
  t('Version extra');
  t('Version extra delta');