1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-03 04:02:43 +01:00

(stable) Promote 2015 Week 41

This commit is contained in:
epriestley 2015-10-10 04:49:38 -07:00
commit be4752f05a
31 changed files with 488 additions and 209 deletions

4
.gitignore vendored
View file

@ -34,3 +34,7 @@
# NPM local packages # NPM local packages
/support/aphlict/server/node_modules/ /support/aphlict/server/node_modules/
# Places for users to add custom resources.
/resources/cows/custom/*
/resources/figlet/custom/*

View file

@ -13,7 +13,7 @@ return array(
'differential.pkg.css' => '2de124c9', 'differential.pkg.css' => '2de124c9',
'differential.pkg.js' => '6223dd9d', 'differential.pkg.js' => '6223dd9d',
'diffusion.pkg.css' => 'f45955ed', 'diffusion.pkg.css' => 'f45955ed',
'diffusion.pkg.js' => '0115b37c', 'diffusion.pkg.js' => 'ca1c8b5a',
'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.css' => '4845691a',
'maniphest.pkg.js' => '3ec6a6d5', 'maniphest.pkg.js' => '3ec6a6d5',
'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
@ -371,7 +371,7 @@ return array(
'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef', 'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef',
'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667', 'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667',
'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947', 'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947',
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => '2b228192', 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc',
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781',
'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58', 'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
@ -574,7 +574,7 @@ return array(
'javelin-behavior-diffusion-commit-graph' => '9007c197', 'javelin-behavior-diffusion-commit-graph' => '9007c197',
'javelin-behavior-diffusion-jump-to' => '73d09eef', 'javelin-behavior-diffusion-jump-to' => '73d09eef',
'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-locate-file' => '6d3e1947',
'javelin-behavior-diffusion-pull-lastmodified' => '2b228192', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc',
'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-doorkeeper-tag' => 'e5822781',
'javelin-behavior-durable-column' => 'c72aa091', 'javelin-behavior-durable-column' => 'c72aa091',
'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-error-log' => '6882e80a',
@ -998,13 +998,6 @@ return array(
'javelin-install', 'javelin-install',
'javelin-util', 'javelin-util',
), ),
'2b228192' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-json',
),
'2b8de964' => array( '2b8de964' => array(
'javelin-install', 'javelin-install',
'javelin-util', 'javelin-util',
@ -1939,6 +1932,13 @@ return array(
'javelin-install', 'javelin-install',
'javelin-util', 'javelin-util',
), ),
'f01586dc' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-json',
),
'f24a53cb' => array( 'f24a53cb' => array(
'phui-fontkit-css', 'phui-fontkit-css',
), ),

View file

@ -1011,6 +1011,7 @@ phutil_register_library_map(array(
'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php',
'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php', 'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php',
'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
'HarbormasterDrydockBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterDrydockBuildStepGroup.php',
'HarbormasterDrydockCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php', 'HarbormasterDrydockCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php',
'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php', 'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php',
'HarbormasterExecFuture' => 'applications/harbormaster/future/HarbormasterExecFuture.php', 'HarbormasterExecFuture' => 'applications/harbormaster/future/HarbormasterExecFuture.php',
@ -4813,6 +4814,7 @@ phutil_register_library_map(array(
'HarbormasterController' => 'PhabricatorController', 'HarbormasterController' => 'PhabricatorController',
'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterDAO' => 'PhabricatorLiskDAO', 'HarbormasterDAO' => 'PhabricatorLiskDAO',
'HarbormasterDrydockBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact', 'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact',
'HarbormasterExecFuture' => 'Future', 'HarbormasterExecFuture' => 'Future',

View file

@ -98,12 +98,10 @@ final class DiffusionLastModifiedController extends DiffusionController {
$modified = DiffusionView::linkCommit( $modified = DiffusionView::linkCommit(
$drequest->getRepository(), $drequest->getRepository(),
$commit->getCommitIdentifier()); $commit->getCommitIdentifier());
$date = phabricator_date($epoch, $viewer); $date = phabricator_datetime($epoch, $viewer);
$time = phabricator_time($epoch, $viewer);
} else { } else {
$modified = ''; $modified = '';
$date = ''; $date = '';
$time = '';
} }
$data = $commit->getCommitData(); $data = $commit->getCommitData();
@ -137,7 +135,6 @@ final class DiffusionLastModifiedController extends DiffusionController {
$return = array( $return = array(
'commit' => $modified, 'commit' => $modified,
'date' => $date, 'date' => $date,
'time' => $time,
'author' => $author, 'author' => $author,
'details' => $details, 'details' => $details,
); );

View file

@ -21,6 +21,11 @@ final class DiffusionBranchTableView extends DiffusionView {
$drequest = $this->getDiffusionRequest(); $drequest = $this->getDiffusionRequest();
$current_branch = $drequest->getBranch(); $current_branch = $drequest->getBranch();
$repository = $drequest->getRepository(); $repository = $drequest->getRepository();
$commits = $this->commits;
$viewer = $this->getUser();
$buildables = $this->loadBuildables($commits);
$have_builds = false;
$can_close_branches = ($repository->isHg()); $can_close_branches = ($repository->isHg());
@ -31,13 +36,21 @@ final class DiffusionBranchTableView extends DiffusionView {
$rows = array(); $rows = array();
$rowc = array(); $rowc = array();
foreach ($this->branches as $branch) { foreach ($this->branches as $branch) {
$commit = idx($this->commits, $branch->getCommitIdentifier()); $commit = idx($commits, $branch->getCommitIdentifier());
if ($commit) { if ($commit) {
$details = $commit->getSummary(); $details = $commit->getSummary();
$datetime = phabricator_datetime($commit->getEpoch(), $this->user); $datetime = phabricator_datetime($commit->getEpoch(), $viewer);
$buildable = idx($buildables, $commit->getPHID());
if ($buildable) {
$build_status = $this->renderBuildable($buildable);
$have_builds = true;
} else {
$build_status = null;
}
} else { } else {
$datetime = null; $datetime = null;
$details = null; $details = null;
$build_status = null;
} }
switch ($repository->shouldSkipAutocloseBranch($branch->getShortName())) { switch ($repository->shouldSkipAutocloseBranch($branch->getShortName())) {
@ -86,16 +99,7 @@ final class DiffusionBranchTableView extends DiffusionView {
} }
$rows[] = array( $rows[] = array(
phutil_tag( $this->linkBranchHistory($branch->getShortName()),
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'history',
'branch' => $branch->getShortName(),
)),
),
pht('History')),
phutil_tag( phutil_tag(
'a', 'a',
array( array(
@ -109,10 +113,11 @@ final class DiffusionBranchTableView extends DiffusionView {
self::linkCommit( self::linkCommit(
$drequest->getRepository(), $drequest->getRepository(),
$branch->getCommitIdentifier()), $branch->getCommitIdentifier()),
$build_status,
$status, $status,
AphrontTableView::renderSingleDisplayLine($details),
$status_icon, $status_icon,
$datetime, $datetime,
AphrontTableView::renderSingleDisplayLine($details),
); );
if ($branch->getShortName() == $current_branch) { if ($branch->getShortName() == $current_branch) {
$rowc[] = 'highlighted'; $rowc[] = 'highlighted';
@ -124,33 +129,37 @@ final class DiffusionBranchTableView extends DiffusionView {
$view = new AphrontTableView($rows); $view = new AphrontTableView($rows);
$view->setHeaders( $view->setHeaders(
array( array(
pht('History'), null,
pht('Branch'), pht('Branch'),
pht('Head'), pht('Head'),
null,
pht('State'), pht('State'),
pht(''),
pht('Modified'),
pht('Details'), pht('Details'),
null,
pht('Committed'),
)); ));
$view->setColumnClasses( $view->setColumnClasses(
array( array(
'', '',
'pri', 'pri',
'', '',
'', 'icon',
'',
'', '',
'wide', 'wide',
'',
'',
)); ));
$view->setColumnVisibility( $view->setColumnVisibility(
array( array(
true, true,
true, true,
true, true,
$have_builds,
$can_close_branches, $can_close_branches,
)); ));
$view->setRowClasses($rowc); $view->setRowClasses($rowc);
return $view->render(); return $view->render();
} }
} }

View file

@ -31,6 +31,8 @@ final class DiffusionBrowseTableView extends DiffusionView {
$show_edit = false; $show_edit = false;
foreach ($this->paths as $path) { foreach ($this->paths as $path) {
$history_link = $this->linkHistory($path->getPath());
$dir_slash = null; $dir_slash = null;
$file_type = $path->getFileType(); $file_type = $path->getFileType();
if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
@ -67,7 +69,6 @@ final class DiffusionBrowseTableView extends DiffusionView {
'lint' => celerity_generate_unique_node_id(), 'lint' => celerity_generate_unique_node_id(),
'commit' => celerity_generate_unique_node_id(), 'commit' => celerity_generate_unique_node_id(),
'date' => celerity_generate_unique_node_id(), 'date' => celerity_generate_unique_node_id(),
'time' => celerity_generate_unique_node_id(),
'author' => celerity_generate_unique_node_id(), 'author' => celerity_generate_unique_node_id(),
'details' => celerity_generate_unique_node_id(), 'details' => celerity_generate_unique_node_id(),
); );
@ -78,13 +79,13 @@ final class DiffusionBrowseTableView extends DiffusionView {
} }
$rows[] = array( $rows[] = array(
$history_link,
$browse_link, $browse_link,
idx($dict, 'lint'), idx($dict, 'lint'),
$dict['commit'], $dict['commit'],
$dict['author'], $dict['author'],
$dict['details'], $dict['details'],
$dict['date'], $dict['date'],
$dict['time'],
); );
} }
@ -108,29 +109,29 @@ final class DiffusionBrowseTableView extends DiffusionView {
$view = new AphrontTableView($rows); $view = new AphrontTableView($rows);
$view->setHeaders( $view->setHeaders(
array( array(
null,
pht('Path'), pht('Path'),
($lint ? $lint : pht('Lint')), ($lint ? $lint : pht('Lint')),
pht('Modified'), pht('Modified'),
pht('Author/Committer'), pht('Author/Committer'),
pht('Details'), pht('Details'),
pht('Date'), pht('Committed'),
pht('Time'),
)); ));
$view->setColumnClasses( $view->setColumnClasses(
array( array(
'nudgeright',
'',
'',
'', '',
'n',
'n',
'', '',
'wide', 'wide',
'', '',
'right',
)); ));
$view->setColumnVisibility( $view->setColumnVisibility(
array( array(
true, true,
$show_lint,
true, true,
$show_lint,
true, true,
true, true,
true, true,
@ -140,11 +141,11 @@ final class DiffusionBrowseTableView extends DiffusionView {
$view->setDeviceVisibility( $view->setDeviceVisibility(
array( array(
true, true,
false,
true, true,
false, false,
true, true,
false, false,
true,
false, false,
)); ));

View file

@ -7,12 +7,10 @@ final class DiffusionHistoryTableView extends DiffusionView {
private $handles = array(); private $handles = array();
private $isHead; private $isHead;
private $parents; private $parents;
private $buildCache;
public function setHistory(array $history) { public function setHistory(array $history) {
assert_instances_of($history, 'DiffusionPathChange'); assert_instances_of($history, 'DiffusionPathChange');
$this->history = $history; $this->history = $history;
$this->buildCache = null;
return $this; return $this;
} }
@ -62,33 +60,14 @@ final class DiffusionHistoryTableView extends DiffusionView {
return $this; return $this;
} }
public function loadBuildablesOnDemand() {
if ($this->buildCache !== null) {
return $this->buildCache;
}
$commits_to_builds = array();
$commits = mpull($this->history, 'getCommit');
$commit_phids = mpull($commits, 'getPHID');
$buildables = id(new HarbormasterBuildableQuery())
->setViewer($this->getUser())
->withBuildablePHIDs($commit_phids)
->withManualBuildables(false)
->execute();
$this->buildCache = mpull($buildables, null, 'getBuildablePHID');
return $this->buildCache;
}
public function render() { public function render() {
$drequest = $this->getDiffusionRequest(); $drequest = $this->getDiffusionRequest();
$viewer = $this->getUser(); $viewer = $this->getUser();
$buildables = $this->loadBuildables(mpull($this->history, 'getCommit'));
$has_any_build = false;
$show_revisions = PhabricatorApplication::isClassInstalledForViewer( $show_revisions = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorDifferentialApplication', 'PhabricatorDifferentialApplication',
$viewer); $viewer);
@ -110,11 +89,9 @@ final class DiffusionHistoryTableView extends DiffusionView {
$epoch = $history->getEpoch(); $epoch = $history->getEpoch();
if ($epoch) { if ($epoch) {
$date = phabricator_date($epoch, $this->user); $committed = phabricator_datetime($epoch, $viewer);
$time = phabricator_time($epoch, $this->user);
} else { } else {
$date = null; $committed = null;
$time = null;
} }
$data = $history->getCommitData(); $data = $history->getCommitData();
@ -160,36 +137,9 @@ final class DiffusionHistoryTableView extends DiffusionView {
$build = null; $build = null;
if ($show_builds) { if ($show_builds) {
$buildable_lookup = $this->loadBuildablesOnDemand(); $buildable = idx($buildables, $commit->getPHID());
$buildable = idx($buildable_lookup, $commit->getPHID());
if ($buildable !== null) { if ($buildable !== null) {
$icon = HarbormasterBuildable::getBuildableStatusIcon( $build = $this->renderBuildable($buildable);
$buildable->getBuildableStatus());
$color = HarbormasterBuildable::getBuildableStatusColor(
$buildable->getBuildableStatus());
$name = HarbormasterBuildable::getBuildableStatusName(
$buildable->getBuildableStatus());
$icon_view = id(new PHUIIconView())
->setIconFont($icon.' '.$color);
$tooltip_view = javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array('tip' => $name),
),
$icon_view);
Javelin::initBehavior('phabricator-tooltips');
$href_view = phutil_tag(
'a',
array('href' => '/'.$buildable->getMonogram()),
$tooltip_view);
$build = $href_view;
$has_any_build = true; $has_any_build = true;
} }
} }
@ -214,8 +164,7 @@ final class DiffusionHistoryTableView extends DiffusionView {
null), null),
$author, $author,
$summary, $summary,
$date, $committed,
$time,
); );
} }
@ -226,30 +175,28 @@ final class DiffusionHistoryTableView extends DiffusionView {
null, null,
pht('Commit'), pht('Commit'),
null, null,
pht('Revision'), null,
pht('Author/Committer'), pht('Author/Committer'),
pht('Details'), pht('Details'),
pht('Date'), pht('Committed'),
pht('Time'),
)); ));
$view->setColumnClasses( $view->setColumnClasses(
array( array(
'threads', 'threads',
'nudgeright', 'nudgeright',
'n', '',
'icon', 'icon',
'n', '',
'', '',
'wide', 'wide',
'', '',
'right',
)); ));
$view->setColumnVisibility( $view->setColumnVisibility(
array( array(
$graph ? true : false, $graph ? true : false,
true, true,
true, true,
true, $has_any_build,
$show_revisions, $show_revisions,
)); ));
$view->setDeviceVisibility( $view->setDeviceVisibility(
@ -262,7 +209,6 @@ final class DiffusionHistoryTableView extends DiffusionView {
false, false,
true, true,
false, false,
false,
)); ));
return $view->render(); return $view->render();
} }

View file

@ -29,6 +29,8 @@ final class DiffusionTagListView extends DiffusionView {
$drequest = $this->getDiffusionRequest(); $drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository(); $repository = $drequest->getRepository();
$buildables = $this->loadBuildables($this->commits);
$has_builds = false;
$rows = array(); $rows = array();
foreach ($this->tags as $tag) { foreach ($this->tags as $tag) {
@ -80,30 +82,56 @@ final class DiffusionTagListView extends DiffusionView {
} }
} }
$build = null;
if ($commit) {
$buildable = idx($buildables, $commit->getPHID());
if ($buildable) {
$build = $this->renderBuildable($buildable);
$has_builds = true;
}
}
$history = $this->linkTagHistory($tag->getName());
$rows[] = array( $rows[] = array(
$history,
$tag_link, $tag_link,
$commit_link, $commit_link,
$description, $build,
$author, $author,
$description,
phabricator_datetime($tag->getEpoch(), $this->user), phabricator_datetime($tag->getEpoch(), $this->user),
); );
} }
$table = new AphrontTableView($rows); $table = id(new AphrontTableView($rows))
$table->setHeaders( ->setHeaders(
array( array(
pht('Tag'), null,
pht('Commit'), pht('Tag'),
pht('Description'), pht('Commit'),
pht('Author'), null,
pht('Created'), pht('Author'),
)); pht('Description'),
$table->setColumnClasses( pht('Created'),
array( ))
'pri', ->setColumnClasses(
'', array(
'wide', 'nudgeright',
)); 'pri',
'',
'',
'',
'wide',
))
->setColumnVisibility(
array(
true,
true,
true,
$has_builds,
));
return $table->render(); return $table->render();
} }

View file

@ -20,6 +20,30 @@ abstract class DiffusionView extends AphrontView {
'path' => $path, 'path' => $path,
)); ));
return $this->renderHistoryLink($href);
}
final public function linkBranchHistory($branch) {
$href = $this->getDiffusionRequest()->generateURI(
array(
'action' => 'history',
'branch' => $branch,
));
return $this->renderHistoryLink($href);
}
final public function linkTagHistory($tag) {
$href = $this->getDiffusionRequest()->generateURI(
array(
'action' => 'history',
'commit' => $tag,
));
return $this->renderHistoryLink($href);
}
private function renderHistoryLink($href) {
return javelin_tag( return javelin_tag(
'a', 'a',
array( array(
@ -31,7 +55,7 @@ abstract class DiffusionView extends AphrontView {
'align' => 'E', 'align' => 'E',
), ),
), ),
id(new PHUIIconView())->setIconFont('fa-list-ul blue')); id(new PHUIIconView())->setIconFont('fa-history bluegrey'));
} }
final public function linkBrowse($path, array $details = array()) { final public function linkBrowse($path, array $details = array()) {
@ -170,4 +194,58 @@ abstract class DiffusionView extends AphrontView {
return hsprintf('%s', $name); return hsprintf('%s', $name);
} }
final protected function renderBuildable(HarbormasterBuildable $buildable) {
$status = $buildable->getBuildableStatus();
$icon = HarbormasterBuildable::getBuildableStatusIcon($status);
$color = HarbormasterBuildable::getBuildableStatusColor($status);
$name = HarbormasterBuildable::getBuildableStatusName($status);
$icon_view = id(new PHUIIconView())
->setIconFont($icon.' '.$color);
$tooltip_view = javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array('tip' => $name),
),
$icon_view);
Javelin::initBehavior('phabricator-tooltips');
return phutil_tag(
'a',
array('href' => '/'.$buildable->getMonogram()),
$tooltip_view);
}
final protected function loadBuildables(array $commits) {
assert_instances_of($commits, 'PhabricatorRepositoryCommit');
if (!$commits) {
return array();
}
$viewer = $this->getUser();
$harbormaster_app = 'PhabricatorHarbormasterApplication';
$have_harbormaster = PhabricatorApplication::isClassInstalledForViewer(
$harbormaster_app,
$viewer);
if ($have_harbormaster) {
$buildables = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withBuildablePHIDs(mpull($commits, 'getPHID'))
->withManualBuildables(false)
->execute();
$buildables = mpull($buildables, null, 'getBuildablePHID');
} else {
$buildables = array();
}
return $buildables;
}
} }

View file

@ -262,13 +262,14 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
array( array(
DrydockResourceStatus::STATUS_PENDING, DrydockResourceStatus::STATUS_PENDING,
DrydockResourceStatus::STATUS_ACTIVE, DrydockResourceStatus::STATUS_ACTIVE,
DrydockResourceStatus::STATUS_BROKEN,
DrydockResourceStatus::STATUS_RELEASED, DrydockResourceStatus::STATUS_RELEASED,
)) ))
->execute(); ->execute();
$allocated_phids = array(); $allocated_phids = array();
foreach ($pool as $resource) { foreach ($pool as $resource) {
$allocated_phids[] = $resource->getAttribute('almanacDevicePHID'); $allocated_phids[] = $resource->getAttribute('almanacBindingPHID');
} }
$allocated_phids = array_fuse($allocated_phids); $allocated_phids = array_fuse($allocated_phids);

View file

@ -19,6 +19,10 @@ abstract class DrydockBlueprintImplementation extends Phobject {
return array(); return array();
} }
public function getViewer() {
return PhabricatorUser::getOmnipotentUser();
}
/* -( Lease Acquisition )-------------------------------------------------- */ /* -( Lease Acquisition )-------------------------------------------------- */
@ -288,7 +292,7 @@ abstract class DrydockBlueprintImplementation extends Phobject {
} }
protected function newLease(DrydockBlueprint $blueprint) { protected function newLease(DrydockBlueprint $blueprint) {
return id(new DrydockLease()); return DrydockLease::initializeNewLease();
} }
protected function requireActiveLease(DrydockLease $lease) { protected function requireActiveLease(DrydockLease $lease) {
@ -310,4 +314,67 @@ abstract class DrydockBlueprintImplementation extends Phobject {
} }
} }
/**
* Apply standard limits on resource allocation rate.
*
* @param DrydockBlueprint The blueprint requesting an allocation.
* @return bool True if further allocations should be limited.
*/
protected function shouldLimitAllocatingPoolSize(
DrydockBlueprint $blueprint) {
// TODO: If this mechanism sticks around, these values should be
// configurable by the blueprint implementation.
// Limit on total number of active resources.
$total_limit = 1;
// Always allow at least this many allocations to be in flight at once.
$min_allowed = 1;
// Allow this fraction of allocating resources as a fraction of active
// resources.
$growth_factor = 0.25;
$resource = new DrydockResource();
$conn_r = $resource->establishConnection('r');
$counts = queryfx_all(
$conn_r,
'SELECT status, COUNT(*) N FROM %T WHERE blueprintPHID = %s',
$resource->getTableName(),
$blueprint->getPHID());
$counts = ipull($counts, 'N', 'status');
$n_alloc = idx($counts, DrydockResourceStatus::STATUS_PENDING, 0);
$n_active = idx($counts, DrydockResourceStatus::STATUS_ACTIVE, 0);
$n_broken = idx($counts, DrydockResourceStatus::STATUS_BROKEN, 0);
$n_released = idx($counts, DrydockResourceStatus::STATUS_RELEASED, 0);
// If we're at the limit on total active resources, limit additional
// allocations.
$n_total = ($n_alloc + $n_active + $n_broken + $n_released);
if ($n_total >= $total_limit) {
return true;
}
// If the number of in-flight allocations is fewer than the minimum number
// of allowed allocations, don't impose a limit.
if ($n_alloc < $min_allowed) {
return false;
}
$allowed_alloc = (int)ceil($n_active * $growth_factor);
// If the number of in-flight allocation is fewer than the number of
// allowed allocations according to the pool growth factor, don't impose
// a limit.
if ($n_alloc < $allowed_alloc) {
return false;
}
return true;
}
} }

View file

@ -29,6 +29,17 @@ final class DrydockWorkingCopyBlueprintImplementation
public function canAllocateResourceForLease( public function canAllocateResourceForLease(
DrydockBlueprint $blueprint, DrydockBlueprint $blueprint,
DrydockLease $lease) { DrydockLease $lease) {
$viewer = $this->getViewer();
if ($this->shouldLimitAllocatingPoolSize($blueprint)) {
return false;
}
// TODO: If we have a pending resource which is compatible with the
// configuration for this lease, prevent a new allocation? Otherwise the
// queue can fill up with copies of requests from the same lease. But
// maybe we can deal with this with "pre-leasing"?
return true; return true;
} }
@ -37,6 +48,12 @@ final class DrydockWorkingCopyBlueprintImplementation
DrydockResource $resource, DrydockResource $resource,
DrydockLease $lease) { DrydockLease $lease) {
// Don't hand out leases on working copies which have not activated, since
// it may take an arbitrarily long time for them to acquire a host.
if (!$resource->isActive()) {
return false;
}
$need_map = $lease->getAttribute('repositories.map'); $need_map = $lease->getAttribute('repositories.map');
if (!is_array($need_map)) { if (!is_array($need_map)) {
return false; return false;
@ -104,8 +121,13 @@ final class DrydockWorkingCopyBlueprintImplementation
$host_lease = $this->newLease($blueprint) $host_lease = $this->newLease($blueprint)
->setResourceType('host') ->setResourceType('host')
->setOwnerPHID($resource_phid) ->setOwnerPHID($resource_phid)
->setAttribute('workingcopy.resourcePHID', $resource_phid) ->setAttribute('workingcopy.resourcePHID', $resource_phid);
->queueForActivation();
$resource
->setAttribute('host.leasePHID', $host_lease->getPHID())
->save();
$host_lease->queueForActivation();
// TODO: Add some limits to the number of working copies we can have at // TODO: Add some limits to the number of working copies we can have at
// once? // once?
@ -121,7 +143,6 @@ final class DrydockWorkingCopyBlueprintImplementation
return $resource return $resource
->setAttribute('repositories.map', $map) ->setAttribute('repositories.map', $map)
->setAttribute('host.leasePHID', $host_lease->getPHID())
->allocateResource(); ->allocateResource();
} }
@ -165,7 +186,13 @@ final class DrydockWorkingCopyBlueprintImplementation
DrydockBlueprint $blueprint, DrydockBlueprint $blueprint,
DrydockResource $resource) { DrydockResource $resource) {
$lease = $this->loadHostLease($resource); try {
$lease = $this->loadHostLease($resource);
} catch (Exception $ex) {
// If we can't load the lease, assume we don't need to take any actions
// to destroy it.
return;
}
// Destroy the lease on the host. // Destroy the lease on the host.
$lease->releaseOnDestruction(); $lease->releaseOnDestruction();
@ -310,8 +337,10 @@ final class DrydockWorkingCopyBlueprintImplementation
} }
private function loadRepositories(array $phids) { private function loadRepositories(array $phids) {
$viewer = $this->getViewer();
$repositories = id(new PhabricatorRepositoryQuery()) $repositories = id(new PhabricatorRepositoryQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer($viewer)
->withPHIDs($phids) ->withPHIDs($phids)
->execute(); ->execute();
$repositories = mpull($repositories, null, 'getPHID'); $repositories = mpull($repositories, null, 'getPHID');
@ -343,7 +372,7 @@ final class DrydockWorkingCopyBlueprintImplementation
} }
private function loadHostLease(DrydockResource $resource) { private function loadHostLease(DrydockResource $resource) {
$viewer = PhabricatorUser::getOmnipotentUser(); $viewer = $this->getViewer();
$lease_phid = $resource->getAttribute('host.leasePHID'); $lease_phid = $resource->getAttribute('host.leasePHID');

View file

@ -19,7 +19,7 @@ final class DrydockLeaseWaitingForResourcesLogType extends DrydockLogType {
return pht( return pht(
'Waiting for available resources from: %s.', 'Waiting for available resources from: %s.',
$viewer->renderHandleList($blueprint_phids)); $viewer->renderHandleList($blueprint_phids)->render());
} }
} }

View file

@ -19,6 +19,16 @@ final class DrydockLease extends DrydockDAO
private $activateWhenAcquired = false; private $activateWhenAcquired = false;
private $slotLocks = array(); private $slotLocks = array();
public static function initializeNewLease() {
$lease = new DrydockLease();
// Pregenerate a PHID so that the caller can set something up to release
// this lease before queueing it for activation.
$lease->setPHID($lease->generatePHID());
return $lease;
}
/** /**
* Flag this lease to be released when its destructor is called. This is * Flag this lease to be released when its destructor is called. This is
* mostly useful if you have a script which acquires, uses, and then releases * mostly useful if you have a script which acquires, uses, and then releases
@ -232,6 +242,7 @@ final class DrydockLease extends DrydockDAO
} }
$this->openTransaction(); $this->openTransaction();
try { try {
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array(); $this->slotLocks = array();
@ -247,16 +258,12 @@ final class DrydockLease extends DrydockDAO
throw $ex; throw $ex;
} }
try { $this
$this ->setResourcePHID($resource->getPHID())
->setResourcePHID($resource->getPHID()) ->attachResource($resource)
->attachResource($resource) ->setStatus($new_status)
->setStatus($new_status) ->save();
->save();
} catch (Exception $ex) {
$this->killTransaction();
throw $ex;
}
$this->saveTransaction(); $this->saveTransaction();
$this->isAcquired = true; $this->isAcquired = true;
@ -295,12 +302,24 @@ final class DrydockLease extends DrydockDAO
$this->openTransaction(); $this->openTransaction();
$this try {
->setStatus(DrydockLeaseStatus::STATUS_ACTIVE)
->save();
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array(); $this->slotLocks = array();
} catch (DrydockSlotLockException $ex) {
$this->killTransaction();
$this->logEvent(
DrydockSlotLockFailureLogType::LOGCONST,
array(
'locks' => $ex->getLockMap(),
));
throw $ex;
}
$this
->setStatus(DrydockLeaseStatus::STATUS_ACTIVE)
->save();
$this->saveTransaction(); $this->saveTransaction();

View file

@ -113,13 +113,6 @@ final class DrydockResource extends DrydockDAO
} }
public function allocateResource() { public function allocateResource() {
if ($this->getID()) {
throw new Exception(
pht(
'Trying to allocate a resource which has already been persisted. '.
'Only new resources may be allocated.'));
}
// We expect resources to have a pregenerated PHID, as they should have // We expect resources to have a pregenerated PHID, as they should have
// been created by a call to DrydockBlueprint->newResourceTemplate(). // been created by a call to DrydockBlueprint->newResourceTemplate().
if (!$this->getPHID()) { if (!$this->getPHID()) {
@ -155,9 +148,14 @@ final class DrydockResource extends DrydockDAO
} catch (DrydockSlotLockException $ex) { } catch (DrydockSlotLockException $ex) {
$this->killTransaction(); $this->killTransaction();
// NOTE: We have to log this on the blueprint, as the resource is not if ($this->getID()) {
// going to be saved so the PHID will vanish. $log_target = $this;
$this->getBlueprint()->logEvent( } else {
// If we don't have an ID, we have to log this on the blueprint, as the
// resource is not going to be saved so the PHID will vanish.
$log_target = $this->getBlueprint();
}
$log_target->logEvent(
DrydockSlotLockFailureLogType::LOGCONST, DrydockSlotLockFailureLogType::LOGCONST,
array( array(
'locks' => $ex->getLockMap(), 'locks' => $ex->getLockMap(),
@ -166,14 +164,9 @@ final class DrydockResource extends DrydockDAO
throw $ex; throw $ex;
} }
try { $this
$this ->setStatus($new_status)
->setStatus($new_status) ->save();
->save();
} catch (Exception $ex) {
$this->killTransaction();
throw $ex;
}
$this->saveTransaction(); $this->saveTransaction();
@ -210,12 +203,24 @@ final class DrydockResource extends DrydockDAO
$this->openTransaction(); $this->openTransaction();
$this try {
->setStatus(DrydockResourceStatus::STATUS_ACTIVE)
->save();
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array(); $this->slotLocks = array();
} catch (DrydockSlotLockException $ex) {
$this->killTransaction();
$this->logEvent(
DrydockSlotLockFailureLogType::LOGCONST,
array(
'locks' => $ex->getLockMap(),
));
throw $ex;
}
$this
->setStatus(DrydockResourceStatus::STATUS_ACTIVE)
->save();
$this->saveTransaction(); $this->saveTransaction();
@ -288,6 +293,15 @@ final class DrydockResource extends DrydockDAO
} }
} }
public function isActive() {
switch ($this->getStatus()) {
case DrydockResourceStatus::STATUS_ACTIVE:
return true;
}
return false;
}
public function logEvent($type, array $data = array()) { public function logEvent($type, array $data = array()) {
$log = id(new DrydockLog()) $log = id(new DrydockLog())
->setEpoch(PhabricatorTime::getNow()) ->setEpoch(PhabricatorTime::getNow())

View file

@ -149,6 +149,7 @@ final class DrydockSlotLock extends DrydockDAO {
// time we should be able to figure out which locks are already held. // time we should be able to figure out which locks are already held.
$held = self::loadHeldLocks($locks); $held = self::loadHeldLocks($locks);
$held = mpull($held, 'getOwnerPHID', 'getLockKey'); $held = mpull($held, 'getOwnerPHID', 'getLockKey');
throw new DrydockSlotLockException($held); throw new DrydockSlotLockException($held);
} }
} }

View file

@ -535,7 +535,7 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
// If this lease has been acquired but not activated, queue a task to // If this lease has been acquired but not activated, queue a task to
// activate it. // activate it.
if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACQUIRED) { if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACQUIRED) {
PhabricatorWorker::scheduleTask( $this->queueTask(
__CLASS__, __CLASS__,
array( array(
'leasePHID' => $lease->getPHID(), 'leasePHID' => $lease->getPHID(),
@ -691,7 +691,14 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
->setStatus(DrydockLeaseStatus::STATUS_BROKEN) ->setStatus(DrydockLeaseStatus::STATUS_BROKEN)
->save(); ->save();
$lease->scheduleUpdate(); $this->queueTask(
__CLASS__,
array(
'leasePHID' => $lease->getPHID(),
),
array(
'objectPHID' => $lease->getPHID(),
));
$lease->logEvent( $lease->logEvent(
DrydockLeaseActivationFailureLogType::LOGCONST, DrydockLeaseActivationFailureLogType::LOGCONST,

View file

@ -29,7 +29,9 @@ abstract class HarbormasterDrydockLeaseArtifact
} }
public function willCreateArtifact(PhabricatorUser $actor) { public function willCreateArtifact(PhabricatorUser $actor) {
$this->loadArtifactLease($actor); // We don't load the lease here because it's expected that artifacts are
// created before leases actually exist. This guarantees that the leases
// will be cleaned up.
} }
public function loadArtifactLease(PhabricatorUser $viewer) { public function loadArtifactLease(PhabricatorUser $viewer) {
@ -51,7 +53,15 @@ abstract class HarbormasterDrydockLeaseArtifact
} }
public function releaseArtifact(PhabricatorUser $actor) { public function releaseArtifact(PhabricatorUser $actor) {
$lease = $this->loadArtifactLease($actor); try {
$lease = $this->loadArtifactLease($actor);
} catch (Exception $ex) {
// If we can't load the lease, treat it as already released. Artifacts
// are generated before leases are queued, so it's possible to arrive
// here under normal conditions.
return;
}
if (!$lease->canRelease()) { if (!$lease->canRelease()) {
return; return;
} }

View file

@ -61,7 +61,7 @@ final class HarbormasterPlanEditController extends HarbormasterPlanController {
$is_new = (!$plan->getID()); $is_new = (!$plan->getID());
if ($is_new) { if ($is_new) {
$title = pht('New Build Plan'); $title = pht('New Build Plan');
$cancel_uri = $this->getApplicationURI(); $cancel_uri = $this->getApplicationURI('plan/');
$save_button = pht('Create Build Plan'); $save_button = pht('Create Build Plan');
} else { } else {
$id = $plan->getID(); $id = $plan->getID();

View file

@ -12,7 +12,7 @@ final class HarbormasterDrydockCommandBuildStepImplementation
} }
public function getBuildStepGroupKey() { public function getBuildStepGroupKey() {
return HarbormasterPrototypeBuildStepGroup::GROUPKEY; return HarbormasterDrydockBuildStepGroup::GROUPKEY;
} }
public function getDescription() { public function getDescription() {

View file

@ -12,7 +12,7 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
} }
public function getBuildStepGroupKey() { public function getBuildStepGroupKey() {
return HarbormasterPrototypeBuildStepGroup::GROUPKEY; return HarbormasterDrydockBuildStepGroup::GROUPKEY;
} }
public function execute( public function execute(
@ -41,7 +41,7 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
$working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation()) $working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation())
->getType(); ->getType();
$lease = id(new DrydockLease()) $lease = DrydockLease::initializeNewLease()
->setResourceType($working_copy_type) ->setResourceType($working_copy_type)
->setOwnerPHID($build_target->getPHID()); ->setOwnerPHID($build_target->getPHID());
@ -54,6 +54,18 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
$lease->setAwakenTaskIDs(array($task_id)); $lease->setAwakenTaskIDs(array($task_id));
} }
// TODO: Maybe add a method to mark artifacts like this as pending?
// Create the artifact now so that the lease is always disposed of, even
// if this target is aborted.
$build_target->createArtifact(
$viewer,
$settings['name'],
HarbormasterWorkingCopyArtifact::ARTIFACTCONST,
array(
'drydockLeasePHID' => $lease->getPHID(),
));
$lease->queueForActivation(); $lease->queueForActivation();
$build_target $build_target
@ -73,14 +85,6 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
'Lease "%s" never activated.', 'Lease "%s" never activated.',
$lease->getPHID())); $lease->getPHID()));
} }
$artifact = $build_target->createArtifact(
$viewer,
$settings['name'],
HarbormasterWorkingCopyArtifact::ARTIFACTCONST,
array(
'drydockLeasePHID' => $lease->getPHID(),
));
} }
public function getArtifactOutputs() { public function getArtifactOutputs() {

View file

@ -0,0 +1,25 @@
<?php
final class HarbormasterDrydockBuildStepGroup
extends HarbormasterBuildStepGroup {
const GROUPKEY = 'harbormaster.drydock';
public function getGroupName() {
return pht('Drydock');
}
public function getGroupOrder() {
return 3000;
}
public function isEnabled() {
$drydock_class = 'PhabricatorDrydockApplication';
return PhabricatorApplication::isClassInstalled($drydock_class);
}
public function shouldShowIfEmpty() {
return false;
}
}

View file

@ -77,10 +77,17 @@ final class PonderAnswerView extends AphrontTagView {
->setIconFont('fa-bars') ->setIconFont('fa-bars')
->setDropdownMenu($actions); ->setDropdownMenu($actions);
$header_name = phutil_tag(
'a',
array(
'href' => $handle->getURI(),
),
$handle->getName());
$header = id(new PHUIHeaderView()) $header = id(new PHUIHeaderView())
->setUser($viewer) ->setUser($viewer)
->setEpoch($answer->getDateModified()) ->setEpoch($answer->getDateModified())
->setHeader($handle->getName()) ->setHeader($header_name)
->addActionLink($action_button) ->addActionLink($action_button)
->setImage($handle->getImageURI()) ->setImage($handle->getImageURI())
->setImageURL($handle->getURI()); ->setImageURL($handle->getURI());

View file

@ -160,8 +160,7 @@ abstract class PhabricatorWorker extends Phobject {
try { try {
$worker->doWork(); $worker->doWork();
foreach ($worker->getQueuedTasks() as $queued_task) { foreach ($worker->getQueuedTasks() as $queued_task) {
list($queued_class, $queued_data, $queued_priority) = $queued_task; list($queued_class, $queued_data, $queued_options) = $queued_task;
$queued_options = array('priority' => $queued_priority);
self::scheduleTask($queued_class, $queued_data, $queued_options); self::scheduleTask($queued_class, $queued_data, $queued_options);
} }
break; break;
@ -220,11 +219,14 @@ abstract class PhabricatorWorker extends Phobject {
* *
* @param string Task class to queue. * @param string Task class to queue.
* @param array Data for the followup task. * @param array Data for the followup task.
* @param int|null Priority for the followup task. * @param array Options for the followup task.
* @return this * @return this
*/ */
final protected function queueTask($class, array $data, $priority = null) { final protected function queueTask(
$this->queuedTasks[] = array($class, $data, $priority); $class,
array $data,
array $options = array()) {
$this->queuedTasks[] = array($class, $data, $options);
return $this; return $this;
} }

View file

@ -42,7 +42,8 @@ final class PhabricatorWorkerManagementExecuteWorkflow
$task->getDataID()); $task->getDataID());
$task->setData($task_data->getData()); $task->setData($task_data->getData());
$console->writeOut( echo tsprintf(
"%s\n",
pht( pht(
'Executing task %d (%s)...', 'Executing task %d (%s)...',
$task->getID(), $task->getID(),

View file

@ -217,13 +217,14 @@ final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask {
// so execute it out here and just let the exception escape. // so execute it out here and just let the exception escape.
if ($did_succeed) { if ($did_succeed) {
foreach ($worker->getQueuedTasks() as $task) { foreach ($worker->getQueuedTasks() as $task) {
list($class, $data) = $task; list($class, $data, $options) = $task;
PhabricatorWorker::scheduleTask(
$class, // Default the new task priority to our own priority.
$data, $options = $options + array(
array( 'priority' => (int)$this->getPriority(),
'priority' => (int)$this->getPriority(), );
));
PhabricatorWorker::scheduleTask($class, $data, $options);
} }
} }

View file

@ -1398,6 +1398,11 @@ final class PhabricatorUSEnglishTranslation
'Setting retention policy for "%s" to %s days.', 'Setting retention policy for "%s" to %s days.',
), ),
'Waiting %s second(s) for lease to activate.' => array(
'Waiting a second for lease to activate.',
'Waiting %s seconds for lease to activate.',
),
); );
} }

View file

@ -28,10 +28,16 @@ final class PhabricatorRemarkupCowsayBlockInterpreter
->setText($content) ->setText($content)
->renderCow(); ->renderCow();
if ($this->getEngine()->isTextMode()) { $engine = $this->getEngine();
if ($engine->isTextMode()) {
return $result; return $result;
} }
if ($engine->isHTMLMailMode()) {
return phutil_tag('pre', array(), $result);
}
return phutil_tag( return phutil_tag(
'div', 'div',
array( array(

View file

@ -27,10 +27,16 @@ final class PhabricatorRemarkupFigletBlockInterpreter
$result = $figlet->lineEcho($content); $result = $figlet->lineEcho($content);
if ($this->getEngine()->isTextMode()) { $engine = $this->getEngine();
if ($engine->isTextMode()) {
return $result; return $result;
} }
if ($engine->isHTMLMailMode()) {
return phutil_tag('pre', array(), $result);
}
return phutil_tag( return phutil_tag(
'div', 'div',
array( array(

View file

@ -60,9 +60,9 @@ final class PhabricatorStorageManagementProbeWorkflow
$overall); $overall);
$table->addRow(array( $table->addRow(array(
'name' => phutil_console_format('**%s**', $db), 'name' => tsprintf('**%s**', $db),
'size' => phutil_console_format('**%s**', $database_size), 'size' => tsprintf('**%s**', $database_size),
'percentage' => phutil_console_format('**%s**', $database_percentage), 'percentage' => tsprintf('**%s**', $database_percentage),
)); ));
$data[$db] = isort($data[$db], '_totalSize'); $data[$db] = isort($data[$db], '_totalSize');
foreach ($data[$db] as $table_name => $info) { foreach ($data[$db] as $table_name => $info) {
@ -82,9 +82,9 @@ final class PhabricatorStorageManagementProbeWorkflow
$overall, $overall,
$overall); $overall);
$table->addRow(array( $table->addRow(array(
'name' => phutil_console_format('**%s**', pht('TOTAL')), 'name' => tsprintf('**%s**', pht('TOTAL')),
'size' => phutil_console_format('**%s**', $overall_size), 'size' => tsprintf('**%s**', $overall_size),
'percentage' => phutil_console_format('**%s**', $overall_percentage), 'percentage' => tsprintf('**%s**', $overall_percentage),
)); ));
$table->draw(); $table->draw();

View file

@ -16,7 +16,16 @@ JX.behavior('diffusion-pull-lastmodified', function(config) {
if (!config.map[k][l]) { if (!config.map[k][l]) {
continue; continue;
} }
JX.DOM.setContent(JX.$(config.map[k][l]), JX.$H(r[k][l])); try {
JX.DOM.setContent(JX.$(config.map[k][l]), JX.$H(r[k][l]));
} catch (ex) {
// The way this works is weird and sometimes the components get
// out of sync. Fail gently until we can eventually improve the
// underlying mechanism.
// In particular, we currently may generate lint information
// without generating a lint column. See T9524.
}
} }
} }
}) })