1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-10 23:01:04 +01:00

(stable) Promote 2018 Week 18

This commit is contained in:
epriestley 2018-05-06 05:03:14 -07:00
commit 95fed76a3f
37 changed files with 853 additions and 122 deletions

View file

@ -388,7 +388,7 @@ return array(
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc',
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70',
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
'rsrc/js/application/files/behavior-document-engine.js' => 'ee0deff8',
'rsrc/js/application/files/behavior-document-engine.js' => '3935d8c4',
'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '549459b8',
@ -600,7 +600,7 @@ return array(
'javelin-behavior-diffusion-commit-graph' => '75b83cbb',
'javelin-behavior-diffusion-locate-file' => '6d3e1947',
'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc',
'javelin-behavior-document-engine' => 'ee0deff8',
'javelin-behavior-document-engine' => '3935d8c4',
'javelin-behavior-doorkeeper-tag' => '1db13e70',
'javelin-behavior-drydock-live-operation-status' => '901935ef',
'javelin-behavior-durable-column' => '2ae077e1',
@ -1080,6 +1080,11 @@ return array(
'javelin-dom',
'javelin-vector',
),
'3935d8c4' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'3ab51e2c' => array(
'javelin-behavior',
'javelin-behavior-device',
@ -2105,11 +2110,6 @@ return array(
'javelin-behavior',
'javelin-uri',
),
'ee0deff8' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'efe49472' => array(
'javelin-install',
'javelin-util',

View file

@ -0,0 +1,26 @@
<?php
$packages_table = new PhabricatorOwnersPackage();
$packages_conn = $packages_table->establishConnection('w');
$packages_name = $packages_table->getTableName();
$properties_table = new PhabricatorMetaMTAMailProperties();
$conn = $properties_table->establishConnection('w');
$iterator = new LiskRawMigrationIterator($packages_conn, $packages_name);
foreach ($iterator as $package) {
queryfx(
$conn,
'INSERT IGNORE INTO %T
(objectPHID, mailProperties, dateCreated, dateModified)
VALUES
(%s, %s, %d, %d)',
$properties_table->getTableName(),
$package['phid'],
phutil_json_encode(
array(
'mailKey' => $package['mailKey'],
)),
PhabricatorTime::getNow(),
PhabricatorTime::getNow());
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_owners.owners_package
DROP mailKey;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_owners.owners_package
ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_owners.owners_package
SET properties = '{}' WHERE properties = '';

View file

@ -3572,6 +3572,7 @@ phutil_register_library_map(array(
'PhabricatorOwnersPackageFerretEngine' => 'applications/owners/search/PhabricatorOwnersPackageFerretEngine.php',
'PhabricatorOwnersPackageFulltextEngine' => 'applications/owners/search/PhabricatorOwnersPackageFulltextEngine.php',
'PhabricatorOwnersPackageFunctionDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageFunctionDatasource.php',
'PhabricatorOwnersPackageIgnoredTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageIgnoredTransaction.php',
'PhabricatorOwnersPackageNameNgrams' => 'applications/owners/storage/PhabricatorOwnersPackageNameNgrams.php',
'PhabricatorOwnersPackageNameTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageNameTransaction.php',
'PhabricatorOwnersPackageOwnerDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php',
@ -3867,6 +3868,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php',
'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php',
'PhabricatorPolicySearchEngineExtension' => 'applications/policy/engineextension/PhabricatorPolicySearchEngineExtension.php',
'PhabricatorPolicyStrengthConstants' => 'applications/policy/constants/PhabricatorPolicyStrengthConstants.php',
'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php',
'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php',
'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php',
@ -4998,6 +5000,7 @@ phutil_register_library_map(array(
'PhrictionDocumentMoveToTransaction' => 'applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php',
'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php',
'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php',
'PhrictionDocumentPolicyCodex' => 'applications/phriction/codex/PhrictionDocumentPolicyCodex.php',
'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php',
'PhrictionDocumentSearchConduitAPIMethod' => 'applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php',
'PhrictionDocumentSearchEngine' => 'applications/phriction/query/PhrictionDocumentSearchEngine.php',
@ -9319,6 +9322,7 @@ phutil_register_library_map(array(
'PhabricatorOwnersPackageFerretEngine' => 'PhabricatorFerretEngine',
'PhabricatorOwnersPackageFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorOwnersPackageFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorOwnersPackageIgnoredTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageNameNgrams' => 'PhabricatorSearchNgrams',
'PhabricatorOwnersPackageNameTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
@ -9669,6 +9673,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorPolicyRule' => 'Phobject',
'PhabricatorPolicySearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorPolicyStrengthConstants' => 'PhabricatorPolicyConstants',
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyTestObject' => array(
'Phobject',
@ -11060,6 +11065,7 @@ phutil_register_library_map(array(
'PhabricatorProjectInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorConduitResultInterface',
'PhabricatorPolicyCodexInterface',
),
'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField',
'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField',
@ -11076,6 +11082,7 @@ phutil_register_library_map(array(
'PhrictionDocumentMoveToTransaction' => 'PhrictionDocumentTransactionType',
'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType',
'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField',
'PhrictionDocumentPolicyCodex' => 'PhabricatorPolicyCodex',
'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhrictionDocumentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhrictionDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine',

View file

@ -76,6 +76,16 @@ abstract class DifferentialController extends PhabricatorController {
$repository_phid,
$changeset_path);
// If this particular changeset is generated code and the package does
// not match generated code, remove it from the list.
if ($changeset->isGeneratedChangeset()) {
foreach ($packages as $key => $package) {
if ($package->getMustMatchUngeneratedPaths()) {
unset($packages[$key]);
}
}
}
$this->pathPackageMap[$changeset_path] = $packages;
foreach ($packages as $package) {
$this->packageChangesetMap[$package->getPHID()][] = $changeset;

View file

@ -1,22 +1,43 @@
<?php
final class DifferentialRevisionViewController extends DifferentialController {
final class DifferentialRevisionViewController
extends DifferentialController {
private $revisionID;
private $veryLargeDiff;
private $changesetCount;
public function shouldAllowPublic() {
return true;
}
public function isLargeDiff() {
return ($this->getChangesetCount() > $this->getLargeDiffLimit());
}
public function isVeryLargeDiff() {
return $this->veryLargeDiff;
return ($this->getChangesetCount() > $this->getVeryLargeDiffLimit());
}
public function getLargeDiffLimit() {
return 100;
}
public function getVeryLargeDiffLimit() {
return 1000;
}
public function getChangesetCount() {
if ($this->changesetCount === null) {
throw new PhutilInvalidStateException('setChangesetCount');
}
return $this->changesetCount;
}
public function setChangesetCount($count) {
$this->changesetCount = $count;
return $this;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$this->revisionID = $request->getURIData('id');
@ -82,9 +103,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
idx($diffs, $diff_vs),
$repository);
if (count($rendering_references) > $this->getVeryLargeDiffLimit()) {
$this->veryLargeDiff = true;
}
$this->setChangesetCount(count($rendering_references));
if ($request->getExists('download')) {
return $this->buildRawDiffResponse(
@ -153,10 +172,16 @@ final class DifferentialRevisionViewController extends DifferentialController {
$request_uri = $request->getRequestURI();
$limit = 100;
$large = $request->getStr('large');
if (count($changesets) > $limit && !$large) {
$count = count($changesets);
$large_warning =
($this->isLargeDiff()) &&
(!$this->isVeryLargeDiff()) &&
(!$large);
if ($large_warning) {
$count = $this->getChangesetCount();
$warning = new PHUIInfoView();
$warning->setTitle(pht('Large Diff'));
$warning->setSeverity(PHUIInfoView::SEVERITY_WARNING);
@ -357,23 +382,33 @@ final class DifferentialRevisionViewController extends DifferentialController {
$other_view = $this->renderOtherRevisions($other_revisions);
}
$this->buildPackageMaps($changesets);
if ($this->isVeryLargeDiff()) {
$toc_view = null;
// When rendering a "very large" diff, we skip computation of owners
// that own no files because it is significantly expensive and not very
// valuable.
foreach ($revision->getReviewers() as $reviewer) {
// Give each reviewer a dummy nonempty value so the UI does not render
// the "(Owns No Changed Paths)" note. If that behavior becomes more
// sophisticated in the future, this behavior might also need to.
$reviewer->attachChangesets($changesets);
}
} else {
$this->buildPackageMaps($changesets);
$toc_view = $this->buildTableOfContents(
$changesets,
$visible_changesets,
$target->loadCoverageMap($viewer));
}
// Attach changesets to each reviewer so we can show which Owners package
// reviewers own no files.
foreach ($revision->getReviewers() as $reviewer) {
$reviewer_phid = $reviewer->getReviewerPHID();
$reviewer_changesets = $this->getPackageChangesets($reviewer_phid);
$reviewer->attachChangesets($reviewer_changesets);
// Attach changesets to each reviewer so we can show which Owners package
// reviewers own no files.
foreach ($revision->getReviewers() as $reviewer) {
$reviewer_phid = $reviewer->getReviewerPHID();
$reviewer_changesets = $this->getPackageChangesets($reviewer_phid);
$reviewer->attachChangesets($reviewer_changesets);
}
}
$tab_group = id(new PHUITabGroupView());

View file

@ -7,11 +7,13 @@ final class DifferentialTransactionEditor
private $isCloseByCommit;
private $repositoryPHIDOverride = false;
private $didExpandInlineState = false;
private $affectedPaths;
private $firstBroadcast = false;
private $wasBroadcasting;
private $isDraftDemotion;
private $ownersDiff;
private $ownersChangesets;
public function getEditorApplicationClass() {
return 'PhabricatorDifferentialApplication';
}
@ -968,13 +970,20 @@ final class DifferentialTransactionEditor
return array();
}
if (!$this->affectedPaths) {
$diff = $this->ownersDiff;
$changesets = $this->ownersChangesets;
$this->ownersDiff = null;
$this->ownersChangesets = null;
if (!$changesets) {
return array();
}
$packages = PhabricatorOwnersPackage::loadAffectedPackages(
$packages = PhabricatorOwnersPackage::loadAffectedPackagesForChangesets(
$repository,
$this->affectedPaths);
$diff,
$changesets);
if (!$packages) {
return array();
}
@ -1255,9 +1264,12 @@ final class DifferentialTransactionEditor
$paths[] = $path_prefix.'/'.$changeset->getFilename();
}
// Save the affected paths; we'll use them later to query Owners. This
// uses the un-expanded paths.
$this->affectedPaths = $paths;
// If this change affected paths, save the changesets so we can apply
// Owners rules to them later.
if ($paths) {
$this->ownersDiff = $diff;
$this->ownersChangesets = $changesets;
}
// Mark this as also touching all parent paths, so you can see all pending
// changes to any file within a directory.

View file

@ -120,9 +120,10 @@ final class HeraldDifferentialRevisionAdapter
$repository = $this->loadRepository();
if ($repository) {
$packages = PhabricatorOwnersPackage::loadAffectedPackages(
$packages = PhabricatorOwnersPackage::loadAffectedPackagesForChangesets(
$repository,
$this->loadAffectedPaths());
$this->getDiff(),
$this->loadChangesets());
$this->affectedPackages = $packages;
}
}

View file

@ -542,6 +542,12 @@ final class DifferentialChangesetParser extends Phobject {
PhutilEventEngine::dispatchEvent($event);
$generated = $event->getValue('is_generated');
$attribute = $this->changeset->isGeneratedChangeset();
if ($attribute) {
$generated = true;
}
$this->specialAttributes[self::ATTR_GENERATED] = $generated;
}

View file

@ -12,7 +12,7 @@ final class DifferentialChangeset
protected $awayPaths;
protected $changeType;
protected $fileType;
protected $metadata;
protected $metadata = array();
protected $oldProperties;
protected $newProperties;
protected $addLines;
@ -24,6 +24,11 @@ final class DifferentialChangeset
const TABLE_CACHE = 'differential_changeset_parse_cache';
const METADATA_TRUSTED_ATTRIBUTES = 'attributes.trusted';
const METADATA_UNTRUSTED_ATTRIBUTES = 'attributes.untrusted';
const ATTRIBUTE_GENERATED = 'generated';
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
@ -266,6 +271,90 @@ final class DifferentialChangeset
return null;
}
public function setChangesetMetadata($key, $value) {
if (!is_array($this->metadata)) {
$this->metadata = array();
}
$this->metadata[$key] = $value;
return $this;
}
public function getChangesetMetadata($key, $default = null) {
if (!is_array($this->metadata)) {
return $default;
}
return idx($this->metadata, $key, $default);
}
private function setInternalChangesetAttribute($trusted, $key, $value) {
if ($trusted) {
$meta_key = self::METADATA_TRUSTED_ATTRIBUTES;
} else {
$meta_key = self::METADATA_UNTRUSTED_ATTRIBUTES;
}
$attributes = $this->getChangesetMetadata($meta_key, array());
$attributes[$key] = $value;
$this->setChangesetMetadata($meta_key, $attributes);
return $this;
}
private function getInternalChangesetAttributes($trusted) {
if ($trusted) {
$meta_key = self::METADATA_TRUSTED_ATTRIBUTES;
} else {
$meta_key = self::METADATA_UNTRUSTED_ATTRIBUTES;
}
return $this->getChangesetMetadata($meta_key, array());
}
public function setTrustedChangesetAttribute($key, $value) {
return $this->setInternalChangesetAttribute(true, $key, $value);
}
public function getTrustedChangesetAttributes() {
return $this->getInternalChangesetAttributes(true);
}
public function getTrustedChangesetAttribute($key, $default = null) {
$map = $this->getTrustedChangesetAttributes();
return idx($map, $key, $default);
}
public function setUntrustedChangesetAttribute($key, $value) {
return $this->setInternalChangesetAttribute(false, $key, $value);
}
public function getUntrustedChangesetAttributes() {
return $this->getInternalChangesetAttributes(false);
}
public function getUntrustedChangesetAttribute($key, $default = null) {
$map = $this->getUntrustedChangesetAttributes();
return idx($map, $key, $default);
}
public function getChangesetAttributes() {
// Prefer trusted values over untrusted values when both exist.
return
$this->getTrustedChangesetAttributes() +
$this->getUntrustedChangesetAttributes();
}
public function getChangesetAttribute($key, $default = null) {
$map = $this->getChangesetAttributes();
return idx($map, $key, $default);
}
public function isGeneratedChangeset() {
return $this->getChangesetAttribute(self::ATTRIBUTE_GENERATED);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -226,6 +226,8 @@ final class DifferentialDiff
$changeset->setAddLines($add_lines);
$changeset->setDelLines($del_lines);
self::detectGeneratedCode($changeset);
$diff->addUnsavedChangeset($changeset);
}
$diff->setLineCount($lines);
@ -821,5 +823,50 @@ final class DifferentialDiff
);
}
private static function detectGeneratedCode(
DifferentialChangeset $changeset) {
$is_generated_trusted = self::isTrustedGeneratedCode($changeset);
if ($is_generated_trusted) {
$changeset->setTrustedChangesetAttribute(
DifferentialChangeset::ATTRIBUTE_GENERATED,
$is_generated_trusted);
}
$is_generated_untrusted = self::isUntrustedGeneratedCode($changeset);
if ($is_generated_untrusted) {
$changeset->setUntrustedChangesetAttribute(
DifferentialChangeset::ATTRIBUTE_GENERATED,
$is_generated_untrusted);
}
}
private static function isTrustedGeneratedCode(
DifferentialChangeset $changeset) {
$filename = $changeset->getFilename();
$paths = PhabricatorEnv::getEnvConfig('differential.generated-paths');
foreach ($paths as $regexp) {
if (preg_match($regexp, $filename)) {
return true;
}
}
return false;
}
private static function isUntrustedGeneratedCode(
DifferentialChangeset $changeset) {
if ($changeset->getHunks()) {
$new_data = $changeset->makeNewFile();
if (strpos($new_data, '@'.'generated') !== false) {
return true;
}
}
return false;
}
}

View file

@ -69,7 +69,7 @@ final class DiffusionDocumentRenderingEngine
return;
}
protected function willRenderRef(PhabricatorDocumentRef $ref) {
protected function willStageRef(PhabricatorDocumentRef $ref) {
$drequest = $this->getDiffusionRequest();
$blame_uri = (string)$drequest->generateURI(
@ -78,9 +78,13 @@ final class DiffusionDocumentRenderingEngine
'stable' => true,
));
$ref
->setSymbolMetadata($this->getSymbolMetadata())
->setBlameURI($blame_uri);
$ref->setBlameURI($blame_uri);
}
protected function willRenderRef(PhabricatorDocumentRef $ref) {
$drequest = $this->getDiffusionRequest();
$ref->setSymbolMetadata($this->getSymbolMetadata());
$coverage = $drequest->loadCoverage();
if (strlen($coverage)) {

View file

@ -84,12 +84,29 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
$response->setMimeType($file->getViewableMimeType());
} else {
$is_post = $request->isHTTPPost();
$is_public = !$viewer->isLoggedIn();
// NOTE: Require POST to download files from the primary domain. If the
// request is not a POST request but arrives on the primary domain, we
// render a confirmation dialog. For discussion, see T13094.
$is_safe = ($is_alternate_domain || $is_lfs || $is_post);
// There are two exceptions to this rule:
// Git LFS requests can download with GET. This is safe (Git LFS won't
// execute files it downloads) and necessary to support Git LFS.
// Requests with no credentials may also download with GET. This
// primarily supports downloading files with `arc download` or other
// API clients. This is only "mostly" safe: if you aren't logged in, you
// are likely immune to XSS and CSRF. However, an attacker may still be
// able to set cookies on this domain (for example, to fixate your
// session). For now, we accept these risks because users running
// Phabricator in this mode are knowingly accepting a security risk
// against setup advice, and there's significant value in having
// API development against test and production installs work the same
// way.
$is_safe = ($is_alternate_domain || $is_post || $is_lfs || $is_public);
if (!$is_safe) {
return $this->newDialog()
->setSubmitURI($file->getDownloadURI())

View file

@ -7,6 +7,7 @@ abstract class PhabricatorDocumentEngine
private $highlightedLines = array();
private $encodingConfiguration;
private $highlightingConfiguration;
private $blameConfiguration = true;
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
@ -60,6 +61,19 @@ abstract class PhabricatorDocumentEngine
return $this->highlightingConfiguration;
}
final public function setBlameConfiguration($blame_configuration) {
$this->blameConfiguration = $blame_configuration;
return $this;
}
final public function getBlameConfiguration() {
return $this->blameConfiguration;
}
final protected function getBlameEnabled() {
return $this->blameConfiguration;
}
public function shouldRenderAsync(PhabricatorDocumentRef $ref) {
return false;
}

View file

@ -50,7 +50,7 @@ final class PhabricatorSourceDocumentEngine
}
$options = array();
if ($ref->getBlameURI()) {
if ($ref->getBlameURI() && $this->getBlameEnabled()) {
$content = phutil_split_lines($content);
$blame = range(1, count($content));
$blame = array_fuse($blame);

View file

@ -69,6 +69,9 @@ abstract class PhabricatorDocumentRenderingEngine
$engine->setHighlightingConfiguration($highlight_setting);
}
$blame_setting = ($request->getStr('blame') !== 'off');
$engine->setBlameConfiguration($blame_setting);
$views = array();
foreach ($engines as $candidate_key => $candidate_engine) {
$label = $candidate_engine->getViewAsLabel($ref);
@ -104,6 +107,8 @@ abstract class PhabricatorDocumentRenderingEngine
'controlID' => $control_id,
);
$this->willStageRef($ref);
if ($engine->shouldRenderAsync($ref)) {
$content = $engine->newLoadingContent($ref);
$config['next'] = 'render';
@ -142,7 +147,11 @@ abstract class PhabricatorDocumentRenderingEngine
'value' => $highlight_setting,
),
'blame' => array(
'icon' => 'fa-backward',
'hide' => pht('Hide Blame'),
'show' => pht('Show Blame'),
'uri' => $ref->getBlameURI(),
'enabled' => $blame_setting,
'value' => null,
),
'coverage' => array(
@ -180,6 +189,7 @@ abstract class PhabricatorDocumentRenderingEngine
}
final public function newRenderResponse(PhabricatorDocumentRef $ref) {
$this->willStageRef($ref);
$this->willRenderRef($ref);
$request = $this->getRequest();
@ -207,6 +217,9 @@ abstract class PhabricatorDocumentRenderingEngine
$engine->setHighlightingConfiguration($highlight_setting);
}
$blame_setting = ($request->getStr('blame') !== 'off');
$engine->setBlameConfiguration($blame_setting);
try {
$content = $engine->newDocument($ref);
} catch (Exception $ex) {
@ -314,6 +327,10 @@ abstract class PhabricatorDocumentRenderingEngine
return;
}
protected function willStageRef(PhabricatorDocumentRef $ref) {
return;
}
protected function willRenderRef(PhabricatorDocumentRef $ref) {
return;
}

View file

@ -260,6 +260,18 @@ final class PhabricatorEmbedFileRemarkupRule
$autoplay = null;
}
if ($is_video) {
// See T13135. Chrome refuses to play videos with type "video/quicktime",
// even though it may actually be able to play them. The least awful fix
// based on available information is to simply omit the "type" attribute
// from `<source />` tags. This causes Chrome to try to play the video
// and realize it can, and does not appear to produce any bad behavior in
// any other browser.
$mime_type = null;
} else {
$mime_type = $file->getMimeType();
}
return $this->newTag(
$tag,
array(
@ -274,7 +286,7 @@ final class PhabricatorEmbedFileRemarkupRule
'source',
array(
'src' => $file->getBestURI(),
'type' => $file->getMimeType(),
'type' => $mime_type,
)));
}

View file

@ -106,6 +106,14 @@ EOTEXT
),
'meta_data' => array(
'buildTargetPHID' => $build_target->getPHID(),
// See PHI611. These are undocumented secret magic.
'phabricator:build:id' => (int)$build->getID(),
'phabricator:build:url' =>
PhabricatorEnv::getProductionURI($build->getURI()),
'phabricator:buildable:id' => (int)$buildable->getID(),
'phabricator:buildable:url' =>
PhabricatorEnv::getProductionURI($buildable->getURI()),
),
);

View file

@ -137,4 +137,18 @@ final class HeraldRuleEditor
return pht('[Herald]');
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$body = parent::buildMailBody($object, $xactions);
$body->addLinkSection(
pht('RULE DETAIL'),
PhabricatorEnv::getProductionURI($object->getURI()));
return $body;
}
}

View file

@ -254,6 +254,10 @@ final class HeraldRule extends HeraldDAO
return 'H'.$this->getID();
}
public function getURI() {
return '/'.$this->getMonogram();
}
/* -( Repetition Policies )------------------------------------------------ */

View file

@ -585,7 +585,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
'task.ownerPHID IS NOT NULL');
}
if ($this->ownerPHIDs) {
if ($this->ownerPHIDs !== null) {
$subclause[] = qsprintf(
$conn,
'task.ownerPHID IN (%Ls)',

View file

@ -201,6 +201,16 @@ final class PhabricatorOwnersDetailController
}
$view->addProperty(pht('Auditing'), $auditing);
$ignored = $package->getIgnoredPathAttributes();
$ignored = array_keys($ignored);
if ($ignored) {
$ignored = implode(', ', $ignored);
} else {
$ignored = phutil_tag('em', array(), pht('None'));
}
$view->addProperty(pht('Ignored Attributes'), $ignored);
$description = $package->getDescription();
if (strlen($description)) {
$description = new PHUIRemarkupView($viewer, $description);

View file

@ -162,6 +162,13 @@ EOTEXT
->setIsConduitOnly(true)
->setValue($object->getStatus())
->setOptions($object->getStatusNameMap()),
id(new PhabricatorStringListEditField())
->setKey('ignored')
->setLabel(pht('Ignored Attributes'))
->setDescription(pht('Ignore paths with any of these attributes.'))
->setTransactionType(
PhabricatorOwnersPackageIgnoredTransaction::TRANSACTIONTYPE)
->setValue(array_keys($object->getIgnoredPathAttributes())),
id(new PhabricatorConduitEditField())
->setKey('paths.set')
->setLabel(pht('Paths'))

View file

@ -16,11 +16,11 @@ final class PhabricatorOwnersPackage
protected $auditingEnabled;
protected $autoReview;
protected $description;
protected $mailKey;
protected $status;
protected $viewPolicy;
protected $editPolicy;
protected $dominion;
protected $properties = array();
private $paths = self::ATTACHABLE;
private $owners = self::ATTACHABLE;
@ -41,6 +41,8 @@ final class PhabricatorOwnersPackage
const DOMINION_STRONG = 'strong';
const DOMINION_WEAK = 'weak';
const PROPERTY_IGNORED = 'ignored';
public static function initializeNewPackage(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
@ -118,11 +120,13 @@ final class PhabricatorOwnersPackage
// This information is better available from the history table.
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort',
'description' => 'text',
'auditingEnabled' => 'bool',
'mailKey' => 'bytes20',
'status' => 'text32',
'autoReview' => 'text32',
'dominion' => 'text32',
@ -130,28 +134,36 @@ final class PhabricatorOwnersPackage
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorOwnersPackagePHIDType::TYPECONST);
}
public function save() {
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
public function getPHIDType() {
return PhabricatorOwnersPackagePHIDType::TYPECONST;
}
public function isArchived() {
return ($this->getStatus() == self::STATUS_ARCHIVED);
}
public function setName($name) {
$this->name = $name;
public function getMustMatchUngeneratedPaths() {
$ignore_attributes = $this->getIgnoredPathAttributes();
return !empty($ignore_attributes['generated']);
}
public function getPackageProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setPackageProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function getIgnoredPathAttributes() {
return $this->getPackageProperty(self::PROPERTY_IGNORED, array());
}
public function setIgnoredPathAttributes(array $attributes) {
return $this->setPackageProperty(self::PROPERTY_IGNORED, $attributes);
}
public function loadOwners() {
if (!$this->getID()) {
return array();
@ -181,6 +193,82 @@ final class PhabricatorOwnersPackage
return self::loadPackagesForPaths($repository, $paths);
}
public static function loadAffectedPackagesForChangesets(
PhabricatorRepository $repository,
DifferentialDiff $diff,
array $changesets) {
assert_instances_of($changesets, 'DifferentialChangeset');
$paths_all = array();
$paths_ungenerated = array();
foreach ($changesets as $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $diff);
$paths_all[] = $path;
if (!$changeset->isGeneratedChangeset()) {
$paths_ungenerated[] = $path;
}
}
if (!$paths_all) {
return array();
}
$packages_all = self::loadAffectedPackages(
$repository,
$paths_all);
// If there are no generated changesets, we can't possibly need to throw
// away any packages for matching only generated paths. Just return the
// full set of packages.
if ($paths_ungenerated === $paths_all) {
return $packages_all;
}
$must_match_ungenerated = array();
foreach ($packages_all as $package) {
if ($package->getMustMatchUngeneratedPaths()) {
$must_match_ungenerated[] = $package;
}
}
// If no affected packages have the "Ignore Generated Paths" flag set, we
// can't possibly need to throw any away.
if (!$must_match_ungenerated) {
return $packages_all;
}
if ($paths_ungenerated) {
$packages_ungenerated = self::loadAffectedPackages(
$repository,
$paths_ungenerated);
} else {
$packages_ungenerated = array();
}
// We have some generated paths, and some packages that ignore generated
// paths. Take all the packages which:
//
// - ignore generated paths; and
// - didn't match any ungenerated paths
//
// ...and remove them from the list.
$must_match_ungenerated = mpull($must_match_ungenerated, null, 'getID');
$packages_ungenerated = mpull($packages_ungenerated, null, 'getID');
$packages_all = mpull($packages_all, null, 'getID');
foreach ($must_match_ungenerated as $package_id => $package) {
if (!isset($packages_ungenerated[$package_id])) {
unset($packages_all[$package_id]);
}
}
return $packages_all;
}
public static function loadOwningPackages($repository, $path) {
if (empty($path)) {
return array();
@ -614,6 +702,10 @@ final class PhabricatorOwnersPackage
->setKey('dominion')
->setType('map<string, wild>')
->setDescription(pht('Dominion setting information.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('ignored')
->setType('map<string, wild>')
->setDescription(pht('Ignored attribute information.')),
);
}
@ -667,6 +759,13 @@ final class PhabricatorOwnersPackage
'short' => $dominion_short,
);
// Force this to always emit as a JSON object even if empty, never as
// a JSON list.
$ignored = $this->getIgnoredPathAttributes();
if (!$ignored) {
$ignored = (object)array();
}
return array(
'name' => $this->getName(),
'description' => $this->getDescription(),
@ -675,6 +774,7 @@ final class PhabricatorOwnersPackage
'review' => $review,
'audit' => $audit,
'dominion' => $dominion,
'ignored' => $ignored,
);
}

View file

@ -0,0 +1,85 @@
<?php
final class PhabricatorOwnersPackageIgnoredTransaction
extends PhabricatorOwnersPackageTransactionType {
const TRANSACTIONTYPE = 'owners.ignored';
public function generateOldValue($object) {
return $object->getIgnoredPathAttributes();
}
public function generateNewValue($object, $value) {
return array_fill_keys($value, true);
}
public function applyInternalEffects($object, $value) {
$object->setIgnoredPathAttributes($value);
}
public function getTitle() {
$old = array_keys($this->getOldValue());
$new = array_keys($this->getNewValue());
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
$all_n = new PhutilNumber(count($add) + count($rem));
$add_n = phutil_count($add);
$rem_n = phutil_count($rem);
if ($new && $old) {
return pht(
'%s changed %s ignored attribute(s), added %s: %s; removed %s: %s.',
$this->renderAuthor(),
$all_n,
$add_n,
$this->renderValueList($add),
$rem_n,
$this->renderValueList($rem));
} else if ($new) {
return pht(
'%s changed %s ignored attribute(s), added %s: %s.',
$this->renderAuthor(),
$all_n,
$add_n,
$this->rendervalueList($add));
} else {
return pht(
'%s changed %s ignored attribute(s), removed %s: %s.',
$this->renderAuthor(),
$all_n,
$rem_n,
$this->rendervalueList($rem));
}
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$valid_attributes = array(
'generated' => true,
);
foreach ($xactions as $xaction) {
$new = $xaction->getNewValue();
foreach ($new as $attribute) {
if (isset($valid_attributes[$attribute])) {
continue;
}
$errors[] = $this->newInvalidError(
pht(
'Changeset attribute "%s" is not valid. Valid changeset '.
'attributes are: %s.',
$attribute,
implode(', ', array_keys($valid_attributes))),
$xaction);
}
}
return $errors;
}
}

View file

@ -108,19 +108,23 @@ final class PhabricatorOwnersPackagePathsTransaction
// paths now.
$display_map = array();
$seen_map = array();
foreach ($new as $key => $spec) {
$display_path = $spec['path'];
$raw_path = rtrim($display_path, '/').'/';
// If the user entered two paths which normalize to the same value
// (like "src/main.c" and "src/main.c/"), discard the duplicates.
if (isset($display_map[$raw_path])) {
// If the user entered two paths in the same repository which normalize
// to the same value (like "src/main.c" and "src/main.c/"), discard the
// duplicates.
$repository_phid = $spec['repositoryPHID'];
if (isset($seen_map[$repository_phid][$raw_path])) {
unset($new[$key]);
continue;
}
$new[$key]['path'] = $raw_path;
$display_map[$raw_path] = $display_path;
$seen_map[$repository_phid][$raw_path] = true;
}
$diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new);

View file

@ -0,0 +1,82 @@
<?php
final class PhrictionDocumentPolicyCodex
extends PhabricatorPolicyCodex {
public function getPolicySpecialRuleDescriptions() {
$object = $this->getObject();
$strongest_policy = $this->getStrongestPolicy();
$rules = array();
$rules[] = $this->newRule()
->setDescription(
pht('To view a wiki document, you must also be able to view all '.
'of its ancestors. The most-restrictive view policy of this '.
'document\'s ancestors is "%s".',
$strongest_policy->getShortName()))
->setCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW));
$rules[] = $this->newRule()
->setDescription(
pht('To edit a wiki document, you must also be able to view all '.
'of its ancestors.'))
->setCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT));
return $rules;
}
public function getDefaultPolicy() {
$ancestors = $this->getObject()->getAncestors();
if ($ancestors) {
$root = head($ancestors);
} else {
$root = $this->getObject();
}
$root_policy_phid = $root->getPolicy($this->getCapability());
return id(new PhabricatorPolicyQuery())
->setViewer($this->getViewer())
->withPHIDs(array($root_policy_phid))
->executeOne();
}
public function compareToDefaultPolicy(PhabricatorPolicy $policy) {
$root_policy = $this->getDefaultPolicy();
$strongest_policy = $this->getStrongestPolicy();
// Note that we never return 'weaker', because Phriction documents can
// never have weaker permissions than their parents. If this object has
// been set to weaker permissions anyway, return 'adjusted'.
if ($root_policy == $strongest_policy) {
$strength = null;
} else if ($strongest_policy->isStrongerThan($root_policy)) {
$strength = PhabricatorPolicyStrengthConstants::STRONGER;
} else {
$strength = PhabricatorPolicyStrengthConstants::ADJUSTED;
}
return $strength;
}
private function getStrongestPolicy() {
$ancestors = $this->getObject()->getAncestors();
$ancestors[] = $this->getObject();
$strongest_policy = $this->getDefaultPolicy();
foreach ($ancestors as $ancestor) {
$ancestor_policy_phid = $ancestor->getPolicy($this->getCapability());
$ancestor_policy = id(new PhabricatorPolicyQuery())
->setViewer($this->getViewer())
->withPHIDs(array($ancestor_policy_phid))
->executeOne();
if ($ancestor_policy->isStrongerThan($strongest_policy)) {
$strongest_policy = $ancestor_policy;
}
}
return $strongest_policy;
}
}

View file

@ -219,6 +219,13 @@ final class PhrictionEditController
->execute();
$view_capability = PhabricatorPolicyCapability::CAN_VIEW;
$edit_capability = PhabricatorPolicyCapability::CAN_EDIT;
$codex = id(PhabricatorPolicyCodex::newFromObject($document, $viewer))
->setCapability($view_capability);
$view_capability_description = $codex->getPolicySpecialRuleForCapability(
PhabricatorPolicyCapability::CAN_VIEW)->getDescription();
$edit_capability_description = $codex->getPolicySpecialRuleForCapability(
PhabricatorPolicyCapability::CAN_EDIT)->getDescription();
$form = id(new AphrontFormView())
->setUser($viewer)
@ -264,16 +271,14 @@ final class PhrictionEditController
->setPolicyObject($document)
->setCapability($view_capability)
->setPolicies($policies)
->setCaption(
$document->describeAutomaticCapability($view_capability)))
->setCaption($view_capability_description))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('editPolicy')
->setPolicyObject($document)
->setCapability($edit_capability)
->setPolicies($policies)
->setCaption(
$document->describeAutomaticCapability($edit_capability)))
->setCaption($edit_capability_description))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Edit Notes'))

View file

@ -11,7 +11,8 @@ final class PhrictionDocument extends PhrictionDAO
PhabricatorFerretInterface,
PhabricatorProjectInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorConduitResultInterface {
PhabricatorConduitResultInterface,
PhabricatorPolicyCodexInterface {
protected $slug;
protected $depth;
@ -200,22 +201,6 @@ final class PhrictionDocument extends PhrictionDAO
return false;
}
public function describeAutomaticCapability($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return pht(
'To view a wiki document, you must also be able to view all '.
'of its parents.');
case PhabricatorPolicyCapability::CAN_EDIT:
return pht(
'To edit a wiki document, you must also be able to view all '.
'of its parents.');
}
return null;
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
@ -328,4 +313,13 @@ final class PhrictionDocument extends PhrictionDAO
->setAttachmentKey('content'),
);
}
/* -( PhabricatorPolicyCodexInterface )------------------------------------ */
public function newPolicyCodex() {
return new PhrictionDocumentPolicyCodex();
}
}

View file

@ -29,6 +29,27 @@ abstract class PhabricatorPolicyCodex
return array();
}
public function getDefaultPolicy() {
return PhabricatorPolicyQuery::getDefaultPolicyForObject(
$this->viewer,
$this->object,
$this->capability);
}
public function compareToDefaultPolicy(PhabricatorPolicy $policy) {
return null;
}
final public function getPolicySpecialRuleForCapability($capability) {
foreach ($this->getPolicySpecialRuleDescriptions() as $rule) {
if (in_array($capability, $rule->getCapabilities())) {
return $rule;
}
}
return null;
}
final protected function newRule() {
return new PhabricatorPolicyCodexRuleDescription();
}

View file

@ -0,0 +1,9 @@
<?php
final class PhabricatorPolicyStrengthConstants
extends PhabricatorPolicyConstants {
const WEAKER = 'weaker';
const STRONGER = 'stronger';
const ADJUSTED = 'adjusted';
}

View file

@ -169,25 +169,47 @@ final class PhabricatorPolicyExplainController
$capability) {
$viewer = $this->getViewer();
$default_policy = PhabricatorPolicyQuery::getDefaultPolicyForObject(
$viewer,
$object,
$capability);
if (!$default_policy) {
$strength = null;
if ($object instanceof PhabricatorPolicyCodexInterface) {
$codex = id(PhabricatorPolicyCodex::newFromObject($object, $viewer))
->setCapability($capability);
$strength = $codex->compareToDefaultPolicy($policy);
$default_policy = $codex->getDefaultPolicy();
} else {
$default_policy = PhabricatorPolicyQuery::getDefaultPolicyForObject(
$viewer,
$object,
$capability);
if ($default_policy) {
if ($default_policy->getPHID() == $policy->getPHID()) {
return;
}
if ($default_policy->getPHID() != $policy->getPHID()) {
if ($default_policy->isStrongerThan($policy)) {
$strength = PhabricatorPolicyStrengthConstants::WEAKER;
} else if ($policy->isStrongerThan($default_policy)) {
$strength = PhabricatorPolicyStrengthConstants::STRONGER;
} else {
$strength = PhabricatorPolicyStrengthConstants::ADJUSTED;
}
}
}
}
if (!$strength) {
return;
}
if ($default_policy->getPHID() == $policy->getPHID()) {
return;
}
if ($default_policy->isStrongerThan($policy)) {
if ($strength == PhabricatorPolicyStrengthConstants::WEAKER) {
$info = pht(
'This object has a less restrictive policy ("%s") than the default '.
'policy for similar objects (which is "%s").',
$policy->getShortName(),
$default_policy->getShortName());
} else if ($policy->isStrongerThan($default_policy)) {
} else if ($strength == PhabricatorPolicyStrengthConstants::STRONGER) {
$info = pht(
'This object has a more restrictive policy ("%s") than the default '.
'policy for similar objects (which is "%s").',

View file

@ -434,11 +434,12 @@ final class PhabricatorPolicy
$capability,
$active_only) {
$exceptions = array();
if ($object instanceof PhabricatorPolicyCodexInterface) {
$codex = PhabricatorPolicyCodex::newFromObject($object, $viewer);
$codex = id(PhabricatorPolicyCodex::newFromObject($object, $viewer))
->setCapability($capability);
$rules = $codex->getPolicySpecialRuleDescriptions();
$exceptions = array();
foreach ($rules as $rule) {
$is_active = $rule->getIsActive();
if ($is_active) {
@ -467,11 +468,13 @@ final class PhabricatorPolicy
$exceptions[] = $description;
}
} else if (method_exists($object, 'describeAutomaticCapability')) {
$exceptions = (array)$object->describeAutomaticCapability($capability);
$exceptions = array_filter($exceptions);
} else {
$exceptions = array();
}
if (!$exceptions) {
if (method_exists($object, 'describeAutomaticCapability')) {
$exceptions = (array)$object->describeAutomaticCapability($capability);
$exceptions = array_filter($exceptions);
}
}
return $exceptions;

View file

@ -473,30 +473,47 @@ final class PHUIHeaderView extends AphrontTagView {
// policy differs from the default policy. If it does, we'll call it out
// as changed.
if (!$use_space_policy) {
$default_policy = PhabricatorPolicyQuery::getDefaultPolicyForObject(
$viewer,
$object,
$view_capability);
if ($default_policy) {
if ($default_policy->getPHID() != $policy->getPHID()) {
$container_classes[] = 'policy-adjusted';
if ($default_policy->isStrongerThan($policy)) {
// The policy has strictly been weakened. For example, the
// default might be "All Users" and the current policy is "Public".
$container_classes[] = 'policy-adjusted-weaker';
} else if ($policy->isStrongerThan($default_policy)) {
// The policy has strictly been strengthened, and is now more
// restrictive than the default. For example, "All Users" has
// been replaced with "No One".
$container_classes[] = 'policy-adjusted-stronger';
} else {
// The policy has been adjusted but not strictly strengthened
// or weakened. For example, "Members of X" has been replaced with
// "Members of Y".
$container_classes[] = 'policy-adjusted-different';
$strength = null;
if ($object instanceof PhabricatorPolicyCodexInterface) {
$codex = id(PhabricatorPolicyCodex::newFromObject($object, $viewer))
->setCapability($view_capability);
$strength = $codex->compareToDefaultPolicy($policy);
} else {
$default_policy = PhabricatorPolicyQuery::getDefaultPolicyForObject(
$viewer,
$object,
$view_capability);
if ($default_policy) {
if ($default_policy->getPHID() != $policy->getPHID()) {
if ($default_policy->isStrongerThan($policy)) {
$strength = PhabricatorPolicyStrengthConstants::WEAKER;
} else if ($policy->isStrongerThan($default_policy)) {
$strength = PhabricatorPolicyStrengthConstants::STRONGER;
} else {
$strength = PhabricatorPolicyStrengthConstants::ADJUSTED;
}
}
}
}
if ($strength) {
if ($strength == PhabricatorPolicyStrengthConstants::WEAKER) {
// The policy has strictly been weakened. For example, the
// default might be "All Users" and the current policy is "Public".
$container_classes[] = 'policy-adjusted-weaker';
} else if ($strength == PhabricatorPolicyStrengthConstants::STRONGER) {
// The policy has strictly been strengthened, and is now more
// restrictive than the default. For example, "All Users" has
// been replaced with "No One".
$container_classes[] = 'policy-adjusted-stronger';
} else {
// The policy has been adjusted but not strictly strengthened
// or weakened. For example, "Members of X" has been replaced with
// "Members of Y".
$container_classes[] = 'policy-adjusted-different';
}
}
}
$policy_name = array($policy->getShortName());

View file

@ -105,6 +105,29 @@ JX.behavior('document-engine', function(config, statics) {
list.addItem(highlight_item);
var blame_item;
if (data.blame.uri) {
blame_item = new JX.PHUIXActionView()
.setIcon(data.blame.icon);
var onblame = JX.bind(null, function(data, e) {
e.prevent();
if (blame_item.getDisabled()) {
return;
}
data.blame.enabled = !data.blame.enabled;
onview(data);
menu.close();
}, data);
blame_item.setHandler(onblame);
list.addItem(blame_item);
}
menu.setContent(list.getNode());
menu.listen('open', function() {
@ -118,8 +141,22 @@ JX.behavior('document-engine', function(config, statics) {
if (is_selected) {
encode_item.setDisabled(!engine.spec.canEncode);
highlight_item.setDisabled(!engine.spec.canHighlight);
if (blame_item) {
blame_item.setDisabled(!engine.spec.canBlame);
}
}
}
if (blame_item) {
var blame_label;
if (data.blame.enabled) {
blame_label = data.blame.hide;
} else {
blame_label = data.blame.show;
}
blame_item.setName(blame_label);
}
});
data.menu = menu;
@ -137,6 +174,12 @@ JX.behavior('document-engine', function(config, statics) {
uri.setQueryParam('encode', data.encode.value);
}
if (data.blame.enabled) {
uri.setQueryParam('blame', null);
} else {
uri.setQueryParam('blame', 'off');
}
return uri.toString();
}
@ -211,7 +254,7 @@ JX.behavior('document-engine', function(config, statics) {
JX.DOM.setContent(viewport, JX.$H(r.markup));
// If this engine supports rendering blame, populate or draw it.
if (spec.canBlame) {
if (spec.canBlame && data.blame.enabled) {
blame(data);
}
}