diff --git a/resources/celerity/map.php b/resources/celerity/map.php
index 78b3660c4c..2631b54f8c 100644
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -9,8 +9,8 @@ return array(
'names' => array(
'conpherence.pkg.css' => 'e68cf1fa',
'conpherence.pkg.js' => '15191c65',
- 'core.pkg.css' => 'cb8ae4dc',
- 'core.pkg.js' => 'e1f0f7bd',
+ 'core.pkg.css' => '8be474cc',
+ 'core.pkg.js' => 'e452721e',
'differential.pkg.css' => '06dc617c',
'differential.pkg.js' => 'c2ca903a',
'diffusion.pkg.css' => 'a2d17c7d',
@@ -144,7 +144,7 @@ return array(
'rsrc/css/phui/phui-cms.css' => '504b4b23',
'rsrc/css/phui/phui-comment-form.css' => 'ac68149f',
'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad',
- 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb',
+ 'rsrc/css/phui/phui-crumbs-view.css' => '10728aaa',
'rsrc/css/phui/phui-curtain-view.css' => '2bdaf026',
'rsrc/css/phui/phui-document-pro.css' => '8af7ea27',
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
@@ -259,7 +259,7 @@ return array(
'rsrc/externals/javelin/lib/__tests__/URI.js' => '1e45fda9',
'rsrc/externals/javelin/lib/__tests__/behavior.js' => '1ea62783',
'rsrc/externals/javelin/lib/behavior.js' => '61cbc29a',
- 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '8d3bc1b2',
+ 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => 'dfaf006b',
'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => '70baed2f',
'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => '185bbd53',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd',
@@ -710,7 +710,7 @@ return array(
'javelin-scrollbar' => '9065f639',
'javelin-sound' => '949c0fe5',
'javelin-stratcom' => '327f418a',
- 'javelin-tokenizer' => '8d3bc1b2',
+ 'javelin-tokenizer' => 'dfaf006b',
'javelin-typeahead' => '70baed2f',
'javelin-typeahead-composite-source' => '503e17fd',
'javelin-typeahead-normalizer' => '185bbd53',
@@ -810,7 +810,7 @@ return array(
'phui-cms-css' => '504b4b23',
'phui-comment-form-css' => 'ac68149f',
'phui-comment-panel-css' => 'f50152ad',
- 'phui-crumbs-view-css' => '6ece3bbb',
+ 'phui-crumbs-view-css' => '10728aaa',
'phui-curtain-view-css' => '2bdaf026',
'phui-document-summary-view-css' => '9ca48bdf',
'phui-document-view-css' => '878c2f52',
@@ -1573,12 +1573,6 @@ return array(
'javelin-stratcom',
'javelin-behavior',
),
- '8d3bc1b2' => array(
- 'javelin-dom',
- 'javelin-util',
- 'javelin-stratcom',
- 'javelin-install',
- ),
'8d4a8c72' => array(
'javelin-install',
'javelin-dom',
@@ -2025,6 +2019,12 @@ return array(
'phuix-icon-view',
'phabricator-prefab',
),
+ 'dfaf006b' => array(
+ 'javelin-dom',
+ 'javelin-util',
+ 'javelin-stratcom',
+ 'javelin-install',
+ ),
'e1d25dfb' => array(
'javelin-behavior',
'javelin-stratcom',
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 78ae048715..e1b13dbecb 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -816,6 +816,7 @@ phutil_register_library_map(array(
'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php',
'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php',
'DiffusionInlineCommentPreviewController' => 'applications/diffusion/controller/DiffusionInlineCommentPreviewController.php',
+ 'DiffusionInternalAncestorsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php',
'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php',
'DiffusionLastModifiedController' => 'applications/diffusion/controller/DiffusionLastModifiedController.php',
'DiffusionLastModifiedQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php',
@@ -2525,6 +2526,7 @@ phutil_register_library_map(array(
'PhabricatorChatLogDAO' => 'applications/chatlog/storage/PhabricatorChatLogDAO.php',
'PhabricatorChatLogEvent' => 'applications/chatlog/storage/PhabricatorChatLogEvent.php',
'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php',
+ 'PhabricatorCheckboxesEditField' => 'applications/transactions/editfield/PhabricatorCheckboxesEditField.php',
'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php',
'PhabricatorClassConfigType' => 'applications/config/type/PhabricatorClassConfigType.php',
'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php',
@@ -2881,6 +2883,7 @@ phutil_register_library_map(array(
'PhabricatorDocumentRef' => 'applications/files/document/PhabricatorDocumentRef.php',
'PhabricatorDocumentRenderingEngine' => 'applications/files/document/render/PhabricatorDocumentRenderingEngine.php',
'PhabricatorDoorkeeperApplication' => 'applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php',
+ 'PhabricatorDoubleExportField' => 'infrastructure/export/field/PhabricatorDoubleExportField.php',
'PhabricatorDraft' => 'applications/draft/storage/PhabricatorDraft.php',
'PhabricatorDraftDAO' => 'applications/draft/storage/PhabricatorDraftDAO.php',
'PhabricatorDraftEngine' => 'applications/transactions/draft/PhabricatorDraftEngine.php',
@@ -6147,6 +6150,7 @@ phutil_register_library_map(array(
'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController',
'DiffusionInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController',
+ 'DiffusionInternalAncestorsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionLastModifiedController' => 'DiffusionController',
'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
@@ -8140,6 +8144,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface',
),
'PhabricatorChatLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhabricatorCheckboxesEditField' => 'PhabricatorEditField',
'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorClassConfigType' => 'PhabricatorTextConfigType',
'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions',
@@ -8538,6 +8543,7 @@ phutil_register_library_map(array(
'PhabricatorDocumentRef' => 'Phobject',
'PhabricatorDocumentRenderingEngine' => 'Phobject',
'PhabricatorDoorkeeperApplication' => 'PhabricatorApplication',
+ 'PhabricatorDoubleExportField' => 'PhabricatorExportField',
'PhabricatorDraft' => 'PhabricatorDraftDAO',
'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
'PhabricatorDraftEngine' => 'Phobject',
diff --git a/src/applications/audit/query/PhabricatorCommitSearchEngine.php b/src/applications/audit/query/PhabricatorCommitSearchEngine.php
index 6811427c93..993626e1e3 100644
--- a/src/applications/audit/query/PhabricatorCommitSearchEngine.php
+++ b/src/applications/audit/query/PhabricatorCommitSearchEngine.php
@@ -53,6 +53,10 @@ final class PhabricatorCommitSearchEngine
$query->withUnreachable($map['unreachable']);
}
+ if ($map['ancestorsOf']) {
+ $query->withAncestorsOf($map['ancestorsOf']);
+ }
+
return $query;
}
@@ -103,6 +107,13 @@ final class PhabricatorCommitSearchEngine
pht(
'Find or exclude unreachable commits which are not ancestors of '.
'any branch, tag, or ref.')),
+ id(new PhabricatorSearchStringListField())
+ ->setLabel(pht('Ancestors Of'))
+ ->setKey('ancestorsOf')
+ ->setDescription(
+ pht(
+ 'Find commits which are ancestors of a particular ref, '.
+ 'like "master".')),
);
}
diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php
index 9959bfabab..6b13e893d5 100644
--- a/src/applications/differential/controller/DifferentialRevisionViewController.php
+++ b/src/applications/differential/controller/DifferentialRevisionViewController.php
@@ -182,25 +182,31 @@ final class DifferentialRevisionViewController
if ($large_warning) {
$count = $this->getChangesetCount();
- $warning = new PHUIInfoView();
- $warning->setTitle(pht('Large Diff'));
- $warning->setSeverity(PHUIInfoView::SEVERITY_WARNING);
- $warning->appendChild(hsprintf(
- '%s %s',
+ $expand_uri = $request_uri
+ ->alter('large', 'true')
+ ->setFragment('toc');
+
+ $message = array(
pht(
- 'This diff is large and affects %s files. '.
- 'You may load each file individually or ',
+ 'This large diff affects %s files. Files without inline '.
+ 'comments have been collapsed.',
new PhutilNumber($count)),
+ ' ',
phutil_tag(
- 'a',
- array(
- 'class' => 'button button-grey',
- 'href' => $request_uri
- ->alter('large', 'true')
- ->setFragment('toc'),
- ),
- pht('Show All Files Inline'))));
- $warning = $warning->render();
+ 'strong',
+ array(),
+ phutil_tag(
+ 'a',
+ array(
+ 'href' => $expand_uri,
+ ),
+ pht('Expand All Files'))),
+ );
+
+ $warning = id(new PHUIInfoView())
+ ->setTitle(pht('Large Diff'))
+ ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
+ ->appendChild($message);
$old = array_select_keys($changesets, $old_ids);
$new = array_select_keys($changesets, $new_ids);
diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php
index 3271248ce8..64ee1d5e81 100644
--- a/src/applications/differential/editor/DifferentialTransactionEditor.php
+++ b/src/applications/differential/editor/DifferentialTransactionEditor.php
@@ -682,8 +682,13 @@ final class DifferentialTransactionEditor
if ($config_inline || $config_attach) {
$body_limit = PhabricatorEnv::getEnvConfig('metamta.email-body-limit');
- $patch = $this->buildPatchForMail($diff);
- if ($config_inline) {
+ try {
+ $patch = $this->buildPatchForMail($diff, $body_limit);
+ } catch (ArcanistDiffByteSizeException $ex) {
+ $patch = null;
+ }
+
+ if (($patch !== null) && $config_inline) {
$lines = substr_count($patch, "\n");
$bytes = strlen($patch);
@@ -706,7 +711,7 @@ final class DifferentialTransactionEditor
}
}
- if ($config_attach) {
+ if (($patch !== null) && $config_attach) {
// See T12033, T11767, and PHI55. This is a crude fix to stop the
// major concrete problems that lackluster email size limits cause.
if (strlen($patch) < $body_limit) {
@@ -1411,13 +1416,14 @@ final class DifferentialTransactionEditor
array('style' => 'font-family: monospace;'), $patch);
}
- private function buildPatchForMail(DifferentialDiff $diff) {
+ private function buildPatchForMail(DifferentialDiff $diff, $byte_limit) {
$format = PhabricatorEnv::getEnvConfig('metamta.differential.patch-format');
return id(new DifferentialRawDiffRenderer())
->setViewer($this->getActor())
->setFormat($format)
->setChangesets($diff->getChangesets())
+ ->setByteLimit($byte_limit)
->buildPatch();
}
diff --git a/src/applications/differential/render/DifferentialRawDiffRenderer.php b/src/applications/differential/render/DifferentialRawDiffRenderer.php
index 0b826e9ba8..4f25646e4a 100644
--- a/src/applications/differential/render/DifferentialRawDiffRenderer.php
+++ b/src/applications/differential/render/DifferentialRawDiffRenderer.php
@@ -5,6 +5,7 @@ final class DifferentialRawDiffRenderer extends Phobject {
private $changesets;
private $format = 'unified';
private $viewer;
+ private $byteLimit;
public function setFormat($format) {
$this->format = $format;
@@ -35,6 +36,15 @@ final class DifferentialRawDiffRenderer extends Phobject {
return $this->viewer;
}
+ public function setByteLimit($byte_limit) {
+ $this->byteLimit = $byte_limit;
+ return $this;
+ }
+
+ public function getByteLimit() {
+ return $this->byteLimit;
+ }
+
public function buildPatch() {
$diff = new DifferentialDiff();
$diff->attachChangesets($this->getChangesets());
@@ -52,15 +62,18 @@ final class DifferentialRawDiffRenderer extends Phobject {
$bundle = ArcanistBundle::newFromChanges($changes);
$bundle->setLoadFileDataCallback(array($loader, 'loadFileData'));
+ $byte_limit = $this->getByteLimit();
+ if ($byte_limit) {
+ $bundle->setByteLimit($byte_limit);
+ }
+
$format = $this->getFormat();
switch ($format) {
case 'git':
return $bundle->toGitPatch();
- break;
case 'unified':
default:
return $bundle->toUnifiedDiff();
- break;
}
}
}
diff --git a/src/applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php
new file mode 100644
index 0000000000..01808bb4eb
--- /dev/null
+++ b/src/applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php
@@ -0,0 +1,51 @@
+';
+ }
+
+ protected function defineCustomParamTypes() {
+ return array(
+ 'ref' => 'required string',
+ 'commits' => 'required list',
+ );
+ }
+
+ protected function getResult(ConduitAPIRequest $request) {
+ $drequest = $this->getDiffusionRequest();
+ $repository = $drequest->getRepository();
+
+ $commits = $request->getValue('commits');
+ $ref = $request->getValue('ref');
+
+ $graph = new PhabricatorGitGraphStream($repository, $ref);
+
+ $keep = array();
+ foreach ($commits as $identifier) {
+ try {
+ $graph->getCommitDate($identifier);
+ $keep[] = $identifier;
+ } catch (Exception $ex) {
+ // Not an ancestor.
+ }
+ }
+
+ return $keep;
+ }
+
+}
diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php
index 8996bab628..53e8d51bf9 100644
--- a/src/applications/diffusion/query/DiffusionCommitQuery.php
+++ b/src/applications/diffusion/query/DiffusionCommitQuery.php
@@ -22,10 +22,14 @@ final class DiffusionCommitQuery
private $epochMin;
private $epochMax;
private $importing;
+ private $ancestorsOf;
private $needCommitData;
private $needDrafts;
+ private $mustFilterRefs = false;
+ private $refRepository;
+
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
@@ -92,7 +96,7 @@ final class DiffusionCommitQuery
}
public function withRepositoryIDs(array $repository_ids) {
- $this->repositoryIDs = $repository_ids;
+ $this->repositoryIDs = array_unique($repository_ids);
return $this;
}
@@ -152,6 +156,11 @@ final class DiffusionCommitQuery
return $this;
}
+ public function withAncestorsOf(array $refs) {
+ $this->ancestorsOf = $refs;
+ return $this;
+ }
+
public function getIdentifierMap() {
if ($this->identifierMap === null) {
throw new Exception(
@@ -307,6 +316,54 @@ final class DiffusionCommitQuery
protected function didFilterPage(array $commits) {
$viewer = $this->getViewer();
+ if ($this->mustFilterRefs) {
+ // If this flag is set, the query has an "Ancestors Of" constraint and
+ // at least one of the constraining refs had too many ancestors for us
+ // to apply the constraint with a big "commitIdentifier IN (%Ls)" clause.
+ // We're going to filter each page and hope we get a full result set
+ // before the query overheats.
+
+ $ancestor_list = mpull($commits, 'getCommitIdentifier');
+ $ancestor_list = array_values($ancestor_list);
+
+ foreach ($this->ancestorsOf as $ref) {
+ try {
+ $ancestor_list = DiffusionQuery::callConduitWithDiffusionRequest(
+ $viewer,
+ DiffusionRequest::newFromDictionary(
+ array(
+ 'repository' => $this->refRepository,
+ 'user' => $viewer,
+ )),
+ 'diffusion.internal.ancestors',
+ array(
+ 'ref' => $ref,
+ 'commits' => $ancestor_list,
+ ));
+ } catch (ConduitClientException $ex) {
+ throw new PhabricatorSearchConstraintException(
+ $ex->getMessage());
+ }
+
+ if (!$ancestor_list) {
+ break;
+ }
+ }
+
+ $ancestor_list = array_fuse($ancestor_list);
+ foreach ($commits as $key => $commit) {
+ $identifier = $commit->getCommitIdentifier();
+ if (!isset($ancestor_list[$identifier])) {
+ $this->didRejectResult($commit);
+ unset($commits[$key]);
+ }
+ }
+
+ if (!$commits) {
+ return $commits;
+ }
+ }
+
if ($this->needCommitData) {
$data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
'commitID in (%Ld)',
@@ -364,6 +421,95 @@ final class DiffusionCommitQuery
$this->withRepositoryIDs($repository_ids);
}
+ if ($this->ancestorsOf !== null) {
+ if (count($this->repositoryIDs) !== 1) {
+ throw new PhabricatorSearchConstraintException(
+ pht(
+ 'To search for commits which are ancestors of particular refs, '.
+ 'you must constrain the search to exactly one repository.'));
+ }
+
+ $repository_id = head($this->repositoryIDs);
+ $history_limit = $this->getRawResultLimit() * 32;
+ $viewer = $this->getViewer();
+
+ $repository = id(new PhabricatorRepositoryQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($repository_id))
+ ->executeOne();
+
+ if (!$repository) {
+ throw new PhabricatorEmptyQueryException();
+ }
+
+ if ($repository->isSVN()) {
+ throw new PhabricatorSearchConstraintException(
+ pht(
+ 'Subversion does not support searching for ancestors of '.
+ 'a particular ref. This operation is not meaningful in '.
+ 'Subversion.'));
+ }
+
+ if ($repository->isHg()) {
+ throw new PhabricatorSearchConstraintException(
+ pht(
+ 'Mercurial does not currently support searching for ancestors of '.
+ 'a particular ref.'));
+ }
+
+ $can_constrain = true;
+ $history_identifiers = array();
+ foreach ($this->ancestorsOf as $key => $ref) {
+ try {
+ $raw_history = DiffusionQuery::callConduitWithDiffusionRequest(
+ $viewer,
+ DiffusionRequest::newFromDictionary(
+ array(
+ 'repository' => $repository,
+ 'user' => $viewer,
+ )),
+ 'diffusion.historyquery',
+ array(
+ 'commit' => $ref,
+ 'limit' => $history_limit,
+ ));
+ } catch (ConduitClientException $ex) {
+ throw new PhabricatorSearchConstraintException(
+ $ex->getMessage());
+ }
+
+ $ref_identifiers = array();
+ foreach ($raw_history['pathChanges'] as $change) {
+ $ref_identifiers[] = $change['commitIdentifier'];
+ }
+
+ // If this ref had fewer total commits than the limit, we're safe to
+ // apply the constraint as a large `IN (...)` query for a list of
+ // commit identifiers. This is efficient.
+ if ($history_limit) {
+ if (count($ref_identifiers) >= $history_limit) {
+ $can_constrain = false;
+ break;
+ }
+ }
+
+ $history_identifiers += array_fuse($ref_identifiers);
+ }
+
+ // If all refs had a small number of ancestors, we can just put the
+ // constraint into the query here and we're done. Otherwise, we need
+ // to filter each page after it comes out of the MySQL layer.
+ if ($can_constrain) {
+ $where[] = qsprintf(
+ $conn,
+ 'commit.commitIdentifier IN (%Ls)',
+ $history_identifiers);
+ } else {
+ $this->mustFilterRefs = true;
+ $this->refRepository = $repository;
+ }
+ }
+
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php
index f85c79621c..68f87a6f6b 100644
--- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php
+++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php
@@ -516,7 +516,7 @@ final class ManiphestTaskSearchEngine
);
if (ManiphestTaskPoints::getIsEnabled()) {
- $fields[] = id(new PhabricatorIntExportField())
+ $fields[] = id(new PhabricatorDoubleExportField())
->setKey('points')
->setLabel('Points');
}
diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php
index 8c48727a0b..d2eee5685e 100644
--- a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php
+++ b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php
@@ -162,13 +162,17 @@ EOTEXT
->setIsConduitOnly(true)
->setValue($object->getStatus())
->setOptions($object->getStatusNameMap()),
- id(new PhabricatorStringListEditField())
+ id(new PhabricatorCheckboxesEditField())
->setKey('ignored')
->setLabel(pht('Ignored Attributes'))
->setDescription(pht('Ignore paths with any of these attributes.'))
->setTransactionType(
PhabricatorOwnersPackageIgnoredTransaction::TRANSACTIONTYPE)
- ->setValue(array_keys($object->getIgnoredPathAttributes())),
+ ->setValue(array_keys($object->getIgnoredPathAttributes()))
+ ->setOptions(
+ array(
+ 'generated' => pht('Ignore generated files (review only).'),
+ )),
id(new PhabricatorConduitEditField())
->setKey('paths.set')
->setLabel(pht('Paths'))
diff --git a/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php b/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php
index 3badd04da7..c62ecd0494 100644
--- a/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php
+++ b/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php
@@ -64,9 +64,12 @@ final class PasteCreateConduitAPIMethod extends PasteConduitAPIMethod {
->setTransactionType(PhabricatorPasteTitleTransaction::TRANSACTIONTYPE)
->setNewValue($title);
- $xactions[] = id(new PhabricatorPasteTransaction())
- ->setTransactionType(PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE)
- ->setNewValue($language);
+ if (strlen($language)) {
+ $xactions[] = id(new PhabricatorPasteTransaction())
+ ->setTransactionType(
+ PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE)
+ ->setNewValue($language);
+ }
$editor = id(new PhabricatorPasteEditor())
->setActor($viewer)
diff --git a/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php b/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php
index a0c060d430..8927270c0d 100644
--- a/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php
+++ b/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php
@@ -38,4 +38,20 @@ final class PhabricatorPasteLanguageTransaction
$this->renderLanguageValue($this->getNewValue()));
}
+ public function validateTransactions($object, array $xactions) {
+ $errors = array();
+
+ foreach ($xactions as $xaction) {
+ $new = $xaction->getNewValue();
+
+ if ($new !== null && !strlen($new)) {
+ $errors[] = $this->newInvalidError(
+ pht('Paste language must be null or a nonempty string.'),
+ $xaction);
+ }
+ }
+
+ return $errors;
+ }
+
}
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index 0a09503ee9..8889040ddf 100644
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -364,7 +364,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
if (!strlen($name)) {
$name = $this->getName();
$name = phutil_utf8_strtolower($name);
- $name = preg_replace('@[/ -:<>]+@', '-', $name);
+ $name = preg_replace('@[ -/:->]+@', '-', $name);
$name = trim($name, '-');
if (!strlen($name)) {
$name = $this->getCallsign();
diff --git a/src/applications/transactions/editfield/PhabricatorCheckboxesEditField.php b/src/applications/transactions/editfield/PhabricatorCheckboxesEditField.php
new file mode 100644
index 0000000000..82d85c4425
--- /dev/null
+++ b/src/applications/transactions/editfield/PhabricatorCheckboxesEditField.php
@@ -0,0 +1,36 @@
+getOptions();
+
+ return id(new AphrontFormCheckboxControl())
+ ->setOptions($options);
+ }
+
+ protected function newConduitParameterType() {
+ return new ConduitStringListParameterType();
+ }
+
+ protected function newHTTPParameterType() {
+ return new AphrontStringListHTTPParameterType();
+ }
+
+ public function setOptions(array $options) {
+ $this->options = $options;
+ return $this;
+ }
+
+ public function getOptions() {
+ if ($this->options === null) {
+ throw new PhutilInvalidStateException('setOptions');
+ }
+
+ return $this->options;
+ }
+
+}
diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php
index 94c95a5052..07bf3589a8 100644
--- a/src/applications/transactions/editfield/PhabricatorEditField.php
+++ b/src/applications/transactions/editfield/PhabricatorEditField.php
@@ -302,6 +302,14 @@ abstract class PhabricatorEditField extends Phobject {
}
public function getPreviewPanel() {
+ if ($this->getIsHidden()) {
+ return null;
+ }
+
+ if ($this->getIsLocked()) {
+ return null;
+ }
+
return $this->previewPanel;
}
diff --git a/src/docs/user/userguide/owners.diviner b/src/docs/user/userguide/owners.diviner
index 35260b08af..95a3882552 100644
--- a/src/docs/user/userguide/owners.diviner
+++ b/src/docs/user/userguide/owners.diviner
@@ -132,6 +132,21 @@ behavior. If you want more powerful auditing behavior, you can use Herald to
write more sophisticated rules.
+Ignored Attributes
+==================
+
+You can automatically exclude certain types of files, like generated files,
+with **Ignored Attributes**.
+
+When a package is marked as ignoring files with a particular attribute, and
+a file in a particular change has that attribute, the file will be ignored when
+computing ownership.
+
+(This feature is currently rough, only works for Differential revisions, and
+may not always compute the correct set of owning packages in some complex
+cases where it interacts with dominion rules.)
+
+
Files in Multiple Packages
==========================
diff --git a/src/infrastructure/export/field/PhabricatorDoubleExportField.php b/src/infrastructure/export/field/PhabricatorDoubleExportField.php
new file mode 100644
index 0000000000..18087dd706
--- /dev/null
+++ b/src/infrastructure/export/field/PhabricatorDoubleExportField.php
@@ -0,0 +1,25 @@
+setDataType(PHPExcel_Cell_DataType::TYPE_NUMERIC);
+ }
+
+ public function getCharacterWidth() {
+ return 8;
+ }
+
+}
diff --git a/src/infrastructure/export/format/PhabricatorExcelExportFormat.php b/src/infrastructure/export/format/PhabricatorExcelExportFormat.php
index 2b0c787884..606df393d0 100644
--- a/src/infrastructure/export/format/PhabricatorExcelExportFormat.php
+++ b/src/infrastructure/export/format/PhabricatorExcelExportFormat.php
@@ -155,8 +155,12 @@ EOHELP
return $this->sheet;
}
+
+ /**
+ * @phutil-external-symbol class PHPExcel_Cell
+ */
private function getCellName($col, $row = null) {
- $col_name = chr(ord('A') + $col);
+ $col_name = PHPExcel_Cell::stringFromColumnIndex($col);
if ($row === null) {
return $col_name;
diff --git a/src/view/form/control/AphrontFormCheckboxControl.php b/src/view/form/control/AphrontFormCheckboxControl.php
index ed6cde3776..e030b7d17e 100644
--- a/src/view/form/control/AphrontFormCheckboxControl.php
+++ b/src/view/form/control/AphrontFormCheckboxControl.php
@@ -34,6 +34,20 @@ final class AphrontFormCheckboxControl extends AphrontFormControl {
return 'aphront-form-control-checkbox';
}
+ public function setOptions(array $options) {
+ $boxes = array();
+ foreach ($options as $key => $value) {
+ $boxes[] = array(
+ 'value' => $key,
+ 'label' => $value,
+ );
+ }
+
+ $this->boxes = $boxes;
+
+ return $this;
+ }
+
protected function renderInput() {
$rows = array();
foreach ($this->boxes as $box) {
@@ -41,14 +55,28 @@ final class AphrontFormCheckboxControl extends AphrontFormControl {
if ($id === null) {
$id = celerity_generate_unique_node_id();
}
+
+ $name = idx($box, 'name');
+ if ($name === null) {
+ $name = $this->getName().'[]';
+ }
+
+ $value = $box['value'];
+
+ if (array_key_exists('checked', $box)) {
+ $checked = $box['checked'];
+ } else {
+ $checked = in_array($value, $this->getValue());
+ }
+
$checkbox = phutil_tag(
'input',
array(
'id' => $id,
'type' => 'checkbox',
- 'name' => $box['name'],
+ 'name' => $name,
'value' => $box['value'],
- 'checked' => $box['checked'] ? 'checked' : null,
+ 'checked' => $checked ? 'checked' : null,
'disabled' => $this->getDisabled() ? 'disabled' : null,
));
$label = phutil_tag(
diff --git a/src/view/phui/PHUICrumbView.php b/src/view/phui/PHUICrumbView.php
index 68c43be4e3..1e5dad2ec6 100644
--- a/src/view/phui/PHUICrumbView.php
+++ b/src/view/phui/PHUICrumbView.php
@@ -8,6 +8,7 @@ final class PHUICrumbView extends AphrontView {
private $isLastCrumb;
private $workflow;
private $aural;
+ private $alwaysVisible;
public function setAural($aural) {
$this->aural = $aural;
@@ -18,6 +19,22 @@ final class PHUICrumbView extends AphrontView {
return $this->aural;
}
+ /**
+ * Make this crumb always visible, even on devices where it would normally
+ * be hidden.
+ *
+ * @param bool True to make the crumb always visible.
+ * @return this
+ */
+ public function setAlwaysVisible($always_visible) {
+ $this->alwaysVisible = $always_visible;
+ return $this;
+ }
+
+ public function getAlwaysVisible() {
+ return $this->alwaysVisible;
+ }
+
public function setWorkflow($workflow) {
$this->workflow = $workflow;
return $this;
@@ -98,6 +115,10 @@ final class PHUICrumbView extends AphrontView {
$classes[] = 'phabricator-last-crumb';
}
+ if ($this->getAlwaysVisible()) {
+ $classes[] = 'phui-crumb-always-visible';
+ }
+
$tag = javelin_tag(
$this->href ? 'a' : 'span',
array(
diff --git a/webroot/rsrc/css/phui/phui-crumbs-view.css b/webroot/rsrc/css/phui/phui-crumbs-view.css
index 6155e9b2b8..0811b36134 100644
--- a/webroot/rsrc/css/phui/phui-crumbs-view.css
+++ b/webroot/rsrc/css/phui/phui-crumbs-view.css
@@ -55,6 +55,8 @@
}
.device-phone .phui-crumb-view.phabricator-last-crumb .phui-crumb-name,
+.device-phone .phui-crumb-view.phui-crumb-always-visible .phui-crumb-name,
+.device-phone .phui-crumb-view.phui-crumb-always-visible + .phui-crumb-divider,
.device-phone .phui-crumb-view.phui-crumb-has-icon,
.device-phone .phui-crumb-has-icon + .phui-crumb-divider {
display: inline-block;
diff --git a/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js b/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js
index a60c91e528..5e293577aa 100644
--- a/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js
+++ b/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js
@@ -395,8 +395,12 @@ JX.install('Tokenizer', {
break;
case 'delete':
if (!this._focus.value.length) {
+ // In unusual cases, it's possible for us to end up with a token
+ // that has the empty string ("") as a value. Support removal of
+ // this unusual token.
+
var tok;
- while ((tok = this._tokens.pop())) {
+ while ((tok = this._tokens.pop()) !== null) {
if (this._remove(tok, true)) {
break;
}