Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14028481
D7982.id18086.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
22 KB
Referenced Files
None
Subscribers
None
D7982.id18086.diff
View Options
Index: resources/sql/autopatches/20140116.reporefcursor.sql
===================================================================
--- /dev/null
+++ resources/sql/autopatches/20140116.reporefcursor.sql
@@ -0,0 +1,11 @@
+CREATE TABLE {$NAMESPACE}_repository.repository_refcursor (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ repositoryPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ refType VARCHAR(32) NOT NULL COLLATE utf8_bin,
+ refNameHash VARCHAR(12) NOT NULL COLLATE latin1_bin,
+ refNameRaw LONGTEXT NOT NULL COLLATE latin1_bin,
+ refNameEncoding VARCHAR(16) COLLATE utf8_bin,
+ commitIdentifier VARCHAR(40) NOT NULL COLLATE utf8_bin,
+
+ KEY `key_cursor` (repositoryPHID, refType, refNameHash)
+) ENGINE=InnoDB, COLLATE=utf8_general_ci;
Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -1856,6 +1856,7 @@
'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php',
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php',
'PhabricatorRepositoryManagementPullWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php',
+ 'PhabricatorRepositoryManagementRefsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php',
'PhabricatorRepositoryManagementWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementWorkflow.php',
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryMercurialCommitChangeParserWorker.php',
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryMercurialCommitMessageParserWorker.php',
@@ -1872,6 +1873,9 @@
'PhabricatorRepositoryPushLogQuery' => 'applications/repository/query/PhabricatorRepositoryPushLogQuery.php',
'PhabricatorRepositoryPushLogSearchEngine' => 'applications/repository/query/PhabricatorRepositoryPushLogSearchEngine.php',
'PhabricatorRepositoryQuery' => 'applications/repository/query/PhabricatorRepositoryQuery.php',
+ 'PhabricatorRepositoryRefCursor' => 'applications/repository/storage/PhabricatorRepositoryRefCursor.php',
+ 'PhabricatorRepositoryRefCursorQuery' => 'applications/repository/query/PhabricatorRepositoryRefCursorQuery.php',
+ 'PhabricatorRepositoryRefEngine' => 'applications/repository/engine/PhabricatorRepositoryRefEngine.php',
'PhabricatorRepositorySearchEngine' => 'applications/repository/query/PhabricatorRepositorySearchEngine.php',
'PhabricatorRepositoryStatusMessage' => 'applications/repository/storage/PhabricatorRepositoryStatusMessage.php',
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositorySvnCommitChangeParserWorker.php',
@@ -4521,6 +4525,7 @@
'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementPullWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
+ 'PhabricatorRepositoryManagementRefsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
@@ -4545,6 +4550,13 @@
'PhabricatorRepositoryPushLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorRepositoryPushLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorRepositoryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhabricatorRepositoryRefCursor' =>
+ array(
+ 0 => 'PhabricatorRepositoryDAO',
+ 1 => 'PhabricatorPolicyInterface',
+ ),
+ 'PhabricatorRepositoryRefCursorQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhabricatorRepositoryRefEngine' => 'PhabricatorRepositoryEngine',
'PhabricatorRepositorySearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorRepositoryStatusMessage' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
Index: src/applications/diffusion/data/DiffusionBranchInformation.php
===================================================================
--- src/applications/diffusion/data/DiffusionBranchInformation.php
+++ src/applications/diffusion/data/DiffusionBranchInformation.php
@@ -42,4 +42,15 @@
);
}
+ // TODO: These are hacks to make this compatible with DiffusionRepositoryRef
+ // for PhabricatorRepositoryRefEngine. The two classes should be merged.
+
+ public function getShortName() {
+ return $this->getName();
+ }
+
+ public function getCommitIdentifier() {
+ return $this->getHeadCommitIdentifier();
+ }
+
}
Index: src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
===================================================================
--- src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
+++ src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
@@ -149,6 +149,7 @@
PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
null);
$this->discoverRepository($repository);
+ $this->updateRepositoryRefs($repository);
$repository->writeStatusMessage(
PhabricatorRepositoryStatusMessage::TYPE_FETCH,
PhabricatorRepositoryStatusMessage::CODE_OKAY);
@@ -267,6 +268,12 @@
}
}
+ private function updateRepositoryRefs(PhabricatorRepository $repository) {
+ id(new PhabricatorRepositoryRefEngine())
+ ->setRepository($repository)
+ ->updateRefs();
+ }
+
private function getDiscoveryEngine(PhabricatorRepository $repository) {
$id = $repository->getID();
if (empty($this->discoveryEngines[$id])) {
@@ -615,6 +622,7 @@
}
+
/**
* @task git
*/
Index: src/applications/repository/engine/PhabricatorRepositoryRefEngine.php
===================================================================
--- /dev/null
+++ src/applications/repository/engine/PhabricatorRepositoryRefEngine.php
@@ -0,0 +1,223 @@
+<?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();
+
+ public function updateRefs() {
+ $this->newRefs = array();
+ $this->deadRefs = array();
+
+ $repository = $this->getRepository();
+
+ $vcs = $repository->getVersionControlSystem();
+ switch ($vcs) {
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
+ // No meaningful refs of any type in Subversion.
+ $branches = array();
+ $bookmarks = array();
+ $tags = array();
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
+ $branches = $this->loadMercurialBranchPositions($repository);
+ $bookmarks = $this->loadMercurialBookmarkPositions($repository);
+ $tags = array();
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
+ $branches = $this->loadGitBranchPositions($repository);
+ $bookmarks = array();
+ $tags = $this->loadGitTagPositions($repository);
+ break;
+ default:
+ throw new Exception(pht('Unknown VCS "%s"!', $vcs));
+ }
+
+ $maps = array(
+ PhabricatorRepositoryRefCursor::TYPE_BRANCH => $branches,
+ PhabricatorRepositoryRefCursor::TYPE_TAG => $tags,
+ PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => $bookmarks,
+ );
+
+ $all_cursors = id(new PhabricatorRepositoryRefCursorQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withRepositoryPHIDs(array($repository->getPHID()))
+ ->execute();
+ $cursor_groups = mgroup($all_cursors, 'getRefType');
+
+ foreach ($maps as $type => $refs) {
+ $cursor_group = idx($cursor_groups, $type, array());
+ $this->updateCursors($cursor_group, $refs, $type);
+ }
+
+ if ($this->newRefs || $this->deadRefs) {
+ $repository->openTransaction();
+ foreach ($this->newRefs as $ref) {
+ $ref->save();
+ }
+ foreach ($this->deadRefs as $ref) {
+ $ref->delete();
+ }
+ $repository->saveTransaction();
+
+ $this->newRefs = array();
+ $this->deadRefs = array();
+ }
+ }
+
+ private function markRefNew(PhabricatorRepositoryRefCursor $cursor) {
+ $this->newRefs[] = $cursor;
+ return $this;
+ }
+
+ private function markRefDead(PhabricatorRepositoryRefCursor $cursor) {
+ $this->deadRefs[] = $cursor;
+ return $this;
+ }
+
+ private function updateCursors(
+ array $cursors,
+ array $new_refs,
+ $ref_type) {
+ $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');
+
+ // 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');
+
+ // 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()])) {
+ // 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);
+ }
+ }
+
+ // Now, we're going to insert new cursors for all the commits which are
+ // associated with this ref that don't currently have cursors.
+ $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));
+ }
+
+ foreach ($added_commits as $identifier) {
+ // TODO: Do autoclose stuff here.
+ }
+ }
+
+ // 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) {
+ $this->log(
+ pht(
+ 'Ref %s "%s" no longer exists.',
+ $cursor->getRefType(),
+ $cursor->getRefName()));
+ foreach ($cursor_group as $cursor) {
+ $this->markRefDead($cursor);
+ }
+ }
+ }
+ }
+
+
+/* -( Updating Git Refs )-------------------------------------------------- */
+
+
+ /**
+ * @task git
+ */
+ private function loadGitBranchPositions(PhabricatorRepository $repository) {
+ return id(new DiffusionLowLevelGitRefQuery())
+ ->setRepository($repository)
+ ->withIsOriginBranch(true)
+ ->execute();
+ }
+
+
+ /**
+ * @task git
+ */
+ private function loadGitTagPositions(PhabricatorRepository $repository) {
+ return id(new DiffusionLowLevelGitRefQuery())
+ ->setRepository($repository)
+ ->withIsTag(true)
+ ->execute();
+ }
+
+
+/* -( 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();
+ }
+
+}
Index: src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php
===================================================================
--- /dev/null
+++ src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php
@@ -0,0 +1,49 @@
+<?php
+
+final class PhabricatorRepositoryManagementRefsWorkflow
+ extends PhabricatorRepositoryManagementWorkflow {
+
+ public function didConstruct() {
+ $this
+ ->setName('refs')
+ ->setExamples('**refs** [__options__] __repository__ ...')
+ ->setSynopsis('Update refs in __repository__, named by callsign.')
+ ->setArguments(
+ array(
+ array(
+ 'name' => 'verbose',
+ 'help' => 'Show additional debugging information.',
+ ),
+ array(
+ 'name' => 'repos',
+ 'wildcard' => true,
+ ),
+ ));
+ }
+
+ public function execute(PhutilArgumentParser $args) {
+ $repos = $this->loadRepositories($args, 'repos');
+
+ if (!$repos) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ "Specify one or more repositories to update refs for, ".
+ "by callsign."));
+ }
+
+ $console = PhutilConsole::getConsole();
+ foreach ($repos as $repo) {
+ $console->writeOut("Updating refs in '%s'...\n", $repo->getCallsign());
+
+ $engine = id(new PhabricatorRepositoryRefEngine())
+ ->setRepository($repo)
+ ->setVerbose($args->getArg('verbose'))
+ ->updateRefs();
+ }
+
+ $console->writeOut("Done.\n");
+
+ return 0;
+ }
+
+}
Index: src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php
===================================================================
--- /dev/null
+++ src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php
@@ -0,0 +1,83 @@
+<?php
+
+final class PhabricatorRepositoryRefCursorQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $repositoryPHIDs;
+ private $refTypes;
+
+ public function withRepositoryPHIDs(array $phids) {
+ $this->repositoryPHIDs = $phids;
+ return $this;
+ }
+
+ public function withRefTypes(array $types) {
+ $this->refTypes = $types;
+ return $this;
+ }
+
+ protected function loadPage() {
+ $table = new PhabricatorRepositoryRefCursor();
+ $conn_r = $table->establishConnection('r');
+
+ $data = queryfx_all(
+ $conn_r,
+ 'SELECT * FROM %T r %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildWhereClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ return $table->loadAllFromArray($data);
+ }
+
+ public 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) {
+ unset($refs[$key]);
+ continue;
+ }
+ $ref->attachRepository($repository);
+ }
+
+ return $refs;
+ }
+
+ private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->repositoryPHIDs) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'repositoryPHID IN (%Ls)',
+ $this->repositoryPHIDs);
+ }
+
+ if ($this->refTypes) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'refType IN (%Ls)',
+ $this->refTypes);
+ }
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ return $this->formatWhereClause($where);
+ }
+
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorApplicationDiffusion';
+ }
+
+}
Index: src/applications/repository/storage/PhabricatorRepository.php
===================================================================
--- src/applications/repository/storage/PhabricatorRepository.php
+++ src/applications/repository/storage/PhabricatorRepository.php
@@ -802,6 +802,12 @@
$mirror->delete();
}
+ $ref_cursors = id(new PhabricatorRepositoryRefCursor())
+ ->loadAllWhere('repositoryPHID = %s', $this->getPHID());
+ foreach ($ref_cursors as $cursor) {
+ $cursor->delete();
+ }
+
$conn_w = $this->establishConnection('w');
queryfx(
Index: src/applications/repository/storage/PhabricatorRepositoryCommit.php
===================================================================
--- src/applications/repository/storage/PhabricatorRepositoryCommit.php
+++ src/applications/repository/storage/PhabricatorRepositoryCommit.php
@@ -24,6 +24,8 @@
const IMPORTED_HERALD = 8;
const IMPORTED_ALL = 15;
+ const IMPORTED_CLOSEABLE = 1024;
+
private $commitData = self::ATTACHABLE;
private $audits;
private $repository = self::ATTACHABLE;
@@ -42,7 +44,7 @@
}
public function isImported() {
- return ($this->getImportStatus() == self::IMPORTED_ALL);
+ return $this->isPartiallyImported(self::IMPORTED_ALL);
}
public function writeImportStatusFlag($flag) {
Index: src/applications/repository/storage/PhabricatorRepositoryPushLog.php
===================================================================
--- src/applications/repository/storage/PhabricatorRepositoryPushLog.php
+++ src/applications/repository/storage/PhabricatorRepositoryPushLog.php
@@ -77,18 +77,15 @@
}
public function getRefName() {
- if ($this->getRefNameEncoding() == 'utf8') {
- return $this->getRefNameRaw();
- }
- return phutil_utf8ize($this->getRefNameRaw());
+ return $this->getUTF8StringFromStorage(
+ $this->getRefNameRaw(),
+ $this->getRefNameEncoding());
}
public function setRefName($ref_raw) {
- $encoding = phutil_is_utf8($ref_raw) ? 'utf8' : null;
-
$this->setRefNameRaw($ref_raw);
$this->setRefNameHash(PhabricatorHash::digestForIndex($ref_raw));
- $this->setRefNameEncoding($encoding);
+ $this->setRefNameEncoding($this->detectEncodingForStorage($ref_raw));
return $this;
}
Index: src/applications/repository/storage/PhabricatorRepositoryRefCursor.php
===================================================================
--- /dev/null
+++ src/applications/repository/storage/PhabricatorRepositoryRefCursor.php
@@ -0,0 +1,75 @@
+<?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';
+
+ protected $repositoryPHID;
+ protected $refType;
+ protected $refNameHash;
+ protected $refNameRaw;
+ protected $refNameEncoding;
+ protected $commitIdentifier;
+
+ private $repository = self::ATTACHABLE;
+
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_TIMESTAMPS => false,
+ ) + parent::getConfiguration();
+ }
+
+ 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);
+ }
+
+
+/* -( 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.');
+ }
+
+}
Index: src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
===================================================================
--- src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
+++ src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
@@ -175,4 +175,15 @@
return $value[$key];
}
+ protected function detectEncodingForStorage($string) {
+ return phutil_is_utf8($string) ? 'utf8' : null;
+ }
+
+ protected function getUTF8StringFromStorage($string, $encoding) {
+ if ($encoding == 'utf8') {
+ return $string;
+ }
+ return phutil_utf8ize($string);
+ }
+
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Nov 9, 1:38 PM (1 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6715582
Default Alt Text
D7982.id18086.diff (22 KB)
Attached To
Mode
D7982: Introduce ref cursors for repository parsing
Attached
Detach File
Event Timeline
Log In to Comment