diff --git a/includes/plugins/reposync/VersioncontrolGitRepositoryHistorySynchronizerDefault.class.php b/includes/plugins/reposync/VersioncontrolGitRepositoryHistorySynchronizerDefault.class.php index 32f8fce..6badf9d 100644 --- a/includes/plugins/reposync/VersioncontrolGitRepositoryHistorySynchronizerDefault.class.php +++ b/includes/plugins/reposync/VersioncontrolGitRepositoryHistorySynchronizerDefault.class.php @@ -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,47 +80,45 @@ 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) { $branch->delete(); } - // 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; @@ -130,10 +128,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); @@ -194,7 +198,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; @@ -217,27 +221,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; } /** @@ -355,9 +383,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)) { @@ -371,16 +403,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 @@ -478,10 +527,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) { @@ -509,7 +559,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; } @@ -558,6 +608,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)) { @@ -587,7 +638,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 diff --git a/tests/VersioncontrolGitDataIntegrityTests.test b/tests/VersioncontrolGitDataIntegrityTests.test index cb15744..62d8263 100644 --- a/tests/VersioncontrolGitDataIntegrityTests.test +++ b/tests/VersioncontrolGitDataIntegrityTests.test @@ -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.')); } diff --git a/tests/VersioncontrolGitPushTests.test b/tests/VersioncontrolGitPushTests.test index a6cba23..6423e2e 100644 --- a/tests/VersioncontrolGitPushTests.test +++ b/tests/VersioncontrolGitPushTests.test @@ -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; } diff --git a/tests/VersioncontrolGitTestCase.test b/tests/VersioncontrolGitTestCase.test index 818f9b7..ba1cc00 100644 --- a/tests/VersioncontrolGitTestCase.test +++ b/tests/VersioncontrolGitTestCase.test @@ -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.')); } }