Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/differential/controller/DifferentialRevisionOperationController.php b/src/applications/differential/controller/DifferentialRevisionOperationController.php
index 593815f15b..feeb6770b8 100644
--- a/src/applications/differential/controller/DifferentialRevisionOperationController.php
+++ b/src/applications/differential/controller/DifferentialRevisionOperationController.php
@@ -1,167 +1,157 @@
<?php
final class DifferentialRevisionOperationController
extends DifferentialController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($id))
->setViewer($viewer)
->needActiveDiffs(true)
->executeOne();
if (!$revision) {
return new Aphront404Response();
}
$detail_uri = "/D{$id}";
$op = new DrydockLandRepositoryOperation();
$barrier = $op->getBarrierToLanding($viewer, $revision);
if ($barrier) {
return $this->newDialog()
->setTitle($barrier['title'])
->appendParagraph($barrier['body'])
->addCancelButton($detail_uri);
}
$diff = $revision->getActiveDiff();
$repository = $revision->getRepository();
$default_ref = $this->loadDefaultRef($repository, $diff);
if ($default_ref) {
$v_ref = array($default_ref->getPHID());
} else {
$v_ref = array();
}
$e_ref = true;
$errors = array();
if ($request->isFormPost()) {
$v_ref = $request->getArr('refPHIDs');
$ref_phid = head($v_ref);
if (!strlen($ref_phid)) {
$e_ref = pht('Required');
$errors[] = pht(
'You must select a branch to land this revision onto.');
} else {
$ref = $this->newRefQuery($repository)
->withPHIDs(array($ref_phid))
->executeOne();
if (!$ref) {
$e_ref = pht('Invalid');
$errors[] = pht(
'You must select a branch from this repository to land this '.
'revision onto.');
}
}
if (!$errors) {
// NOTE: The operation is locked to the current active diff, so if the
// revision is updated before the operation applies nothing sneaky
// occurs.
$target = 'branch:'.$ref->getRefName();
$operation = DrydockRepositoryOperation::initializeNewOperation($op)
->setAuthorPHID($viewer->getPHID())
->setObjectPHID($revision->getPHID())
->setRepositoryPHID($repository->getPHID())
->setRepositoryTarget($target)
->setProperty('differential.diffPHID', $diff->getPHID());
$operation->save();
$operation->scheduleUpdate();
return id(new AphrontRedirectResponse())
->setURI($detail_uri);
}
}
$ref_datasource = id(new DiffusionRefDatasource())
->setParameters(
array(
'repositoryPHIDs' => array($repository->getPHID()),
'refTypes' => $this->getTargetableRefTypes(),
));
$form = id(new AphrontFormView())
->setUser($viewer)
->appendRemarkupInstructions(
pht(
'(NOTE) This feature is new and experimental.'))
->appendControl(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Onto Branch'))
->setName('refPHIDs')
->setLimit(1)
->setError($e_ref)
->setValue($v_ref)
->setDatasource($ref_datasource));
return $this->newDialog()
->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle(pht('Land Revision'))
->setErrors($errors)
->appendForm($form)
->addCancelButton($detail_uri)
->addSubmitButton(pht('Land Revision'));
}
private function newRefQuery(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
return id(new PhabricatorRepositoryRefCursorQuery())
->setViewer($viewer)
->withRepositoryPHIDs(array($repository->getPHID()))
->withRefTypes($this->getTargetableRefTypes());
}
private function getTargetableRefTypes() {
return array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
);
}
private function loadDefaultRef(
PhabricatorRepository $repository,
DifferentialDiff $diff) {
$default_name = $this->getDefaultRefName($repository, $diff);
if (!strlen($default_name)) {
return null;
}
- // NOTE: See PHI68. This is a workaround to make "Land Revision" work
- // until T11823 is fixed properly. If we find multiple refs with the same
- // name (normally, duplicate "master" refs), just pick the first one.
-
- $refs = $this->newRefQuery($repository)
+ return $this->newRefQuery($repository)
->withRefNames(array($default_name))
- ->execute();
-
- if ($refs) {
- return head($refs);
- }
-
- return null;
+ ->executeOne();
}
private function getDefaultRefName(
PhabricatorRepository $repository,
DifferentialDiff $diff) {
$onto = $diff->loadTargetBranch();
if ($onto !== null) {
return $onto;
}
return $repository->getDefaultBranch();
}
}
diff --git a/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php b/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php
index 55e86ce879..22ff1d61f0 100644
--- a/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php
+++ b/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php
@@ -1,185 +1,187 @@
<?php
/**
* Resolves references into canonical, stable commit identifiers by examining
* database caches.
*
* This is a counterpart to @{class:DiffusionLowLevelResolveRefsQuery}. This
* query offers fast resolution, but can not resolve everything that the
* low-level query can.
*
* This class can resolve the most common refs (commits, branches, tags) and
* can do so cheapy (by examining the database, without needing to make calls
* to the VCS or the service host).
*/
final class DiffusionCachedResolveRefsQuery
extends DiffusionLowLevelQuery {
private $refs;
private $types;
public function withRefs(array $refs) {
$this->refs = $refs;
return $this;
}
public function withTypes(array $types) {
$this->types = $types;
return $this;
}
protected function executeQuery() {
if (!$this->refs) {
return array();
}
switch ($this->getRepository()->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$result = $this->resolveGitAndMercurialRefs();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$result = $this->resolveSubversionRefs();
break;
default:
throw new Exception(pht('Unsupported repository type!'));
}
if ($this->types !== null) {
$result = $this->filterRefsByType($result, $this->types);
}
return $result;
}
/**
* Resolve refs in Git and Mercurial repositories.
*
* We can resolve commit hashes from the commits table, and branch and tag
* names from the refcursor table.
*/
private function resolveGitAndMercurialRefs() {
$repository = $this->getRepository();
$conn_r = $repository->establishConnection('r');
$results = array();
$prefixes = array();
foreach ($this->refs as $ref) {
// We require refs to look like hashes and be at least 4 characters
// long. This is similar to the behavior of git.
if (preg_match('/^[a-f0-9]{4,}$/', $ref)) {
$prefixes[] = qsprintf(
$conn_r,
'(commitIdentifier LIKE %>)',
$ref);
}
}
if ($prefixes) {
$commits = queryfx_all(
$conn_r,
'SELECT commitIdentifier FROM %T
WHERE repositoryID = %s AND %Q',
id(new PhabricatorRepositoryCommit())->getTableName(),
$repository->getID(),
implode(' OR ', $prefixes));
foreach ($commits as $commit) {
$hash = $commit['commitIdentifier'];
foreach ($this->refs as $ref) {
if (!strncmp($hash, $ref, strlen($ref))) {
$results[$ref][] = array(
'type' => 'commit',
'identifier' => $hash,
);
}
}
}
}
$name_hashes = array();
foreach ($this->refs as $ref) {
$name_hashes[PhabricatorHash::digestForIndex($ref)] = $ref;
}
$cursors = queryfx_all(
$conn_r,
- 'SELECT refNameHash, refType, commitIdentifier, isClosed FROM %T
- WHERE repositoryPHID = %s AND refNameHash IN (%Ls)',
+ 'SELECT c.refNameHash, c.refType, p.commitIdentifier, p.isClosed
+ FROM %T c JOIN %T p ON p.cursorID = c.id
+ WHERE c.repositoryPHID = %s AND c.refNameHash IN (%Ls)',
id(new PhabricatorRepositoryRefCursor())->getTableName(),
+ id(new PhabricatorRepositoryRefPosition())->getTableName(),
$repository->getPHID(),
array_keys($name_hashes));
foreach ($cursors as $cursor) {
if (isset($name_hashes[$cursor['refNameHash']])) {
$results[$name_hashes[$cursor['refNameHash']]][] = array(
'type' => $cursor['refType'],
'identifier' => $cursor['commitIdentifier'],
'closed' => (bool)$cursor['isClosed'],
);
// TODO: In Git, we don't store (and thus don't return) the hash
// of the tag itself. It would be vaguely nice to do this.
}
}
return $results;
}
/**
* Resolve refs in Subversion repositories.
*
* We can resolve all numeric identifiers and the keyword `HEAD`.
*/
private function resolveSubversionRefs() {
$repository = $this->getRepository();
$max_commit = id(new PhabricatorRepositoryCommit())
->loadOneWhere(
'repositoryID = %d ORDER BY epoch DESC, id DESC LIMIT 1',
$repository->getID());
if (!$max_commit) {
// This repository is empty or hasn't parsed yet, so none of the refs are
// going to resolve.
return array();
}
$max_commit_id = (int)$max_commit->getCommitIdentifier();
$results = array();
foreach ($this->refs as $ref) {
if ($ref == 'HEAD') {
// Resolve "HEAD" to mean "the most recent commit".
$results[$ref][] = array(
'type' => 'commit',
'identifier' => $max_commit_id,
);
continue;
}
if (!preg_match('/^\d+$/', $ref)) {
// This ref is non-numeric, so it doesn't resolve to anything.
continue;
}
// Resolve other commits if we can deduce their existence.
// TODO: When we import only part of a repository, we won't necessarily
// have all of the smaller commits. Should we fail to resolve them here
// for repositories with a subpath? It might let us simplify other things
// elsewhere.
if ((int)$ref <= $max_commit_id) {
$results[$ref][] = array(
'type' => 'commit',
'identifier' => (int)$ref,
);
}
}
return $results;
}
}
diff --git a/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php b/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php
index af07ea052a..4b1cd829d5 100644
--- a/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php
+++ b/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php
@@ -1,494 +1,612 @@
<?php
/**
* Update the ref cursors for a repository, which track the positions of
* branches, bookmarks, and tags.
*/
final class PhabricatorRepositoryRefEngine
extends PhabricatorRepositoryEngine {
- private $newRefs = array();
- private $deadRefs = array();
+ private $newPositions = array();
+ private $deadPositions = array();
private $closeCommits = array();
private $hasNoCursors;
public function updateRefs() {
- $this->newRefs = array();
- $this->deadRefs = array();
+ $this->newPositions = array();
+ $this->deadPositions = array();
$this->closeCommits = array();
$repository = $this->getRepository();
+ $viewer = $this->getViewer();
$branches_may_close = false;
$vcs = $repository->getVersionControlSystem();
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
// No meaningful refs of any type in Subversion.
$maps = array();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$branches = $this->loadMercurialBranchPositions($repository);
$bookmarks = $this->loadMercurialBookmarkPositions($repository);
$maps = array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH => $branches,
PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => $bookmarks,
);
$branches_may_close = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$maps = $this->loadGitRefPositions($repository);
break;
default:
throw new Exception(pht('Unknown VCS "%s"!', $vcs));
}
// Fill in any missing types with empty lists.
$maps = $maps + array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH => array(),
PhabricatorRepositoryRefCursor::TYPE_TAG => array(),
PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => array(),
PhabricatorRepositoryRefCursor::TYPE_REF => array(),
);
$all_cursors = id(new PhabricatorRepositoryRefCursorQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->setViewer($viewer)
->withRepositoryPHIDs(array($repository->getPHID()))
+ ->needPositions(true)
->execute();
$cursor_groups = mgroup($all_cursors, 'getRefType');
$this->hasNoCursors = (!$all_cursors);
// Find all the heads of closing refs.
$all_closing_heads = array();
foreach ($all_cursors as $cursor) {
- if ($this->shouldCloseRef($cursor->getRefType(), $cursor->getRefName())) {
- $all_closing_heads[] = $cursor->getCommitIdentifier();
+ $should_close = $this->shouldCloseRef(
+ $cursor->getRefType(),
+ $cursor->getRefName());
+ if (!$should_close) {
+ continue;
+ }
+
+ foreach ($cursor->getPositionIdentifiers() as $identifier) {
+ $all_closing_heads[] = $identifier;
}
}
$all_closing_heads = array_unique($all_closing_heads);
$all_closing_heads = $this->removeMissingCommits($all_closing_heads);
foreach ($maps as $type => $refs) {
$cursor_group = idx($cursor_groups, $type, array());
$this->updateCursors($cursor_group, $refs, $type, $all_closing_heads);
}
if ($this->closeCommits) {
$this->setCloseFlagOnCommits($this->closeCommits);
}
- if ($this->newRefs || $this->deadRefs) {
+ if ($this->newPositions || $this->deadPositions) {
$repository->openTransaction();
- foreach ($this->newRefs as $ref) {
- $ref->save();
- }
- foreach ($this->deadRefs as $ref) {
- // Shove this ref into the old refs table so the discovery engine
- // can check if any commits have been rendered unreachable.
- id(new PhabricatorRepositoryOldRef())
- ->setRepositoryPHID($repository->getPHID())
- ->setCommitIdentifier($ref->getCommitIdentifier())
- ->save();
-
- $ref->delete();
- }
- $repository->saveTransaction();
- $this->newRefs = array();
- $this->deadRefs = array();
+ $this->saveNewPositions();
+ $this->deleteDeadPositions();
+
+ $repository->saveTransaction();
}
$branches = $maps[PhabricatorRepositoryRefCursor::TYPE_BRANCH];
if ($branches && $branches_may_close) {
$this->updateBranchStates($repository, $branches);
}
}
private function updateBranchStates(
PhabricatorRepository $repository,
array $branches) {
assert_instances_of($branches, 'DiffusionRepositoryRef');
+ $viewer = $this->getViewer();
$all_cursors = id(new PhabricatorRepositoryRefCursorQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->setViewer($viewer)
->withRepositoryPHIDs(array($repository->getPHID()))
+ ->needPositions(true)
->execute();
$state_map = array();
$type_branch = PhabricatorRepositoryRefCursor::TYPE_BRANCH;
foreach ($all_cursors as $cursor) {
if ($cursor->getRefType() !== $type_branch) {
continue;
}
$raw_name = $cursor->getRefNameRaw();
- $hash = $cursor->getCommitIdentifier();
- $state_map[$raw_name][$hash] = $cursor;
+ foreach ($cursor->getPositions() as $position) {
+ $hash = $position->getCommitIdentifier();
+ $state_map[$raw_name][$hash] = $position;
+ }
}
+ $updates = array();
foreach ($branches as $branch) {
- $cursor = idx($state_map, $branch->getShortName(), array());
- $cursor = idx($cursor, $branch->getCommitIdentifier());
- if (!$cursor) {
+ $position = idx($state_map, $branch->getShortName(), array());
+ $position = idx($position, $branch->getCommitIdentifier());
+ if (!$position) {
continue;
}
$fields = $branch->getRawFields();
- $cursor_state = (bool)$cursor->getIsClosed();
+ $position_state = (bool)$position->getIsClosed();
$branch_state = (bool)idx($fields, 'closed');
- if ($cursor_state != $branch_state) {
- $cursor->setIsClosed((int)$branch_state)->save();
+ if ($position_state != $branch_state) {
+ $updates[$position->getID()] = (int)$branch_state;
}
}
+
+ if ($updates) {
+ $position_table = id(new PhabricatorRepositoryRefPosition());
+ $conn = $position_table->establishConnection('w');
+
+ $position_table->openTransaction();
+ foreach ($updates as $position_id => $branch_state) {
+ queryfx(
+ $conn,
+ 'UPDATE %T SET isClosed = %d WHERE id = %d',
+ $position_table->getTableName(),
+ $branch_state,
+ $position_id);
+ }
+ $position_table->saveTransaction();
+ }
}
- private function markRefNew(PhabricatorRepositoryRefCursor $cursor) {
- $this->newRefs[] = $cursor;
+ private function markPositionNew(
+ PhabricatorRepositoryRefPosition $position) {
+ $this->newPositions[] = $position;
return $this;
}
- private function markRefDead(PhabricatorRepositoryRefCursor $cursor) {
- $this->deadRefs[] = $cursor;
+ private function markPositionDead(
+ PhabricatorRepositoryRefPosition $position) {
+ $this->deadPositions[] = $position;
return $this;
}
private function markCloseCommits(array $identifiers) {
foreach ($identifiers as $identifier) {
$this->closeCommits[$identifier] = $identifier;
}
return $this;
}
/**
* Remove commits which no longer exist in the repository from a list.
*
* After a force push and garbage collection, we may have branch cursors which
* point at commits which no longer exist. This can make commands issued later
* fail. See T5839 for discussion.
*
* @param list<string> List of commit identifiers.
* @return list<string> List with nonexistent identifiers removed.
*/
private function removeMissingCommits(array $identifiers) {
if (!$identifiers) {
return array();
}
$resolved = id(new DiffusionLowLevelResolveRefsQuery())
->setRepository($this->getRepository())
->withRefs($identifiers)
->execute();
foreach ($identifiers as $key => $identifier) {
if (empty($resolved[$identifier])) {
unset($identifiers[$key]);
}
}
return $identifiers;
}
private function updateCursors(
array $cursors,
array $new_refs,
$ref_type,
array $all_closing_heads) {
$repository = $this->getRepository();
// NOTE: Mercurial branches may have multiple branch heads; this logic
// is complex primarily to account for that.
- // Group all the cursors by their ref name, like "master". Since Mercurial
- // branches may have multiple heads, there could be several cursors with
- // the same name.
- $cursor_groups = mgroup($cursors, 'getRefNameRaw');
+ $cursors = mpull($cursors, null, 'getRefNameRaw');
// Group all the new ref values by their name. As above, these groups may
// have multiple members in Mercurial.
$ref_groups = mgroup($new_refs, 'getShortName');
foreach ($ref_groups as $name => $refs) {
$new_commits = mpull($refs, 'getCommitIdentifier', 'getCommitIdentifier');
- $ref_cursors = idx($cursor_groups, $name, array());
- $old_commits = mpull($ref_cursors, null, 'getCommitIdentifier');
+ $ref_cursor = idx($cursors, $name);
+ if ($ref_cursor) {
+ $old_positions = $ref_cursor->getPositions();
+ } else {
+ $old_positions = array();
+ }
// We're going to delete all the cursors pointing at commits which are
// no longer associated with the refs. This primarily makes the Mercurial
// multiple head case easier, and means that when we update a ref we
// delete the old one and write a new one.
- foreach ($ref_cursors as $cursor) {
- if (isset($new_commits[$cursor->getCommitIdentifier()])) {
+ foreach ($old_positions as $old_position) {
+ $hash = $old_position->getCommitIdentifier();
+ if (isset($new_commits[$hash])) {
// This ref previously pointed at this commit, and still does.
$this->log(
pht(
'Ref %s "%s" still points at %s.',
$ref_type,
$name,
- $cursor->getCommitIdentifier()));
- } else {
- // This ref previously pointed at this commit, but no longer does.
- $this->log(
- pht(
- 'Ref %s "%s" no longer points at %s.',
- $ref_type,
- $name,
- $cursor->getCommitIdentifier()));
-
- // Nuke the obsolete cursor.
- $this->markRefDead($cursor);
+ $hash));
+ continue;
}
+
+ // This ref previously pointed at this commit, but no longer does.
+ $this->log(
+ pht(
+ 'Ref %s "%s" no longer points at %s.',
+ $ref_type,
+ $name,
+ $hash));
+
+ // Nuke the obsolete cursor.
+ $this->markPositionDead($old_position);
}
// Now, we're going to insert new cursors for all the commits which are
// associated with this ref that don't currently have cursors.
+ $old_commits = mpull($old_positions, 'getCommitIdentifier');
+ $old_commits = array_fuse($old_commits);
+
$added_commits = array_diff_key($new_commits, $old_commits);
foreach ($added_commits as $identifier) {
$this->log(
pht(
'Ref %s "%s" now points at %s.',
$ref_type,
$name,
$identifier));
- $this->markRefNew(
- id(new PhabricatorRepositoryRefCursor())
- ->setRepositoryPHID($repository->getPHID())
- ->setRefType($ref_type)
- ->setRefName($name)
- ->setCommitIdentifier($identifier));
+
+ if (!$ref_cursor) {
+ // If this is the first time we've seen a particular ref (for
+ // example, a new branch) we need to insert a RefCursor record
+ // for it before we can insert a RefPosition.
+
+ $ref_cursor = $this->newRefCursor(
+ $repository,
+ $ref_type,
+ $name);
+ }
+
+ $new_position = id(new PhabricatorRepositoryRefPosition())
+ ->setCursorID($ref_cursor->getID())
+ ->setCommitIdentifier($identifier)
+ ->setIsClosed(0);
+
+ $this->markPositionNew($new_position);
}
if ($this->shouldCloseRef($ref_type, $name)) {
foreach ($added_commits as $identifier) {
$new_identifiers = $this->loadNewCommitIdentifiers(
$identifier,
$all_closing_heads);
$this->markCloseCommits($new_identifiers);
}
}
}
// Find any cursors for refs which no longer exist. This happens when a
// branch, tag or bookmark is deleted.
- foreach ($cursor_groups as $name => $cursor_group) {
- if (idx($ref_groups, $name) === null) {
- foreach ($cursor_group as $cursor) {
- $this->log(
- pht(
- 'Ref %s "%s" no longer exists.',
- $cursor->getRefType(),
- $cursor->getRefName()));
- $this->markRefDead($cursor);
- }
+ foreach ($cursors as $name => $cursor) {
+ if (!empty($ref_groups[$name])) {
+ // This ref still has some positions, so we don't need to wipe it
+ // out. Try the next one.
+ continue;
+ }
+
+ foreach ($cursor->getPositions() as $position) {
+ $this->log(
+ pht(
+ 'Ref %s "%s" no longer exists.',
+ $cursor->getRefType(),
+ $cursor->getRefName()));
+
+ $this->markPositionDead($position);
}
}
}
private function shouldCloseRef($ref_type, $ref_name) {
if ($ref_type !== PhabricatorRepositoryRefCursor::TYPE_BRANCH) {
return false;
}
if ($this->hasNoCursors) {
// If we don't have any cursors, don't close things. Particularly, this
// corresponds to the case where you've just updated to this code on an
// existing repository: we don't want to requeue message steps for every
// commit on a closeable ref.
return false;
}
return $this->getRepository()->shouldAutocloseBranch($ref_name);
}
/**
* Find all ancestors of a new closing branch head which are not ancestors
* of any old closing branch head.
*/
private function loadNewCommitIdentifiers(
$new_head,
array $all_closing_heads) {
$repository = $this->getRepository();
$vcs = $repository->getVersionControlSystem();
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
if ($all_closing_heads) {
$parts = array();
foreach ($all_closing_heads as $head) {
$parts[] = hgsprintf('%s', $head);
}
// See T5896. Mercurial can not parse an "X or Y or ..." rev list
// with more than about 300 items, because it exceeds the maximum
// allowed recursion depth. Split all the heads into chunks of
// 256, and build a query like this:
//
// ((1 or 2 or ... or 255) or (256 or 257 or ... 511))
//
// If we have more than 65535 heads, we'll do that again:
//
// (((1 or ...) or ...) or ((65536 or ...) or ...))
$chunk_size = 256;
while (count($parts) > $chunk_size) {
$chunks = array_chunk($parts, $chunk_size);
foreach ($chunks as $key => $chunk) {
$chunks[$key] = '('.implode(' or ', $chunk).')';
}
$parts = array_values($chunks);
}
$parts = '('.implode(' or ', $parts).')';
list($stdout) = $this->getRepository()->execxLocalCommand(
'log --template %s --rev %s',
'{node}\n',
hgsprintf('%s', $new_head).' - '.$parts);
} else {
list($stdout) = $this->getRepository()->execxLocalCommand(
'log --template %s --rev %s',
'{node}\n',
hgsprintf('%s', $new_head));
}
$stdout = trim($stdout);
if (!strlen($stdout)) {
return array();
}
return phutil_split_lines($stdout, $retain_newlines = false);
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
if ($all_closing_heads) {
list($stdout) = $this->getRepository()->execxLocalCommand(
'log --format=%s %s --not %Ls',
'%H',
$new_head,
$all_closing_heads);
} else {
list($stdout) = $this->getRepository()->execxLocalCommand(
'log --format=%s %s',
'%H',
$new_head);
}
$stdout = trim($stdout);
if (!strlen($stdout)) {
return array();
}
return phutil_split_lines($stdout, $retain_newlines = false);
default:
throw new Exception(pht('Unsupported VCS "%s"!', $vcs));
}
}
/**
* Mark a list of commits as closeable, and queue workers for those commits
* which don't already have the flag.
*/
private function setCloseFlagOnCommits(array $identifiers) {
$repository = $this->getRepository();
$commit_table = new PhabricatorRepositoryCommit();
$conn_w = $commit_table->establishConnection('w');
$vcs = $repository->getVersionControlSystem();
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$class = 'PhabricatorRepositoryGitCommitMessageParserWorker';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$class = 'PhabricatorRepositorySvnCommitMessageParserWorker';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$class = 'PhabricatorRepositoryMercurialCommitMessageParserWorker';
break;
default:
throw new Exception(pht("Unknown repository type '%s'!", $vcs));
}
$all_commits = queryfx_all(
$conn_w,
'SELECT id, commitIdentifier, importStatus FROM %T
WHERE repositoryID = %d AND commitIdentifier IN (%Ls)',
$commit_table->getTableName(),
$repository->getID(),
$identifiers);
$closeable_flag = PhabricatorRepositoryCommit::IMPORTED_CLOSEABLE;
$all_commits = ipull($all_commits, null, 'commitIdentifier');
foreach ($identifiers as $identifier) {
$row = idx($all_commits, $identifier);
if (!$row) {
throw new Exception(
pht(
'Commit "%s" has not been discovered yet! Run discovery before '.
'updating refs.',
$identifier));
}
if (!($row['importStatus'] & $closeable_flag)) {
queryfx(
$conn_w,
'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d',
$commit_table->getTableName(),
$closeable_flag,
$row['id']);
$data = array(
'commitID' => $row['id'],
'only' => true,
);
PhabricatorWorker::scheduleTask($class, $data);
}
}
return $this;
}
+ private function newRefCursor(
+ PhabricatorRepository $repository,
+ $ref_type,
+ $ref_name) {
+
+ $cursor = id(new PhabricatorRepositoryRefCursor())
+ ->setRepositoryPHID($repository->getPHID())
+ ->setRefType($ref_type)
+ ->setRefName($ref_name);
+
+ try {
+ return $cursor->save();
+ } catch (AphrontDuplicateKeyQueryException $ex) {
+ // If we raced another daemon to create this position and lost the race,
+ // load the cursor the other daemon created instead.
+ }
+
+ $viewer = $this->getViewer();
+
+ $cursor = id(new PhabricatorRepositoryRefCursorQuery())
+ ->setViewer($viewer)
+ ->withRepositoryPHIDs(array($repository->getPHID()))
+ ->withRefTypes(array($ref_type))
+ ->withRefNames(array($ref_name))
+ ->needPositions(true)
+ ->executeOne();
+ if (!$cursor) {
+ throw new Exception(
+ pht(
+ 'Failed to create a new ref cursor (for "%s", of type "%s", in '.
+ 'repository "%s") because it collided with an existing cursor, '.
+ 'but then failed to load that cursor.',
+ $ref_name,
+ $ref_type,
+ $repository->getDisplayName()));
+ }
+
+ return $cursor;
+ }
+
+ private function saveNewPositions() {
+ $positions = $this->newPositions;
+
+ foreach ($positions as $position) {
+ try {
+ $position->save();
+ } catch (AphrontDuplicateKeyQueryException $ex) {
+ // We may race another daemon to create this position. If we do, and
+ // we lose the race, that's fine: the other daemon did our work for
+ // us and we can continue.
+ }
+ }
+
+ $this->newPositions = array();
+ }
+
+ private function deleteDeadPositions() {
+ $positions = $this->deadPositions;
+ $repository = $this->getRepository();
+
+ foreach ($positions as $position) {
+ // Shove this ref into the old refs table so the discovery engine
+ // can check if any commits have been rendered unreachable.
+ id(new PhabricatorRepositoryOldRef())
+ ->setRepositoryPHID($repository->getPHID())
+ ->setCommitIdentifier($position->getCommitIdentifier())
+ ->save();
+
+ $position->delete();
+ }
+
+ $this->deadPositions = array();
+ }
+
+
/* -( Updating Git Refs )-------------------------------------------------- */
/**
* @task git
*/
private function loadGitRefPositions(PhabricatorRepository $repository) {
$refs = id(new DiffusionLowLevelGitRefQuery())
->setRepository($repository)
->execute();
return mgroup($refs, 'getRefType');
}
/* -( Updating Mercurial Refs )-------------------------------------------- */
/**
* @task hg
*/
private function loadMercurialBranchPositions(
PhabricatorRepository $repository) {
return id(new DiffusionLowLevelMercurialBranchesQuery())
->setRepository($repository)
->execute();
}
/**
* @task hg
*/
private function loadMercurialBookmarkPositions(
PhabricatorRepository $repository) {
// TODO: Implement support for Mercurial bookmarks.
return array();
}
}
diff --git a/src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php b/src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php
index 706fe801b6..b3c960e025 100644
--- a/src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php
+++ b/src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php
@@ -1,131 +1,153 @@
<?php
final class PhabricatorRepositoryRefCursorQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $repositoryPHIDs;
private $refTypes;
private $refNames;
private $datasourceQuery;
+ private $needPositions;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withRepositoryPHIDs(array $phids) {
$this->repositoryPHIDs = $phids;
return $this;
}
public function withRefTypes(array $types) {
$this->refTypes = $types;
return $this;
}
public function withRefNames(array $names) {
$this->refNames = $names;
return $this;
}
public function withDatasourceQuery($query) {
$this->datasourceQuery = $query;
return $this;
}
+ public function needPositions($need) {
+ $this->needPositions = $need;
+ return $this;
+ }
+
public function newResultObject() {
return new PhabricatorRepositoryRefCursor();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function willFilterPage(array $refs) {
$repository_phids = mpull($refs, 'getRepositoryPHID');
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($repository_phids)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
foreach ($refs as $key => $ref) {
$repository = idx($repositories, $ref->getRepositoryPHID());
if (!$repository) {
$this->didRejectResult($ref);
unset($refs[$key]);
continue;
}
$ref->attachRepository($repository);
}
+ if (!$refs) {
+ return $refs;
+ }
+
+ if ($this->needPositions) {
+ $positions = id(new PhabricatorRepositoryRefPosition())->loadAllWhere(
+ 'cursorID IN (%Ld)',
+ mpull($refs, 'getID'));
+ $positions = mgroup($positions, 'getCursorID');
+
+ foreach ($refs as $key => $ref) {
+ $ref_positions = idx($positions, $ref->getID(), array());
+ $ref->attachPositions($ref_positions);
+ }
+ }
+
return $refs;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn,
'repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
if ($this->refTypes !== null) {
$where[] = qsprintf(
$conn,
'refType IN (%Ls)',
$this->refTypes);
}
if ($this->refNames !== null) {
$name_hashes = array();
foreach ($this->refNames as $name) {
$name_hashes[] = PhabricatorHash::digestForIndex($name);
}
$where[] = qsprintf(
$conn,
'refNameHash IN (%Ls)',
$name_hashes);
}
if (strlen($this->datasourceQuery)) {
$where[] = qsprintf(
$conn,
'refNameRaw LIKE %>',
$this->datasourceQuery);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
}
diff --git a/src/applications/repository/storage/PhabricatorRepositoryRefCursor.php b/src/applications/repository/storage/PhabricatorRepositoryRefCursor.php
index ec0c90744c..87f737d86e 100644
--- a/src/applications/repository/storage/PhabricatorRepositoryRefCursor.php
+++ b/src/applications/repository/storage/PhabricatorRepositoryRefCursor.php
@@ -1,96 +1,111 @@
<?php
/**
* Stores the previous value of a ref (like a branch or tag) so we can figure
* out how a repository has changed when we discover new commits or branch
* heads.
*/
final class PhabricatorRepositoryRefCursor
extends PhabricatorRepositoryDAO
implements PhabricatorPolicyInterface {
const TYPE_BRANCH = 'branch';
const TYPE_TAG = 'tag';
const TYPE_BOOKMARK = 'bookmark';
const TYPE_REF = 'ref';
protected $repositoryPHID;
protected $refType;
protected $refNameHash;
protected $refNameRaw;
protected $refNameEncoding;
private $repository = self::ATTACHABLE;
+ private $positions = self::ATTACHABLE;
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_AUX_PHID => true,
self::CONFIG_BINARY => array(
'refNameRaw' => true,
),
self::CONFIG_COLUMN_SCHEMA => array(
'refType' => 'text32',
'refNameHash' => 'bytes12',
'refNameEncoding' => 'text16?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_ref' => array(
'columns' => array('repositoryPHID', 'refType', 'refNameHash'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorRepositoryRefCursorPHIDType::TYPECONST);
}
public function getRefName() {
return $this->getUTF8StringFromStorage(
$this->getRefNameRaw(),
$this->getRefNameEncoding());
}
public function setRefName($ref_raw) {
$this->setRefNameRaw($ref_raw);
$this->setRefNameHash(PhabricatorHash::digestForIndex($ref_raw));
$this->setRefNameEncoding($this->detectEncodingForStorage($ref_raw));
return $this;
}
public function attachRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function getRepository() {
return $this->assertAttached($this->repository);
}
+ public function attachPositions(array $positions) {
+ assert_instances_of($positions, 'PhabricatorRepositoryRefPosition');
+ $this->positions = $positions;
+ return $this;
+ }
+
+ public function getPositions() {
+ return $this->assertAttached($this->positions);
+ }
+
+ public function getPositionIdentifiers() {
+ return mpull($this->getPositions(), 'getCommitIdentifier');
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return $this->getRepository()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getRepository()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return pht('Repository refs have the same policies as their repository.');
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, May 1, 6:48 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
108899
Default Alt Text
(40 KB)

Event Timeline