Commit 15ae559e authored by Marco Villegas's avatar Marco Villegas
Browse files

Issue #1985426: Implement versioncontrol reposync plugin option to store...

Issue #1985426: Implement versioncontrol reposync plugin option to store tag/commit relations optionally.
parent 6f22c55c
Loading
Loading
Loading
Loading
+111 −60
Changes for includes/plugins/reposync/VersioncontrolGitRepositoryHistorySynchronizerDefault.class.php: 111 added lines, 60 removed lines.
Original line number Diff line number Diff line
@@ -34,6 +34,17 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco
    return $branches;
  }

  protected function loadTagsKeyed() {
    $tags = array();
    $tags_db = $this->repository->loadTags(array(), array(), array('may cache' => FALSE));

    foreach ($tags_db as $tag) {
      $tags[$tag->name] = $tag;
    }

    return $tags;
  }

  protected function loadCommitHashes($ids = array(), $conditions = array()) {
    $commits = $this->repository->loadCommits($ids, $conditions, array('may cache' => FALSE));

@@ -45,18 +56,7 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco
    return $revisions;
  }

  /**
   * Actually update the repository by fetching commits and other stuff
   * directly from the repository, invoking the git executable.
   * @param VersioncontrolGitRepository $repository
   * @return
   *   TRUE if the logs were updated, or FALSE if fetching and updating the logs
   *   failed for whatever reason.
   *
   * FIXME while largely ported to the new entities system, this is still not 100%
   * done
   */
  public function syncFull() {
  public function syncFull($options) {
    if (!$this->verify() || !$this->prepare()) {
      return FALSE;
    }
@@ -80,8 +80,8 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco
      $branch->insert();
    }

    // reload branches, after they was inserted
    $branches = $this->loadBranchesKeyed();
    // Reload branches, after they was inserted.
    $branches_db = $this->loadBranchesKeyed();

    // Deleted branches are removed, commits in them are not!
    foreach(array_diff_key($branches_db, $branches_repo) as $branch) {
@@ -94,39 +94,37 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco
      $this->repository->save(array('nested' => FALSE));
    }

    // 2. Process commits

    // Fetch commits from the repo and load them from the db.
    $commits_repo_hashes = $this->repository->fetchCommits();
    $commits_db_hashes = $this->loadCommitHashes();
    // 2. Process tags

    // Insert new commits in the database.
    foreach (array_diff($commits_repo_hashes, $commits_db_hashes) as $hash) {
      $this->parseAndInsertCommit($hash, $branches);
    }

    // 3. Process tags

    // Insert new tags in the database.
    // Fetch tags from the repository and load them from the db.
    $tags_in_repo = $this->repository->fetchTags();
    $tags_in_db = $this->repository->loadTags();
    $tags_in_db_by_name = array();
    foreach ($tags_in_db as $tag) {
      $tags_in_db_by_name[$tag->name] = $tag;
    }
    unset($tags_in_db);
    $tags_in_db_by_name = $this->loadTagsKeyed();

    // Check for new tags.
    $tags_new = array_diff_key($tags_in_repo, $tags_in_db_by_name);
    $tags_deleted = array_diff_key($tags_in_db_by_name, $tags_in_repo);
    if (!empty($tags_new)) {
      // Insert new tags in the database.
      $this->processTags($tags_new);
    }
    // Delete removed tags
    // Reload tags, after they was inserted.
    $tags_in_db_by_name = $this->loadTagsKeyed();
    // Delete removed tags.
    foreach($tags_deleted as $tag) {
      $tag->delete();
    }

    // 3. Process commits

    // Fetch commits from the repo and load them from the db.
    $commits_repo_hashes = $this->repository->fetchCommits();
    $commits_db_hashes = $this->loadCommitHashes();

    // Insert new commits in the database.
    foreach (array_diff($commits_repo_hashes, $commits_db_hashes) as $hash) {
      $this->parseAndInsertCommit($hash, $branches_db, $tags_in_db_by_name, $options);
    }

    $this->finalize();

    return TRUE;
@@ -136,10 +134,16 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco
   * Parse the output of 'git log' and insert a commit based on its data.
   *
   * @param VersioncontrolRepository $repository
   * @param array $logs The output of 'git log' to parse
   * @param array $branch_label_list An associative list of branchname => VersioncontrolBranch
   * @param array $hash
   *   A valid git commit hash.
   * @param array $branches
   *   A VersioncontrolBranch list keyed by branch name.
   * @param array $tags
   *   A VersioncontrolTag list keyed by tag name.
   * @param array $options
   *   See VersioncontrolRepositoryHistorySynchronizerInterface::options.
   */
  protected function parseAndInsertCommit($hash, $branches) {
  protected function parseAndInsertCommit($hash, $branches, $tags, $options) {
    $command = "show --numstat --summary --pretty=format:\"%H%n%P%n%an%n%ae%n%at%n%cn%n%ce%n%ct%n%B%nENDOFOUTPUTGITMESSAGEHERE\" " . escapeshellarg($hash);
    $logs = $this->execute($command);

@@ -200,7 +204,7 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco

    $op = new VersioncontrolGitOperation($this->repository->getBackend());
    $op->build($op_data);
    $op->labels = $this->getBranchesOfCommit($revision, $branches);
    $op->labels = $this->getCommitLabels($revision, $branches, $tags, $options);
    $op->insert(array('map users' => TRUE));

    $item_action = $merge ? VERSIONCONTROL_ACTION_MERGED : VERSIONCONTROL_ACTION_MODIFIED;
@@ -223,27 +227,51 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco
  }

  /**
   * Returns an array of all branches a given commit is in.
   * Returns an array of all branches and tags given commit is in.
   *
   * @param string $revision
   *   Git commit hash.
   * @param array $branch_label_list
   * @return VersioncontrolBranch
   *   A VersioncontrolBranch list keyed by branch name.
   * @param array $tag_label_list
   *   A VersioncontrolTag list keyed by tag name.
   * @param array $options
   *   See VersioncontrolRepositoryHistorySynchronizerInterface::options.
   *
   * @return array
   *   List of labels(VersioncontrolBranch, VersioncontrolTag) associated with
   *   the revision.
   */
  protected function getBranchesOfCommit($revision, $branch_label_list) {
  protected function getCommitLabels($revision, $branch_label_list, $tag_label_list, $options) {
    $labels = array();

    // Process branches.
    if ($options['operation_labels_mapping'] & VersioncontrolRepositoryHistorySynchronizerInterface::MAP_COMMIT_BRANCH_RELATIONS) {
      $exec = 'branch --no-color --contains ' . escapeshellarg($revision);
      $logs = $this->execute($exec);
    $branches = array();

      while (($line = next($logs)) !== FALSE) {
        $line = trim($line);
        if ($line[0] == '*') {
          $line = substr($line, 2);
        }
        if (!empty($branch_label_list[$line])) {
        $branches[] = $branch_label_list[$line];
          $labels[] = $branch_label_list[$line];
        }
      }
    }

    return $branches;
    // Process tags.
    if ($options['operation_labels_mapping'] & VersioncontrolRepositoryHistorySynchronizerInterface::MAP_COMMIT_TAG_RELATIONS) {
      $exec = 'tag -l --contains ' . escapeshellarg($revision);
      $logs = $this->execute($exec);
      while (($line = next($logs)) !== FALSE) {
        if (!empty($tag_label_list[$line])) {
          $labels[] = $tag_label_list[$line];
        }
      }
    }

    return $labels;
  }

  /**
@@ -361,9 +389,13 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco

  /**
   * Does all processing to insert the tags in $tags_new in the database.
   *
   * @param VersioncontrolGitRepository $repository
   * @param array $tags_new All new tags.
   * @return none
   * @param array $tags_new
   *   All new tags.
   *
   * @return
   *   None.
   */
  function processTags($tags_new) {
    if (empty($tags_new)) {
@@ -377,16 +409,33 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco
      $exec = "for-each-ref --format=\"$format\" refs/tags/" . escapeshellarg($tag_name);
      $logs_tag_msg = $this->execute($exec);

      $tag_ops = $this->repository->loadCommits(array(), array('revision' => $tag_commit));
      $tagged_commit_op = reset($tag_ops);
      // Get the specific tag data for annotated vs not annotated tags.
      if ($logs_tag_msg[1] == 'commit') {
        // simple tag
        // [2] is tagged commit [3] tagname [4] and [5] empty [6] commit log message
        // We get the tagger, the tag_date and the tag_message from the tagged commit.
        $tagger = $tagged_commit_op->author;
        $tag_date = $tagged_commit_op->date + 1;
        $message = $tagged_commit_op->message;
        // [2] is tagged commit [3] tagname [4] and [5] empty [6] commit log
        // message.
        // We get the tagger, the tag_date and the tag_message from the tagged
        // commit.
        $command = "rev-list -1 --pretty=format:\"%an%n%at%n%BENDOFGITTAGOUTPUTMESAGEHERE\" " . escapeshellarg($hash);
        $logs = $this->execute($command);
        $line = next($logs);
        $commits[] = trim($line);
        $tagger = next($logs);
        $tag_date = next($logs);
        $message = '';
        $body_start = TRUE;
        while (($line = next($logs)) !== FALSE) {
          if ($line == 'ENDOFGITTAGOUTPUTMESAGEHERE') {
            break;
          }
          if ($body_start) {
            $message .= "$line";
            $body_start = FALSE;
          }
          else {
            $message .= "\n$line";
          }
        }
      }
      elseif ($logs_tag_msg[1] == 'tag') {
        // annotated tag
@@ -484,10 +533,11 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco
    return $this->repository->exec($command);
  }

  public function syncInitial() {
    // TODO halstead's optimized fast-export-based parser goes here. But for now, just use the same old crap

    return $this->syncFull();
  public function syncInitial($options) {
    // @todo halstead's optimized fast-export-based parser goes here. But for
    // now, just use the same old crap.
    $sync_options = $this->repository->getSynchronizerOptions();
    return $this->syncFull($sync_options);
  }

  protected function getCommitInterval($start, $end) {
@@ -515,7 +565,7 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco
    return $commit_hashes;
  }

  public function syncEvent(VersioncontrolEvent $event) {
  public function syncEvent(VersioncontrolEvent $event, $options) {
    if (!$this->verify() || !$this->prepare()) {
      return FALSE;
    }
@@ -564,6 +614,7 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco
      if (VERSIONCONTROL_LABEL_BRANCH == $label->type && !$ref->eventDeletedMe()) {
        // TODO replace this with a targeted load based on the $event
        $branches_db = $this->loadBranchesKeyed();
        $tags_db = $this->loadTagsKeyed();
        $commit_hashes = $this->getCommitInterval($ref->old_sha1, $ref->new_sha1);

        if (empty($commit_hashes)) {
@@ -593,7 +644,7 @@ class VersioncontrolGitRepositoryHistorySynchronizerDefault implements Versionco
        }
        // Add commits not in DB yet.
        foreach (array_diff($commit_hashes, $interval_revisions_in_db) as $hash) {
          $this->parseAndInsertCommit($hash, $branches_db);
          $this->parseAndInsertCommit($hash, $branches_db, $tags_db, $options);
        }

        // Only on non-fast-forward, we look for commits not anymore related
+2 −2
Changes for tests/VersioncontrolGitDataIntegrityTests.test: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ class VersioncontrolGitDataIntegrityTests extends VersioncontrolGitTestCase {

    $this->assertEqual($op_count, 32, t('Correct number of operations in {versioncontrol_operations} table after non-bypassing purge.'));
    $this->assertEqual($git_op_count, $op_count, t('Equal number of {versioncontrol_git_operations} and {versioncontrol_operations} records after non-bypassing purge.'));
    $this->assertEqual($op_label_count, 90, t('Correct number of operation/label mappings in {versioncontrol_operation_labels} after non-bypassing purge.'));
    $this->assertEqual($op_label_count, 102, t('Correct number of operation/label mappings in {versioncontrol_operation_labels} after non-bypassing purge.'));

    $this->assertEqual($label_count, 14, t('Correct number of labels in {versioncontrol_labels} after non-bypassing purge.'));
  }
@@ -82,7 +82,7 @@ class VersioncontrolGitDataIntegrityTests extends VersioncontrolGitTestCase {

    $this->assertEqual($op_count, 32, t('Correct number of operations in {versioncontrol_operations} table after bypassing purge.'));
    $this->assertEqual($git_op_count, $op_count, t('Equal number of {versioncontrol_git_operations} and {versioncontrol_operations} records after bypassing purge.'));
    $this->assertEqual($op_label_count, 90, t("Correct number of operation/label mappings in {versioncontrol_operation_labels} after bypassing purge."));
    $this->assertEqual($op_label_count, 102, t("Correct number of operation/label mappings in {versioncontrol_operation_labels} after bypassing purge. ($op_label_count)"));

    $this->assertEqual($label_count, 14, t('Correct number of labels in {versioncontrol_labels} after bypassing purge.'));
  }
+6 −5
Changes for tests/VersioncontrolGitPushTests.test: 6 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -243,10 +243,11 @@ class VersioncontrolGitPushTests extends VersioncontrolGitTestCase {
    $data = '28e2c35b6f7626d662ffda2c344a782e639f76e7 0f0280aefe4d0c3384cb04a0bbb15c9543f46a69 refs/heads/enhancements' . PHP_EOL;
    $event = $this->versioncontrolSimulatePush($data, array('repository' => $this->repository), TRUE, FALSE);

    // first, grab the syncer to directly ensure we get the expected exception.
    // First, grab the syncer to directly ensure we get the expected exception.
    $sync_options = $this->repository->getSynchronizerOptions();
    $sync = $this->repository->getSynchronizer();
    try {
      $sync->syncEvent($event);
      $sync->syncEvent($event, $sync_options);
      $this->fail('Synchronization plugin did not ask for full sync via exception.');
    }
    catch (VersioncontrolNeedsFullSynchronizationException $e) {
@@ -254,9 +255,9 @@ class VersioncontrolGitPushTests extends VersioncontrolGitTestCase {
      $this->repository->unlock();
    }

    // now just make it go with syncFull
    $success = $sync->syncFull();
    if (empty($success)) {
    // Now just make it go with syncFull.
    $success = $sync->syncFull($sync_options);
    if ($success !== TRUE) {
      $this->fail('Repository method reported that synchronization failed.');
      return FALSE;
    }
+2 −2
Changes for tests/VersioncontrolGitTestCase.test: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -124,7 +124,7 @@ abstract class VersioncontrolGitTestCase extends VersioncontrolTestCase {
   *  - 4 branches - master, enhancements, feature, fixes
   *  - 3 tags - simple, signed, annotated (each having the characteristics
   *    for which they're named)
   *  - 45 commit/label mappings
   *  - 51 commit/label mappings
   *
   * @param VersioncontrolGitRepository $repository
   *   The VersioncontrolGitRepository object representing the test repository
@@ -141,7 +141,7 @@ abstract class VersioncontrolGitTestCase extends VersioncontrolTestCase {
      $this->assertEqual(count($repository->loadBranches()), 4, t('Sync of test repository resulted in correct number of branches.'));
      $this->assertEqual(count($repository->loadTags()), 3, t('Sync of test repository resulted in correct number of tags.'));
      $op_label_count = db_query('SELECT COUNT(*) FROM {versioncontrol_operation_labels} vol INNER JOIN {versioncontrol_labels} vl ON vol.label_id = vl.label_id WHERE vl.repo_id = :repo_id', array('repo_id' => $repository->repo_id))->fetchField();
      $this->assertEqual($op_label_count, 45, t('Sync of test repository resulted in correct number of commit/label mappings.'));
      $this->assertEqual($op_label_count, 51, t('Sync of test repository resulted in correct number of commit/label mappings.'));
    }
  }