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; }