From bfabe49c5ae88b62556238443e536c986a78d1f6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 19 Oct 2017 13:35:02 -0700 Subject: [PATCH 01/72] Start revisions in "Draft" if prototypes are enabled Summary: Ref T2543. This is a less ambitious version of the rule in D18628, which I backed off from, since I think this probably still has a fair number of loose ends to tie up. Test Plan: Created a revision locally. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18713 --- .../differential/storage/DifferentialRevision.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index e29db36b14..d14f69f563 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -67,13 +67,19 @@ final class DifferentialRevision extends DifferentialDAO $view_policy = $app->getPolicy( DifferentialDefaultViewCapability::CAPABILITY); + if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { + $initial_state = DifferentialRevisionStatus::DRAFT; + } else { + $initial_state = DifferentialRevisionStatus::NEEDS_REVIEW; + } + return id(new DifferentialRevision()) ->setViewPolicy($view_policy) ->setAuthorPHID($actor->getPHID()) ->attachRepository(null) ->attachActiveDiff(null) ->attachReviewers(array()) - ->setModernRevisionStatus(DifferentialRevisionStatus::NEEDS_REVIEW); + ->setModernRevisionStatus($initial_state); } protected function getConfiguration() { From 1755ec242989fabf1c27ab71f5364ce9be68a74a Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 19 Oct 2017 14:09:25 -0700 Subject: [PATCH 02/72] Show more detailed hints about draft revisions in the UI Summary: Ref T2543. When revisions are in the draft state, tell the user what we're waiting for or why they aren't moving forward. Test Plan: {F5228840} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18714 --- src/__phutil_library_map__.php | 2 + .../customfield/DifferentialDraftField.php | 90 +++++++++++++++++++ .../editor/DifferentialTransactionEditor.php | 48 +--------- .../storage/DifferentialRevision.php | 52 +++++++++++ 4 files changed, 145 insertions(+), 47 deletions(-) create mode 100644 src/applications/differential/customfield/DifferentialDraftField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a650e33fcf..93efcc65e1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -444,6 +444,7 @@ phutil_register_library_map(array( 'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php', 'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php', 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php', + 'DifferentialDraftField' => 'applications/differential/customfield/DifferentialDraftField.php', 'DifferentialExactUserFunctionDatasource' => 'applications/differential/typeahead/DifferentialExactUserFunctionDatasource.php', 'DifferentialFieldParseException' => 'applications/differential/exception/DifferentialFieldParseException.php', 'DifferentialFieldValidationException' => 'applications/differential/exception/DifferentialFieldValidationException.php', @@ -5451,6 +5452,7 @@ phutil_register_library_map(array( 'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher', + 'DifferentialDraftField' => 'DifferentialCoreCustomField', 'DifferentialExactUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DifferentialFieldParseException' => 'Exception', 'DifferentialFieldValidationException' => 'Exception', diff --git a/src/applications/differential/customfield/DifferentialDraftField.php b/src/applications/differential/customfield/DifferentialDraftField.php new file mode 100644 index 0000000000..e37d622a33 --- /dev/null +++ b/src/applications/differential/customfield/DifferentialDraftField.php @@ -0,0 +1,90 @@ +getViewer(); + $revision = $this->getObject(); + + if (!$revision->isDraft()) { + return array(); + } + + $warnings = array(); + + $blocking_map = array( + HarbormasterBuildStatus::STATUS_FAILED, + HarbormasterBuildStatus::STATUS_ABORTED, + HarbormasterBuildStatus::STATUS_ERROR, + HarbormasterBuildStatus::STATUS_PAUSED, + HarbormasterBuildStatus::STATUS_DEADLOCKED, + ); + $blocking_map = array_fuse($blocking_map); + + $builds = $revision->loadActiveBuilds($viewer); + + $waiting = array(); + $blocking = array(); + foreach ($builds as $build) { + if (isset($blocking_map[$build->getBuildStatus()])) { + $blocking[] = $build; + } else { + $waiting[] = $build; + } + } + + $blocking_list = $viewer->renderHandleList(mpull($blocking, 'getPHID')) + ->setAsInline(true); + $waiting_list = $viewer->renderHandleList(mpull($waiting, 'getPHID')) + ->setAsInline(true); + + if ($blocking) { + $warnings[] = pht( + 'This draft revision will not be submitted for review because %s '. + 'build(s) failed: %s.', + phutil_count($blocking), + $blocking_list); + $warnings[] = pht( + 'Fix build failures and update the revision.'); + } else if ($waiting) { + $warnings[] = pht( + 'This draft revision will be sent for review once %s '. + 'build(s) pass: %s.', + phutil_count($waiting), + $waiting_list); + } else { + $warnings[] = pht( + 'This is a draft revision that has not yet been submitted for '. + 'review.'); + } + + return $warnings; + } + +} diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 253d49c1ec..7c9bb01b07 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1570,58 +1570,12 @@ final class DifferentialTransactionEditor private function hasActiveBuilds($object) { $viewer = $this->requireActor(); - $diff = $object->getActiveDiff(); - $buildables = id(new HarbormasterBuildableQuery()) - ->setViewer($viewer) - ->withContainerPHIDs(array($object->getPHID())) - ->withBuildablePHIDs(array($diff->getPHID())) - ->withManualBuildables(false) - ->execute(); - if (!$buildables) { - return false; - } - - $builds = id(new HarbormasterBuildQuery()) - ->setViewer($viewer) - ->withBuildablePHIDs(mpull($buildables, 'getPHID')) - ->withBuildStatuses( - array( - HarbormasterBuildStatus::STATUS_INACTIVE, - HarbormasterBuildStatus::STATUS_PENDING, - HarbormasterBuildStatus::STATUS_BUILDING, - HarbormasterBuildStatus::STATUS_FAILED, - HarbormasterBuildStatus::STATUS_ABORTED, - HarbormasterBuildStatus::STATUS_ERROR, - HarbormasterBuildStatus::STATUS_PAUSED, - HarbormasterBuildStatus::STATUS_DEADLOCKED, - )) - ->needBuildTargets(true) - ->execute(); + $builds = $object->loadActiveBuilds($viewer); if (!$builds) { return false; } - $active = array(); - foreach ($builds as $key => $build) { - foreach ($build->getBuildTargets() as $target) { - if ($target->isAutotarget()) { - // Ignore autotargets when looking for active of failed builds. If - // local tests fail and you continue anyway, you don't need to - // double-confirm them. - continue; - } - - // This build has at least one real target that's doing something. - $active[$key] = $build; - break; - } - } - - if (!$active) { - return false; - } - return true; } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index d14f69f563..76c7c4da95 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -708,6 +708,58 @@ final class DifferentialRevision extends DifferentialDAO return false; } + public function loadActiveBuilds(PhabricatorUser $viewer) { + $diff = $this->getActiveDiff(); + + $buildables = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withContainerPHIDs(array($this->getPHID())) + ->withBuildablePHIDs(array($diff->getPHID())) + ->withManualBuildables(false) + ->execute(); + if (!$buildables) { + return array(); + } + + $builds = id(new HarbormasterBuildQuery()) + ->setViewer($viewer) + ->withBuildablePHIDs(mpull($buildables, 'getPHID')) + ->withBuildStatuses( + array( + HarbormasterBuildStatus::STATUS_INACTIVE, + HarbormasterBuildStatus::STATUS_PENDING, + HarbormasterBuildStatus::STATUS_BUILDING, + HarbormasterBuildStatus::STATUS_FAILED, + HarbormasterBuildStatus::STATUS_ABORTED, + HarbormasterBuildStatus::STATUS_ERROR, + HarbormasterBuildStatus::STATUS_PAUSED, + HarbormasterBuildStatus::STATUS_DEADLOCKED, + )) + ->needBuildTargets(true) + ->execute(); + if (!$builds) { + return array(); + } + + $active = array(); + foreach ($builds as $key => $build) { + foreach ($build->getBuildTargets() as $target) { + if ($target->isAutotarget()) { + // Ignore autotargets when looking for active of failed builds. If + // local tests fail and you continue anyway, you don't need to + // double-confirm them. + continue; + } + + // This build has at least one real target that's doing something. + $active[$key] = $build; + break; + } + } + + return $active; + } + /* -( HarbormasterBuildableInterface )------------------------------------- */ From 01b1854fb45c3fda896362bc565ba09300e59ccd Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 Oct 2017 07:40:11 -0700 Subject: [PATCH 03/72] Fix an issue with attempting to index comments on packages Summary: See rPcd14194a329788d5fff6365bcade278fd18f3612 for a similar change. Implement `getApplicationTransactionCommentObject()` to return `null` explicitly. Test Plan: Ran `bin/search index --type ownerspackage`, got indexing after change. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18715 --- .../owners/storage/PhabricatorOwnersPackageTransaction.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php index 66e15b634a..1dfc944f63 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php @@ -15,4 +15,8 @@ final class PhabricatorOwnersPackageTransaction return 'PhabricatorOwnersPackageTransactionType'; } + public function getApplicationTransactionCommentObject() { + return null; + } + } From cbcab60fbb95e8ba6e00860e71b7e703e71f3d71 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 Oct 2017 10:47:24 -0700 Subject: [PATCH 04/72] Remove obsolete instructions from information on prototype applications Summary: Most of this document is no longer relevant, since we're happy to work on prototypes if you're paying us and no longer have any meaningful free support. Test Plan: Read document. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18719 --- src/docs/user/userguide/prototypes.diviner | 33 ---------------------- 1 file changed, 33 deletions(-) diff --git a/src/docs/user/userguide/prototypes.diviner b/src/docs/user/userguide/prototypes.diviner index 7352502f99..c84bad1178 100644 --- a/src/docs/user/userguide/prototypes.diviner +++ b/src/docs/user/userguide/prototypes.diviner @@ -9,8 +9,6 @@ Overview Phabricator includes //prototype applications//, which are applications in an early stage of development. -IMPORTANT: The upstream does not offer support for these applications. - When we begin working on a new application, we usually implement it as a prototype first. This allows us to get a better sense of how the application might work and integrate with other applications, and what technical and product @@ -32,34 +30,3 @@ on hold indefinitely if we're less excited about it after we begin building it. If you're interested in previewing upcoming applications, you can use the `phabricator.show-prototypes` configuration setting to enable prototypes. - -Feedback on Prototypes -====================== - -We're usually interested in this sort of feedback on prototypes: - - - {icon check, color=green} **Use Cases**: If we're building something that - you think you'd use, we'd love to hear about your use cases for it. This can - help us figure out what features to add and how users may think about, use, - and integrate the application. - - {icon check, color=green} **General Interest**: Is an application something - you're looking forward to? Knowing which applications users are interested - in can help us set priorities. - -We're usually **not** interested in this sort of feedback on prototypes: - - - {icon times, color=red} **Support Requests**: We do not support these - applications. Use them at your own risk, or wait for them to leave the - prototype phase. - - {icon times, color=red} **Bug Reports**: We know these applications don't - work well yet, and usually know about most of the open bugs. Even if we - don't, whatever isn't working yet may change completely before the - application leaves the prototype phase. - - {icon times, color=red} **Contributions / Pull Requests**: These - applications are usually in too early a state to accept contributions. Let - us know about your use case, but wait for release to send code. - -Overall, using prototypes makes it easier for us to explore and develop -application ideas, and to share a preview of what's coming in the future with -users, but prototypes are not yet full applications and we do not provide -support until applications leave the prototype phase. From 65f13b156f5ecdbf689cce16f2bc7b9680818789 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 Oct 2017 10:15:07 -0700 Subject: [PATCH 05/72] Improve "refengine" performance for testing large numbers of Mercurial branches Summary: See PHI158. In the RefEngine, we test if any old branch positions have been removed from the repository. This is uncommon (but not impossible) in Mercurial, and corresponds to users deleting branches in Git. Currently, we end up running `hg log` for each position, in parallel. Because of Python's large startup overhead, this can be resource intensive for repositories with a large number of branches. We have to do this in the general case because the caller may be asking us to resolve `tip`, `newfeature`, `tip~3`, `9`, etc. However, in the specific case where the refs are 40-digit hashes, we can bulk resolve them if they exist, like this: ``` hg log ... --rev (abcd or def0 or ab12 or ...) ``` In the general case, we could probably do less of this than we currently do (instead of testing all old heads, we could prune the list by removing commits which we know are still pointed to by current heads) but that's a slightly more involved change and the effect here is already dramatic. Test Plan: Verified that CPU usage drops from ~110s -> ~0.9s: Before: ``` epriestley@orbital ~/dev/phabricator $ time ./bin/repository refs nss Updating refs in "nss"... Done. real 0m14.676s user 1m24.714s sys 0m21.645s ``` After: ``` epriestley@orbital ~/dev/phabricator $ time ./bin/repository refs nss Updating refs in "nss"... Done. real 0m0.861s user 0m0.882s sys 0m0.213s ``` - Manually resolved `blue`, `tip`, `9`, etc., got expected results. - Tried to resolve invalid hashes, got expected result (no resolution). Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18717 --- .../DiffusionLowLevelResolveRefsQuery.php | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php index b649ca65a8..f9e9f74774 100644 --- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php @@ -256,6 +256,66 @@ final class DiffusionLowLevelResolveRefsQuery return $results; } + // If some of the refs look like hashes, try to bulk resolve them. This + // workflow happens via RefEngine and bulk resolution is dramatically + // faster than individual resolution. See PHI158. + + $hashlike = array(); + foreach ($unresolved as $key => $ref) { + if (preg_match('/^[a-f0-9]{40}\z/', $ref)) { + $hashlike[$key] = $ref; + } + } + + if (count($hashlike) > 1) { + $hashlike_map = array(); + + $hashlike_groups = array_chunk($hashlike, 64, true); + foreach ($hashlike_groups as $hashlike_group) { + $hashlike_arg = array(); + foreach ($hashlike_group as $hashlike_ref) { + $hashlike_arg[] = hgsprintf('%s', $hashlike_ref); + } + $hashlike_arg = '('.implode(' or ', $hashlike_arg).')'; + + list($err, $refs) = $repository->execLocalCommand( + 'log --template=%s --rev %s', + '{node}\n', + $hashlike_arg); + if ($err) { + // NOTE: If any ref fails to resolve, Mercurial will exit with an + // error. We just give up on the whole group and resolve it + // individually below. In theory, we could split it into subgroups + // but the pathway where this bulk resolution matters rarely tries + // to resolve missing refs (see PHI158). + continue; + } + + $refs = phutil_split_lines($refs, false); + + foreach ($refs as $ref) { + $hashlike_map[$ref] = true; + } + } + + foreach ($unresolved as $key => $ref) { + if (!isset($hashlike_map[$ref])) { + continue; + } + + $results[$ref][] = array( + 'type' => 'commit', + 'identifier' => $ref, + ); + + unset($unresolved[$key]); + } + } + + if (!$unresolved) { + return $results; + } + // If we still have unresolved refs (which might be things like "tip"), // try to resolve them individually. From 672247eff30b65210e2f0f00b009f8c6c3f4b64f Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 Oct 2017 10:43:33 -0700 Subject: [PATCH 06/72] Add aural "+" and "-" hints to unified diffs for users who use screenreaders Summary: See PHI160 for discussion. Test Plan: With `?__aural__=1`, saw aural hints: {F5229986} Without, saw normal visual diff. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18718 --- .../DifferentialChangesetOneUpRenderer.php | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php index 7a694cfd98..586680d1b8 100644 --- a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php @@ -41,6 +41,20 @@ final class DifferentialChangesetOneUpRenderer $column_width = 4; + $aural_minus = javelin_tag( + 'span', + array( + 'aural' => true, + ), + '- '); + + $aural_plus = javelin_tag( + 'span', + array( + 'aural' => true, + ), + '+ '); + $out = array(); foreach ($primitives as $k => $p) { $type = $p['type']; @@ -55,8 +69,10 @@ final class DifferentialChangesetOneUpRenderer if ($is_old) { if ($p['htype']) { $class = 'left old'; + $aural = $aural_minus; } else { $class = 'left'; + $aural = null; } if ($type == 'old-file') { @@ -79,14 +95,20 @@ final class DifferentialChangesetOneUpRenderer ), $line); + $render = $p['render']; + if ($aural !== null) { + $render = array($aural, $render); + } + $cells[] = phutil_tag('th', array('class' => $class)); $cells[] = $no_copy; - $cells[] = phutil_tag('td', array('class' => $class), $p['render']); + $cells[] = phutil_tag('td', array('class' => $class), $render); $cells[] = $no_coverage; } else { if ($p['htype']) { $class = 'right new'; $cells[] = phutil_tag('th', array('class' => $class)); + $aural = $aural_plus; } else { $class = 'right'; if ($left_prefix) { @@ -98,6 +120,7 @@ final class DifferentialChangesetOneUpRenderer $oline = $p['oline']; $cells[] = phutil_tag('th', array('id' => $left_id), $oline); + $aural = null; } if ($type == 'new-file') { @@ -120,8 +143,13 @@ final class DifferentialChangesetOneUpRenderer ), $line); + $render = $p['render']; + if ($aural !== null) { + $render = array($aural, $render); + } + $cells[] = $no_copy; - $cells[] = phutil_tag('td', array('class' => $class), $p['render']); + $cells[] = phutil_tag('td', array('class' => $class), $render); $cells[] = $no_coverage; } From 63e6b2553e0ecdb0d10cde7c1845529a6d53e836 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 Oct 2017 11:20:02 -0700 Subject: [PATCH 07/72] Simply how Differential drafts ignore Harbormaster autobuilds Summary: Ref T2543. When a revision is created, we check if any builds are waiting/failed, and submit it for review immediately if we aren't waiting for anything. In doing this, we ignore builds with only autotargets, since these are client-side and failures from local `arc lint` / `arc unit` should not count (the user has already chosen to ignore/skip them). The way we do this has some issues: - Herald may have started builds, but they may still be PENDING and not have any targets yet. In this case, we'll see "no non-autotargets" and ignore the build, which is wrong. - We have to load targets but don't really care about them, which is more work than we really need to do. - And it's kind of complex, too. Instead, just let `BuildQuery` filter out "autobuilds" (builds generated from autoplans) with a JOIN. Test Plan: Ran `arc diff` with builds configured, got a clean "Draft" state instead of an incorrect promotion directly to "Needs Review". Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18721 --- .../storage/DifferentialRevision.php | 25 +-------- .../query/HarbormasterBuildQuery.php | 55 +++++++++++++++++-- 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 76c7c4da95..da7e18ce05 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -721,9 +721,10 @@ final class DifferentialRevision extends DifferentialDAO return array(); } - $builds = id(new HarbormasterBuildQuery()) + return id(new HarbormasterBuildQuery()) ->setViewer($viewer) ->withBuildablePHIDs(mpull($buildables, 'getPHID')) + ->withAutobuilds(false) ->withBuildStatuses( array( HarbormasterBuildStatus::STATUS_INACTIVE, @@ -735,29 +736,7 @@ final class DifferentialRevision extends DifferentialDAO HarbormasterBuildStatus::STATUS_PAUSED, HarbormasterBuildStatus::STATUS_DEADLOCKED, )) - ->needBuildTargets(true) ->execute(); - if (!$builds) { - return array(); - } - - $active = array(); - foreach ($builds as $key => $build) { - foreach ($build->getBuildTargets() as $target) { - if ($target->isAutotarget()) { - // Ignore autotargets when looking for active of failed builds. If - // local tests fail and you continue anyway, you don't need to - // double-confirm them. - continue; - } - - // This build has at least one real target that's doing something. - $active[$key] = $build; - break; - } - } - - return $active; } diff --git a/src/applications/harbormaster/query/HarbormasterBuildQuery.php b/src/applications/harbormaster/query/HarbormasterBuildQuery.php index cf6db6115b..770ca4413b 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildQuery.php @@ -10,6 +10,7 @@ final class HarbormasterBuildQuery private $buildPlanPHIDs; private $initiatorPHIDs; private $needBuildTargets; + private $autobuilds; public function withIDs(array $ids) { $this->ids = $ids; @@ -41,6 +42,11 @@ final class HarbormasterBuildQuery return $this; } + public function withAutobuilds($with_autobuilds) { + $this->autobuilds = $with_autobuilds; + return $this; + } + public function needBuildTargets($need_targets) { $this->needBuildTargets = $need_targets; return $this; @@ -141,50 +147,87 @@ final class HarbormasterBuildQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'b.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid in (%Ls)', + 'b.phid in (%Ls)', $this->phids); } if ($this->buildStatuses !== null) { $where[] = qsprintf( $conn, - 'buildStatus in (%Ls)', + 'b.buildStatus in (%Ls)', $this->buildStatuses); } if ($this->buildablePHIDs !== null) { $where[] = qsprintf( $conn, - 'buildablePHID IN (%Ls)', + 'b.buildablePHID IN (%Ls)', $this->buildablePHIDs); } if ($this->buildPlanPHIDs !== null) { $where[] = qsprintf( $conn, - 'buildPlanPHID IN (%Ls)', + 'b.buildPlanPHID IN (%Ls)', $this->buildPlanPHIDs); } if ($this->initiatorPHIDs !== null) { $where[] = qsprintf( $conn, - 'initiatorPHID IN (%Ls)', + 'b.initiatorPHID IN (%Ls)', $this->initiatorPHIDs); } + if ($this->autobuilds !== null) { + if ($this->autobuilds) { + $where[] = qsprintf( + $conn, + 'p.planAutoKey IS NOT NULL'); + } else { + $where[] = qsprintf( + $conn, + 'p.planAutoKey IS NULL'); + } + } + return $where; } + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + if ($this->shouldJoinPlanTable()) { + $joins[] = qsprintf( + $conn, + 'JOIN %T p ON b.buildPlanPHID = p.phid', + id(new HarbormasterBuildPlan())->getTableName()); + } + + return $joins; + } + + private function shouldJoinPlanTable() { + if ($this->autobuilds !== null) { + return true; + } + + return false; + } + public function getQueryApplicationClass() { return 'PhabricatorHarbormasterApplication'; } + protected function getPrimaryTableAlias() { + return 'b'; + } + } From 157f47cd149c1a811c12850bbf76cc05dd805ad9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 Oct 2017 15:37:19 -0700 Subject: [PATCH 08/72] Rewrite CommitQuery to use UNION for performance Summary: Ref T12680. See PHI167. See that task for discussion. Rewrite `DiffusionCommitQuery` to work more like `DifferentialRevisionQuery`, and use a UNION to find "all revisions you need to audit OR respond to". I tried to get this working a little more cleanly than RevisionQuery does, and can probably simplify that now. Test Plan: Poked at the UI locally without hitting any apparent issues, but my local data is pretty garbage at this point. I'll take a look at how the query plans work on `secure`. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12680 Differential Revision: https://secure.phabricator.com/D18722 --- .../diffusion/query/DiffusionCommitQuery.php | 83 +++++++++++++------ ...PhabricatorCursorPagedPolicyAwareQuery.php | 35 ++++++-- 2 files changed, 85 insertions(+), 33 deletions(-) diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index da8937910e..8996bab628 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -177,7 +177,63 @@ final class DiffusionCommitQuery } protected function loadPage() { - return $this->loadStandardPage($this->newResultObject()); + $table = $this->newResultObject(); + $conn = $table->establishConnection('r'); + + $subqueries = array(); + if ($this->responsiblePHIDs) { + $base_authors = $this->authorPHIDs; + $base_auditors = $this->auditorPHIDs; + + $responsible_phids = $this->responsiblePHIDs; + if ($base_authors) { + $all_authors = array_merge($base_authors, $responsible_phids); + } else { + $all_authors = $responsible_phids; + } + + if ($base_auditors) { + $all_auditors = array_merge($base_auditors, $responsible_phids); + } else { + $all_auditors = $responsible_phids; + } + + $this->authorPHIDs = $all_authors; + $this->auditorPHIDs = $base_auditors; + $subqueries[] = $this->buildStandardPageQuery( + $conn, + $table->getTableName()); + + $this->authorPHIDs = $base_authors; + $this->auditorPHIDs = $all_auditors; + $subqueries[] = $this->buildStandardPageQuery( + $conn, + $table->getTableName()); + } else { + $subqueries[] = $this->buildStandardPageQuery( + $conn, + $table->getTableName()); + } + + if (count($subqueries) > 1) { + foreach ($subqueries as $key => $subquery) { + $subqueries[$key] = '('.$subquery.')'; + } + + $query = qsprintf( + $conn, + '%Q %Q %Q', + implode(' UNION DISTINCT ', $subqueries), + $this->buildOrderClause($conn, true), + $this->buildLimitClause($conn)); + } else { + $query = head($subqueries); + } + + $rows = queryfx_all($conn, '%Q', $query); + $rows = $this->didLoadRawRows($rows); + + return $table->loadAllFromArray($rows); } protected function willFilterPage(array $commits) { @@ -487,18 +543,10 @@ final class DiffusionCommitQuery $this->auditorPHIDs); } - if ($this->responsiblePHIDs !== null) { - $where[] = qsprintf( - $conn, - '(audit.auditorPHID IN (%Ls) OR commit.authorPHID IN (%Ls))', - $this->responsiblePHIDs, - $this->responsiblePHIDs); - } - if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'commit.auditStatus IN (%Ls)', + 'commit.auditStatus IN (%Ld)', $this->statuses); } @@ -541,10 +589,6 @@ final class DiffusionCommitQuery return ($this->auditIDs || $this->auditorPHIDs); } - private function shouldJoinAudit() { - return (bool)$this->responsiblePHIDs; - } - private function shouldJoinOwners() { return (bool)$this->packagePHIDs; } @@ -560,13 +604,6 @@ final class DiffusionCommitQuery $audit_request->getTableName()); } - if ($this->shouldJoinAudit()) { - $join[] = qsprintf( - $conn, - 'LEFT JOIN %T audit ON commit.phid = audit.commitPHID', - $audit_request->getTableName()); - } - if ($this->shouldJoinOwners()) { $join[] = qsprintf( $conn, @@ -584,10 +621,6 @@ final class DiffusionCommitQuery return true; } - if ($this->shouldJoinAudit()) { - return true; - } - if ($this->shouldJoinOwners()) { return true; } diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 9bebfe6957..19d6ba07f4 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -111,7 +111,19 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery AphrontDatabaseConnection $conn, $table_name) { - $rows = queryfx_all( + $query = $this->buildStandardPageQuery($conn, $table_name); + + $rows = queryfx_all($conn, '%Q', $query); + $rows = $this->didLoadRawRows($rows); + + return $rows; + } + + protected function buildStandardPageQuery( + AphrontDatabaseConnection $conn, + $table_name) { + + return qsprintf( $conn, '%Q FROM %T %Q %Q %Q %Q %Q %Q %Q', $this->buildSelectClause($conn), @@ -123,10 +135,6 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $this->buildHavingClause($conn), $this->buildOrderClause($conn), $this->buildLimitClause($conn)); - - $rows = $this->didLoadRawRows($rows); - - return $rows; } protected function didLoadRawRows(array $rows) { @@ -1032,7 +1040,10 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery /** * @task order */ - final protected function buildOrderClause(AphrontDatabaseConnection $conn) { + final protected function buildOrderClause( + AphrontDatabaseConnection $conn, + $for_union = false) { + $orderable = $this->getOrderableColumns(); $vector = $this->getOrderVector(); @@ -1045,7 +1056,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $parts[] = $part; } - return $this->formatOrderClause($conn, $parts); + return $this->formatOrderClause($conn, $parts, $for_union); } @@ -1054,7 +1065,8 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery */ protected function formatOrderClause( AphrontDatabaseConnection $conn, - array $parts) { + array $parts, + $for_union = false) { $is_query_reversed = false; if ($this->getBeforeID()) { @@ -1075,6 +1087,13 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } $table = idx($part, 'table'); + + // When we're building an ORDER BY clause for a sequence of UNION + // statements, we can't refer to tables from the subqueries. + if ($for_union) { + $table = null; + } + $column = $part['column']; if ($table !== null) { From e3a48dde1d5b95df4eb0ea1763a6baa7576e418f Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 Oct 2017 10:03:56 -0700 Subject: [PATCH 09/72] Correct a method signature in DifferentialDraftField Summary: Ref T12190. See . (I have a followup to fix the root issue.) Test Plan: Loaded Differential with an eye on the error log in PHP7, no longer saw warnings. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12190 Differential Revision: https://secure.phabricator.com/D18723 --- .../differential/customfield/DifferentialDraftField.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/differential/customfield/DifferentialDraftField.php b/src/applications/differential/customfield/DifferentialDraftField.php index e37d622a33..e7ed2bedb2 100644 --- a/src/applications/differential/customfield/DifferentialDraftField.php +++ b/src/applications/differential/customfield/DifferentialDraftField.php @@ -24,7 +24,7 @@ final class DifferentialDraftField return true; } - public function renderPropertyViewValue() { + public function renderPropertyViewValue(array $handles) { return null; } From 683df399e7f4753dcea7ce03961e36956d6e790d Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 Oct 2017 14:59:32 -0700 Subject: [PATCH 10/72] Simplify UNION/ORDER query construction in DifferentialRevisionQuery Summary: Ref T12680. Use the slightly sleeker construction from D18722 in Differential. Test Plan: Viewed revision list, reordered by date modified. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12680 Differential Revision: https://secure.phabricator.com/D18727 --- .../query/DifferentialRevisionQuery.php | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index 8fd91cbd7e..727a4ca184 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -38,8 +38,6 @@ final class DifferentialRevisionQuery private $needDrafts; private $needFlags; - private $buildingGlobalOrder; - /* -( Query Configuration )------------------------------------------------ */ @@ -484,12 +482,11 @@ final class DifferentialRevisionQuery } if (count($selects) > 1) { - $this->buildingGlobalOrder = true; $query = qsprintf( $conn_r, '%Q %Q %Q', implode(' UNION DISTINCT ', $selects), - $this->buildOrderClause($conn_r), + $this->buildOrderClause($conn_r, true), $this->buildLimitClause($conn_r)); } else { $query = head($selects); @@ -513,7 +510,6 @@ final class DifferentialRevisionQuery $group_by = $this->buildGroupClause($conn_r); $having = $this->buildHavingClause($conn_r); - $this->buildingGlobalOrder = false; $order_by = $this->buildOrderClause($conn_r); $limit = $this->buildLimitClause($conn_r); @@ -758,17 +754,9 @@ final class DifferentialRevisionQuery } public function getOrderableColumns() { - $primary = ($this->buildingGlobalOrder ? null : 'r'); - return array( - 'id' => array( - 'table' => $primary, - 'column' => 'id', - 'type' => 'int', - 'unique' => true, - ), 'updated' => array( - 'table' => $primary, + 'table' => $this->getPrimaryTableAlias(), 'column' => 'dateModified', 'type' => 'int', ), From 1d213dc1fa4b45280196457c61a1c36c396a602c Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 Oct 2017 15:04:06 -0700 Subject: [PATCH 11/72] Clean up virtual "_ft_rank" column for query construction of Ferret objects Summary: Ref T12974. Ferret object queries SELECT a virtual "_ft_rank" column for relevance ordering. Currently, they always SELECT this column. That's fine and doesn't hurt anything, but makes developing and debugging things kind of a pain since every query has this `, blah blah _ft_rank` junk. Instead, construct this column only if we're actually going to use it. Mostly, this cleans up DarkConsole / query logs a bit. Test Plan: Viewed normal query results on various pages, viewed global search results, ordered Maniphest tasks by normal stuff and by "Relevance". Viewed DarkConsole, saw no more "_ft_rank" junk on normal pages. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12974 Differential Revision: https://secure.phabricator.com/D18728 --- .../policy/PhabricatorCursorPagedPolicyAwareQuery.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 19d6ba07f4..63daa6df79 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -1565,6 +1565,13 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery return $select; } + $vector = $this->getOrderVector(); + if (!$vector->containsKey('rank')) { + // We only need to SELECT the virtual "_ft_rank" column if we're + // actually sorting the results by rank. + return $select; + } + if (!$this->ferretEngine) { $select[] = '0 _ft_rank'; return $select; From 28a24c333fdd4a8def13e624552eeaa3e37f7e08 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 24 Oct 2017 05:57:15 -0700 Subject: [PATCH 12/72] Fix a couple of other missing getApplicationTransactionCommentObject() implementations Summary: See PHI165. See D18715. These objects (projects, blogs) also need implementations now. (I thought about making this method `abstract` or doing try/catch to maybe make this more robust, but I think this should be the end of it, and those changes have mild complexity/compatibility/risk issues.) Test Plan: Changed `bin/search index` to index only one document of each type, ran `bin/search index --all --force`, saw no more comment-related errors. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18729 --- src/applications/phame/storage/PhameBlogTransaction.php | 4 ++++ .../project/storage/PhabricatorProjectTransaction.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/applications/phame/storage/PhameBlogTransaction.php b/src/applications/phame/storage/PhameBlogTransaction.php index c605510d7d..d3d6a79d0a 100644 --- a/src/applications/phame/storage/PhameBlogTransaction.php +++ b/src/applications/phame/storage/PhameBlogTransaction.php @@ -15,6 +15,10 @@ final class PhameBlogTransaction return PhabricatorPhameBlogPHIDType::TYPECONST; } + public function getApplicationTransactionCommentObject() { + return null; + } + public function getBaseTransactionClass() { return 'PhameBlogTransactionType'; } diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index 158c2480c0..a8b2bb0d4a 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -19,6 +19,10 @@ final class PhabricatorProjectTransaction return PhabricatorProjectProjectPHIDType::TYPECONST; } + public function getApplicationTransactionCommentObject() { + return null; + } + public function getBaseTransactionClass() { return 'PhabricatorProjectTransactionType'; } From beaf0ad9a6360154d5c3f79e2e7eb62b553dcf5b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 24 Oct 2017 09:10:48 -0700 Subject: [PATCH 13/72] Attribute revision promotion from "Draft" to "Needs Review" to the author Summary: Ref T2543. When Harbormaster finishes builds and promotes a draft revision to review, we currently publish "Harbormaster requested review of...". Instead, attribute this action to the author, since that's more natural and more useful. Test Plan: Promoted a diff locally, saw it attributed to me rather than Harbormaster. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18730 --- .../differential/editor/DifferentialTransactionEditor.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 7c9bb01b07..5e013eff43 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1537,7 +1537,13 @@ final class DifferentialTransactionEditor if ($object->isDraft() && $auto_undraft) { $active_builds = $this->hasActiveBuilds($object); if (!$active_builds) { + // When Harbormaster moves a revision out of the draft state, we + // attribute the action to the revision author since this is more + // natural and more useful. + $author_phid = $object->getAuthorPHID(); + $xaction = $object->getApplicationTransactionTemplate() + ->setAuthorPHID($author_phid) ->setTransactionType( DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE) ->setOldValue(false) From f1204c8c45683d86afa5624a6ef7f88f88f8a6c7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 Oct 2017 13:38:12 -0700 Subject: [PATCH 14/72] Convert Ponder Questions to Ferret engine Summary: See PHI177. Ref T12974. PonderQuestion was overlooked during the Ferret engine conversions. Test Plan: Ran migrations, searched for questions, got results: {F5241185} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12974 Differential Revision: https://secure.phabricator.com/D18736 --- .../20171026.ferret.01.ponder.doc.sql | 9 +++++++++ .../20171026.ferret.02.ponder.field.sql | 8 ++++++++ .../20171026.ferret.03.ponder.ngrams.sql | 5 +++++ .../20171026.ferret.04.ponder.cngrams.sql | 7 +++++++ .../20171026.ferret.05.ponder.index.php | 11 +++++++++++ src/__phutil_library_map__.php | 3 +++ .../search/PonderQuestionFerretEngine.php | 18 ++++++++++++++++++ .../ponder/storage/PonderQuestion.php | 12 +++++++++++- 8 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20171026.ferret.01.ponder.doc.sql create mode 100644 resources/sql/autopatches/20171026.ferret.02.ponder.field.sql create mode 100644 resources/sql/autopatches/20171026.ferret.03.ponder.ngrams.sql create mode 100644 resources/sql/autopatches/20171026.ferret.04.ponder.cngrams.sql create mode 100644 resources/sql/autopatches/20171026.ferret.05.ponder.index.php create mode 100644 src/applications/ponder/search/PonderQuestionFerretEngine.php diff --git a/resources/sql/autopatches/20171026.ferret.01.ponder.doc.sql b/resources/sql/autopatches/20171026.ferret.01.ponder.doc.sql new file mode 100644 index 0000000000..38c86a4134 --- /dev/null +++ b/resources/sql/autopatches/20171026.ferret.01.ponder.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_ponder.ponder_question_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171026.ferret.02.ponder.field.sql b/resources/sql/autopatches/20171026.ferret.02.ponder.field.sql new file mode 100644 index 0000000000..871f0d8f5b --- /dev/null +++ b/resources/sql/autopatches/20171026.ferret.02.ponder.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_ponder.ponder_question_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171026.ferret.03.ponder.ngrams.sql b/resources/sql/autopatches/20171026.ferret.03.ponder.ngrams.sql new file mode 100644 index 0000000000..3d2a3024b8 --- /dev/null +++ b/resources/sql/autopatches/20171026.ferret.03.ponder.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_ponder.ponder_question_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171026.ferret.04.ponder.cngrams.sql b/resources/sql/autopatches/20171026.ferret.04.ponder.cngrams.sql new file mode 100644 index 0000000000..49b66e0d39 --- /dev/null +++ b/resources/sql/autopatches/20171026.ferret.04.ponder.cngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_ponder.ponder_question_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171026.ferret.05.ponder.index.php b/resources/sql/autopatches/20171026.ferret.05.ponder.index.php new file mode 100644 index 0000000000..20489846d2 --- /dev/null +++ b/resources/sql/autopatches/20171026.ferret.05.ponder.index.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 93efcc65e1..91d5a3cc7b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4774,6 +4774,7 @@ phutil_register_library_map(array( 'PonderQuestionEditController' => 'applications/ponder/controller/PonderQuestionEditController.php', 'PonderQuestionEditEngine' => 'applications/ponder/editor/PonderQuestionEditEngine.php', 'PonderQuestionEditor' => 'applications/ponder/editor/PonderQuestionEditor.php', + 'PonderQuestionFerretEngine' => 'applications/ponder/search/PonderQuestionFerretEngine.php', 'PonderQuestionFulltextEngine' => 'applications/ponder/search/PonderQuestionFulltextEngine.php', 'PonderQuestionHistoryController' => 'applications/ponder/controller/PonderQuestionHistoryController.php', 'PonderQuestionListController' => 'applications/ponder/controller/PonderQuestionListController.php', @@ -10539,6 +10540,7 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PonderQuestionAnswerTransaction' => 'PonderQuestionTransactionType', 'PonderQuestionAnswerWikiTransaction' => 'PonderQuestionTransactionType', @@ -10548,6 +10550,7 @@ phutil_register_library_map(array( 'PonderQuestionEditController' => 'PonderController', 'PonderQuestionEditEngine' => 'PhabricatorEditEngine', 'PonderQuestionEditor' => 'PonderEditor', + 'PonderQuestionFerretEngine' => 'PhabricatorFerretEngine', 'PonderQuestionFulltextEngine' => 'PhabricatorFulltextEngine', 'PonderQuestionHistoryController' => 'PonderController', 'PonderQuestionListController' => 'PonderController', diff --git a/src/applications/ponder/search/PonderQuestionFerretEngine.php b/src/applications/ponder/search/PonderQuestionFerretEngine.php new file mode 100644 index 0000000000..72345f60c0 --- /dev/null +++ b/src/applications/ponder/search/PonderQuestionFerretEngine.php @@ -0,0 +1,18 @@ + Date: Thu, 26 Oct 2017 13:00:09 -0700 Subject: [PATCH 15/72] Add a missing `artifactIndex` key to HarbormasterBuildArtifact Summary: See PHI176. We issue a query with only `artifactIndex` from `BuildTarget`, but don't have an applicable key. Test Plan: This isn't on the normal Harbormaster execution path so I'm not 100% sure I have a local repro, but will confirm with customer. Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Differential Revision: https://secure.phabricator.com/D18732 --- .../harbormaster/storage/build/HarbormasterBuildArtifact.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php index 6825bf6646..461ef4b06f 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php @@ -43,6 +43,9 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO 'key_target' => array( 'columns' => array('buildTargetPHID', 'artifactType'), ), + 'key_index' => array( + 'columns' => array('artifactIndex'), + ), ), ) + parent::getConfiguration(); } From fbfed82efddee9732909036f358bc48c272136e4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 Oct 2017 13:05:31 -0700 Subject: [PATCH 16/72] Add a missing DaemonLogEvent key for garbage collection Summary: See PHI176. This is issued periodically by the garbage collector. Normally this table is relatively small-ish so this missing key isn't hugely noticeable. Test Plan: Ran `./bin/garbage collect --collector daemon.processes --trace` to get the query the GC runs. Ran ##DELETE FROM `daemon_logevent` WHERE epoch < 1508443504 LIMIT 100## before and after the key, saw a much better query plan afterward: Before: ``` mysql> explain DELETE FROM `daemon_logevent` WHERE epoch < 1508443504 LIMIT 100; +----+-------------+-----------------+------+---------------+------+---------+------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------------+------+---------------+------+---------+------+-------+-------------+ | 1 | SIMPLE | daemon_logevent | ALL | NULL | NULL | NULL | NULL | 19325 | Using where | +----+-------------+-----------------+------+---------------+------+---------+------+-------+-------------+ 1 row in set (0.00 sec) ``` After: ``` mysql> explain DELETE FROM `daemon_logevent` WHERE epoch < 1508443504 LIMIT 100; +----+-------------+-----------------+-------+---------------+-----------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------------+-------+---------------+-----------+---------+-------+------+-------------+ | 1 | SIMPLE | daemon_logevent | range | key_epoch | key_epoch | 4 | const | 1 | Using where | +----+-------------+-----------------+-------+---------------+-----------+---------+-------+------+-------------+ 1 row in set (0.00 sec) ``` Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18733 --- src/applications/daemon/storage/PhabricatorDaemonLogEvent.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/daemon/storage/PhabricatorDaemonLogEvent.php b/src/applications/daemon/storage/PhabricatorDaemonLogEvent.php index afdbd1f621..c5d2cbd1d4 100644 --- a/src/applications/daemon/storage/PhabricatorDaemonLogEvent.php +++ b/src/applications/daemon/storage/PhabricatorDaemonLogEvent.php @@ -18,6 +18,9 @@ final class PhabricatorDaemonLogEvent extends PhabricatorDaemonDAO { 'logID' => array( 'columns' => array('logID', 'epoch'), ), + 'key_epoch' => array( + 'columns' => array('epoch'), + ), ), ) + parent::getConfiguration(); } From b04d9fdc0ba875d1fd0ff61152a712e163135e18 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 Oct 2017 13:11:51 -0700 Subject: [PATCH 17/72] Add a missing key to PhabricatorFile for destroying Files Summary: See PHI176. Depends on D18733. We issue a query when deleting files that currently doesn't hit any keys. Test Plan: Ran `./bin/remove destroy --force --trace F56376` to get the query. Ran ##SELECT * FROM `file` WHERE storageEngine = 'blob' AND storageHandle = '23366' LIMIT 1## before and after the change. Before: ``` mysql> explain SELECT * FROM `file` WHERE storageEngine = 'blob' AND storageHandle = '23366' LIMIT 1; +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ | 1 | SIMPLE | file | ALL | NULL | NULL | NULL | NULL | 33866 | Using where | +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 1 row in set (0.01 sec) ``` After: ``` mysql> explain SELECT * FROM `file` WHERE storageEngine = 'blob' AND storageHandle = '23366' LIMIT 1; +----+-------------+-------+------+---------------+------------+---------+-------------+------+------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------------+---------+-------------+------+------------------------------------+ | 1 | SIMPLE | file | ref | key_engine | key_engine | 388 | const,const | 190 | Using index condition; Using where | +----+-------------+-------+------+---------------+------------+---------+-------------+------+------------------------------------+ 1 row in set (0.00 sec) ``` Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18734 --- src/applications/files/storage/PhabricatorFile.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 9636b5b017..c87623c68c 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -134,6 +134,9 @@ final class PhabricatorFile extends PhabricatorFileDAO 'columns' => array('builtinKey'), 'unique' => true, ), + 'key_engine' => array( + 'columns' => array('storageEngine', 'storageHandle(64)'), + ), ), ) + parent::getConfiguration(); } From d1b4c9f50b99b08fd77fbf6554834b61839813eb Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 Oct 2017 13:17:33 -0700 Subject: [PATCH 18/72] Add a missing key on DrydockLease Summary: Depends on D18734. See PHI176. We run this query on the main Drydock lease web UI, among other places. There is currently no `status` key which can satisfy it. Test Plan: Viewed Drydock lease page to get the query. Ran ##explain SELECT * FROM `drydock_lease` WHERE (status IN ('pending', 'acquired', 'active')) ORDER BY `id` DESC LIMIT 101;## before and after the change. I don't have a ton of leases locally so the un-key'd EXPLAIN isn't //that// bad, but still shows that we're getting a better key. Before: ``` mysql> explain SELECT * FROM `drydock_lease` WHERE (status IN ('pending', 'acquired', 'active')) ORDER BY `id` DESC LIMIT 101; +----+-------------+---------------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | drydock_lease | index | NULL | PRIMARY | 4 | NULL | 101 | Using where | +----+-------------+---------------+-------+---------------+---------+---------+------+------+-------------+ 1 row in set (0.00 sec) ``` After: ``` mysql> explain SELECT * FROM `drydock_lease` WHERE (status IN ('pending', 'acquired', 'active')) ORDER BY `id` DESC LIMIT 101; +----+-------------+---------------+-------+---------------+------------+---------+------+------+---------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------------+-------+---------------+------------+---------+------+------+---------------------------------------+ | 1 | SIMPLE | drydock_lease | range | key_status | key_status | 130 | NULL | 5 | Using index condition; Using filesort | +----+-------------+---------------+-------+---------------+------------+---------+------+------+---------------------------------------+ 1 row in set (0.00 sec) ``` Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Differential Revision: https://secure.phabricator.com/D18735 --- src/applications/drydock/storage/DrydockLease.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 0dd4e36b48..e60529afe4 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -83,6 +83,9 @@ final class DrydockLease extends DrydockDAO 'key_resource' => array( 'columns' => array('resourcePHID', 'status'), ), + 'key_status' => array( + 'columns' => array('status'), + ), ), ) + parent::getConfiguration(); } From f7f3dd5b20848c221e16bc34b3ce61ddbdbdde94 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 24 Oct 2017 14:04:06 -0700 Subject: [PATCH 19/72] Don't run Herald build and mail rules when they don't make sense Summary: Ref T2543. Fixes T10109. Currently, Herald only runs in Differential when a change updates the diff. This is partly for historical reasons, and partly because we don't want to restart builds every time someone makes a comment. However, this behavior is inconsistent with other applications (which always trigger on any change), and occasionally confusing to users (in T10109, for example) or otherwise undesirable. A similar issue is that T2543 has introduced a "Draft" state, where revisions don't send normal mail until builds finish. This interacts poorly with "Send me an email" rules (which shouldn't do anything here) and particularly with "Send me an email + only run these actions the first time the rule matches", since that might have an effect like "do nothing when the revision is created, then never anything again since you already did nothing once". To navigate both of these issues, let objects tell Herald that certain actions (like mail or builds) are currently forbidden. If a rule uses a field or action which is currently forbidden, the whole rule automatically fails before it executes, but doesn't count toward "only the first time" as far as Herald's tracking of rule execution is concerned. Then, forbid mail for draft revisions, and forbid builds for revisions which didn't just get updated. Forbidding mail fixes the issues with "Send me an email" that were created by the introduction of the draft state. Finally, make Herald run on every revision update, not just substantive updates to the diff. This resolves T10109. Test Plan: Created revisions via the draft -> submit workflow, saw different transcripts. Here's a mail action being forbidden for a draft: {F5237324} Here's a build action being forbidden for a "mundane" update: {F5237326} Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T10109, T2543 Differential Revision: https://secure.phabricator.com/D18731 --- src/__phutil_library_map__.php | 10 + .../editor/DifferentialTransactionEditor.php | 48 +++-- .../herald/DifferentialHeraldStateReasons.php | 22 ++ .../HarbormasterRunBuildPlansHeraldAction.php | 6 + .../herald/action/HeraldAction.php | 12 ++ .../herald/adapter/HeraldAdapter.php | 35 +++ .../controller/HeraldTranscriptController.php | 20 +- .../herald/engine/HeraldEngine.php | 204 ++++++++++++++---- src/applications/herald/field/HeraldField.php | 4 + .../herald/state/HeraldBuildableState.php | 7 + .../herald/state/HeraldMailableState.php | 7 + src/applications/herald/state/HeraldState.php | 3 + .../herald/state/HeraldStateReasons.php | 26 +++ .../transcript/HeraldConditionTranscript.php | 7 + .../transcript/HeraldRuleTranscript.php | 6 + .../PhabricatorMetaMTAEmailHeraldAction.php | 6 + 16 files changed, 359 insertions(+), 64 deletions(-) create mode 100644 src/applications/differential/herald/DifferentialHeraldStateReasons.php create mode 100644 src/applications/herald/state/HeraldBuildableState.php create mode 100644 src/applications/herald/state/HeraldMailableState.php create mode 100644 src/applications/herald/state/HeraldState.php create mode 100644 src/applications/herald/state/HeraldStateReasons.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 91d5a3cc7b..2040568496 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -458,6 +458,7 @@ phutil_register_library_map(array( 'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php', 'DifferentialGitSVNIDCommitMessageField' => 'applications/differential/field/DifferentialGitSVNIDCommitMessageField.php', 'DifferentialHarbormasterField' => 'applications/differential/customfield/DifferentialHarbormasterField.php', + 'DifferentialHeraldStateReasons' => 'applications/differential/herald/DifferentialHeraldStateReasons.php', 'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php', 'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php', 'DifferentialHovercardEngineExtension' => 'applications/differential/engineextension/DifferentialHovercardEngineExtension.php', @@ -1328,6 +1329,7 @@ phutil_register_library_map(array( 'HeraldApplicationActionGroup' => 'applications/herald/action/HeraldApplicationActionGroup.php', 'HeraldApplyTranscript' => 'applications/herald/storage/transcript/HeraldApplyTranscript.php', 'HeraldBasicFieldGroup' => 'applications/herald/field/HeraldBasicFieldGroup.php', + 'HeraldBuildableState' => 'applications/herald/state/HeraldBuildableState.php', 'HeraldCommitAdapter' => 'applications/diffusion/herald/HeraldCommitAdapter.php', 'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php', 'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php', @@ -1351,6 +1353,7 @@ phutil_register_library_map(array( 'HeraldGroup' => 'applications/herald/group/HeraldGroup.php', 'HeraldInvalidActionException' => 'applications/herald/engine/exception/HeraldInvalidActionException.php', 'HeraldInvalidConditionException' => 'applications/herald/engine/exception/HeraldInvalidConditionException.php', + 'HeraldMailableState' => 'applications/herald/state/HeraldMailableState.php', 'HeraldManageGlobalRulesCapability' => 'applications/herald/capability/HeraldManageGlobalRulesCapability.php', 'HeraldManiphestTaskAdapter' => 'applications/maniphest/herald/HeraldManiphestTaskAdapter.php', 'HeraldNewController' => 'applications/herald/controller/HeraldNewController.php', @@ -1388,6 +1391,8 @@ phutil_register_library_map(array( 'HeraldSchemaSpec' => 'applications/herald/storage/HeraldSchemaSpec.php', 'HeraldSelectFieldValue' => 'applications/herald/value/HeraldSelectFieldValue.php', 'HeraldSpaceField' => 'applications/spaces/herald/HeraldSpaceField.php', + 'HeraldState' => 'applications/herald/state/HeraldState.php', + 'HeraldStateReasons' => 'applications/herald/state/HeraldStateReasons.php', 'HeraldSubscribersField' => 'applications/subscriptions/herald/HeraldSubscribersField.php', 'HeraldSupportActionGroup' => 'applications/herald/action/HeraldSupportActionGroup.php', 'HeraldSupportFieldGroup' => 'applications/herald/field/HeraldSupportFieldGroup.php', @@ -5467,6 +5472,7 @@ phutil_register_library_map(array( 'DifferentialGetWorkingCopy' => 'Phobject', 'DifferentialGitSVNIDCommitMessageField' => 'DifferentialCommitMessageField', 'DifferentialHarbormasterField' => 'DifferentialCustomField', + 'DifferentialHeraldStateReasons' => 'HeraldStateReasons', 'DifferentialHiddenComment' => 'DifferentialDAO', 'DifferentialHostField' => 'DifferentialCustomField', 'DifferentialHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', @@ -6458,6 +6464,7 @@ phutil_register_library_map(array( 'HeraldApplicationActionGroup' => 'HeraldActionGroup', 'HeraldApplyTranscript' => 'Phobject', 'HeraldBasicFieldGroup' => 'HeraldFieldGroup', + 'HeraldBuildableState' => 'HeraldState', 'HeraldCommitAdapter' => array( 'HeraldAdapter', 'HarbormasterBuildableAdapterInterface', @@ -6487,6 +6494,7 @@ phutil_register_library_map(array( 'HeraldGroup' => 'Phobject', 'HeraldInvalidActionException' => 'Exception', 'HeraldInvalidConditionException' => 'Exception', + 'HeraldMailableState' => 'HeraldState', 'HeraldManageGlobalRulesCapability' => 'PhabricatorPolicyCapability', 'HeraldManiphestTaskAdapter' => 'HeraldAdapter', 'HeraldNewController' => 'HeraldController', @@ -6531,6 +6539,8 @@ phutil_register_library_map(array( 'HeraldSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'HeraldSelectFieldValue' => 'HeraldFieldValue', 'HeraldSpaceField' => 'HeraldField', + 'HeraldState' => 'Phobject', + 'HeraldStateReasons' => 'Phobject', 'HeraldSubscribersField' => 'HeraldField', 'HeraldSupportActionGroup' => 'HeraldActionGroup', 'HeraldSupportFieldGroup' => 'HeraldFieldGroup', diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 5e013eff43..9ab30c3f06 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1003,26 +1003,7 @@ final class DifferentialTransactionEditor protected function shouldApplyHeraldRules( PhabricatorLiskDAO $object, array $xactions) { - - if ($this->getIsNewObject()) { - return true; - } - - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case DifferentialTransaction::TYPE_UPDATE: - if (!$this->getIsCloseByCommit()) { - return true; - } - break; - case DifferentialRevisionCommandeerTransaction::TRANSACTIONTYPE: - // When users commandeer revisions, we may need to trigger - // signatures or author-based rules. - return true; - } - } - - return parent::shouldApplyHeraldRules($object, $xactions); + return true; } protected function didApplyHeraldRules( @@ -1211,6 +1192,33 @@ final class DifferentialTransactionEditor $revision, $revision->getActiveDiff()); + // If the object is still a draft, prevent "Send me an email" and other + // similar rules from acting yet. + if (!$object->shouldBroadcast()) { + $adapter->setForbiddenAction( + HeraldMailableState::STATECONST, + DifferentialHeraldStateReasons::REASON_DRAFT); + } + + // If this edit didn't actually change the diff (for example, a user + // edited the title or changed subscribers), prevent "Run build plan" + // and other similar rules from acting yet, since the build results will + // not (or, at least, should not) change unless the actual source changes. + $has_update = false; + $type_update = DifferentialTransaction::TYPE_UPDATE; + foreach ($xactions as $xaction) { + if ($xaction->getTransactionType() == $type_update) { + $has_update = true; + break; + } + } + + if (!$has_update) { + $adapter->setForbiddenAction( + HeraldBuildableState::STATECONST, + DifferentialHeraldStateReasons::REASON_UNCHANGED); + } + return $adapter; } diff --git a/src/applications/differential/herald/DifferentialHeraldStateReasons.php b/src/applications/differential/herald/DifferentialHeraldStateReasons.php new file mode 100644 index 0000000000..d3259560d5 --- /dev/null +++ b/src/applications/differential/herald/DifferentialHeraldStateReasons.php @@ -0,0 +1,22 @@ + pht( + 'This revision is still an unsubmitted draft, so mail will not '. + 'be sent yet.'), + self::REASON_UNCHANGED => pht( + 'The update which triggered Herald did not update the diff for '. + 'this revision, so builds will not run.'), + ); + + return idx($reasons, $reason); + } + +} diff --git a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php index 76bdf9ccc3..8c718e5f5d 100644 --- a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php +++ b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php @@ -7,6 +7,12 @@ final class HarbormasterRunBuildPlansHeraldAction const ACTIONCONST = 'harbormaster.build'; + public function getRequiredAdapterStates() { + return array( + HeraldBuildableState::STATECONST, + ); + } + public function getActionGroupKey() { return HeraldSupportActionGroup::ACTIONGROUPKEY; } diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php index 091f3e9bc2..a2d589f9f2 100644 --- a/src/applications/herald/action/HeraldAction.php +++ b/src/applications/herald/action/HeraldAction.php @@ -17,6 +17,7 @@ abstract class HeraldAction extends Phobject { const DO_STANDARD_PERMISSION = 'do.standard.permission'; const DO_STANDARD_INVALID_ACTION = 'do.standard.invalid-action'; const DO_STANDARD_WRONG_RULE_TYPE = 'do.standard.wrong-rule-type'; + const DO_STANDARD_FORBIDDEN = 'do.standard.forbidden'; abstract public function getHeraldActionName(); abstract public function supportsObject($object); @@ -25,6 +26,10 @@ abstract class HeraldAction extends Phobject { abstract public function renderActionDescription($value); + public function getRequiredAdapterStates() { + return array(); + } + protected function renderActionEffectDescription($type, $data) { return null; } @@ -336,6 +341,11 @@ abstract class HeraldAction extends Phobject { 'color' => 'red', 'name' => pht('Wrong Rule Type'), ), + self::DO_STANDARD_FORBIDDEN => array( + 'icon' => 'fa-ban', + 'color' => 'violet', + 'name' => pht('Forbidden'), + ), ); } @@ -381,6 +391,8 @@ abstract class HeraldAction extends Phobject { return pht( 'This action does not support rules of type "%s".', $data); + case self::DO_STANDARD_FORBIDDEN: + return HeraldStateReasons::getExplanation($data); } return null; diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 78ce862945..1c50c0b5ee 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -37,6 +37,7 @@ abstract class HeraldAdapter extends Phobject { private $fieldMap; private $actionMap; private $edgeCache = array(); + private $forbiddenActions = array(); public function getEmailPHIDs() { return array_values($this->emailPHIDs); @@ -1116,4 +1117,38 @@ abstract class HeraldAdapter extends Phobject { return $this->edgeCache[$type]; } + +/* -( Forbidden Actions )-------------------------------------------------- */ + + + final public function getForbiddenActions() { + return array_keys($this->forbiddenActions); + } + + final public function setForbiddenAction($action, $reason) { + $this->forbiddenActions[$action] = $reason; + return $this; + } + + final public function getRequiredFieldStates($field_key) { + return $this->requireFieldImplementation($field_key) + ->getRequiredAdapterStates(); + } + + final public function getRequiredActionStates($action_key) { + return $this->requireActionImplementation($action_key) + ->getRequiredAdapterStates(); + } + + final public function getForbiddenReason($action) { + if (!isset($this->forbiddenActions[$action])) { + throw new Exception( + pht( + 'Action "%s" is not forbidden!', + $action)); + } + + return $this->forbiddenActions[$action]; + } + } diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index a9509beb33..dc7ca676e3 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -273,7 +273,11 @@ final class HeraldTranscriptController extends HeraldController { ->setTarget(phutil_tag('strong', array(), pht('Conditions')))); foreach ($cond_xscripts as $cond_xscript) { - if ($cond_xscript->getResult()) { + if ($cond_xscript->isForbidden()) { + $icon = 'fa-ban'; + $color = 'indigo'; + $result = pht('Forbidden'); + } else if ($cond_xscript->getResult()) { $icon = 'fa-check'; $color = 'green'; $result = pht('Passed'); @@ -284,12 +288,17 @@ final class HeraldTranscriptController extends HeraldController { } if ($cond_xscript->getNote()) { + $note_text = $cond_xscript->getNote(); + if ($cond_xscript->isForbidden()) { + $note_text = HeraldStateReasons::getExplanation($note_text); + } + $note = phutil_tag( 'div', array( 'class' => 'herald-condition-note', ), - $cond_xscript->getNote()); + $note_text); } else { $note = null; } @@ -310,7 +319,12 @@ final class HeraldTranscriptController extends HeraldController { $cond_list->addItem($cond_item); } - if ($rule_xscript->getResult()) { + if ($rule_xscript->isForbidden()) { + $last_icon = 'fa-ban'; + $last_color = 'indigo'; + $last_result = pht('Forbidden'); + $last_note = pht('Object state prevented rule evaluation.'); + } else if ($rule_xscript->getResult()) { $last_icon = 'fa-check-circle'; $last_color = 'green'; $last_result = pht('Passed'); diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php index df81117b80..b8e1db5626 100644 --- a/src/applications/herald/engine/HeraldEngine.php +++ b/src/applications/herald/engine/HeraldEngine.php @@ -12,6 +12,9 @@ final class HeraldEngine extends Phobject { protected $object; private $dryRun; + private $forbiddenFields = array(); + private $forbiddenActions = array(); + public function setDryRun($dry_run) { $this->dryRun = $dry_run; return $this; @@ -76,39 +79,42 @@ final class HeraldEngine extends Phobject { // This is not a dry run, and this rule is only supposed to be // applied a single time, and it's already been applied... // That means automatic failure. - $xscript = id(new HeraldRuleTranscript()) - ->setRuleID($rule->getID()) + $this->newRuleTranscript($rule) ->setResult(false) - ->setRuleName($rule->getName()) - ->setRuleOwner($rule->getAuthorPHID()) ->setReason( pht( 'This rule is only supposed to be repeated a single time, '. 'and it has already been applied.')); - $this->transcript->addRuleTranscript($xscript); + $rule_matches = false; } else { - $rule_matches = $this->doesRuleMatch($rule, $object); + if ($this->isForbidden($rule, $object)) { + $this->newRuleTranscript($rule) + ->setResult(HeraldRuleTranscript::RESULT_FORBIDDEN) + ->setReason( + pht( + 'Object state is not compatible with rule.')); + + $rule_matches = false; + } else { + $rule_matches = $this->doesRuleMatch($rule, $object); + } } } catch (HeraldRecursiveConditionsException $ex) { $names = array(); - foreach ($this->stack as $rule_id => $ignored) { - $names[] = '"'.$rules[$rule_id]->getName().'"'; + foreach ($this->stack as $rule_phid => $ignored) { + $names[] = '"'.$rules[$rule_phid]->getName().'"'; } $names = implode(', ', $names); - foreach ($this->stack as $rule_id => $ignored) { - $xscript = new HeraldRuleTranscript(); - $xscript->setRuleID($rule_id); - $xscript->setResult(false); - $xscript->setReason( - pht( - "Rules %s are recursively dependent upon one another! ". - "Don't do this! You have formed an unresolvable cycle in the ". - "dependency graph!", - $names)); - $xscript->setRuleName($rules[$rule_id]->getName()); - $xscript->setRuleOwner($rules[$rule_id]->getAuthorPHID()); - $this->transcript->addRuleTranscript($xscript); + foreach ($this->stack as $rule_phid => $ignored) { + $this->newRuleTranscript($rules[$rule_phid]) + ->setResult(false) + ->setReason( + pht( + "Rules %s are recursively dependent upon one another! ". + "Don't do this! You have formed an unresolvable cycle in the ". + "dependency graph!", + $names)); } $rule_matches = false; } @@ -309,14 +315,9 @@ final class HeraldEngine extends Phobject { } } - $rule_transcript = new HeraldRuleTranscript(); - $rule_transcript->setRuleID($rule->getID()); - $rule_transcript->setResult($result); - $rule_transcript->setReason($reason); - $rule_transcript->setRuleName($rule->getName()); - $rule_transcript->setRuleOwner($rule->getAuthorPHID()); - - $this->transcript->addRuleTranscript($rule_transcript); + $this->newRuleTranscript($rule) + ->setResult($result) + ->setReason($reason); return $result; } @@ -327,16 +328,7 @@ final class HeraldEngine extends Phobject { HeraldAdapter $object) { $object_value = $this->getConditionObjectValue($condition, $object); - $test_value = $condition->getValue(); - - $cond = $condition->getFieldCondition(); - - $transcript = new HeraldConditionTranscript(); - $transcript->setRuleID($rule->getID()); - $transcript->setConditionID($condition->getID()); - $transcript->setFieldName($condition->getFieldName()); - $transcript->setCondition($cond); - $transcript->setTestValue($test_value); + $transcript = $this->newConditionTranscript($rule, $condition); try { $result = $object->doesConditionMatch( @@ -351,8 +343,6 @@ final class HeraldEngine extends Phobject { $transcript->setResult($result); - $this->transcript->addConditionTranscript($transcript); - return $result; } @@ -446,4 +436,136 @@ final class HeraldEngine extends Phobject { return false; } + private function newRuleTranscript(HeraldRule $rule) { + $xscript = id(new HeraldRuleTranscript()) + ->setRuleID($rule->getID()) + ->setRuleName($rule->getName()) + ->setRuleOwner($rule->getAuthorPHID()); + + $this->transcript->addRuleTranscript($xscript); + + return $xscript; + } + + private function newConditionTranscript( + HeraldRule $rule, + HeraldCondition $condition) { + + $xscript = id(new HeraldConditionTranscript()) + ->setRuleID($rule->getID()) + ->setConditionID($condition->getID()) + ->setFieldName($condition->getFieldName()) + ->setCondition($condition->getFieldCondition()) + ->setTestValue($condition->getValue()); + + $this->transcript->addConditionTranscript($xscript); + + return $xscript; + } + + private function newApplyTranscript( + HeraldAdapter $adapter, + HeraldRule $rule, + HeraldActionRecord $action) { + + $effect = id(new HeraldEffect()) + ->setObjectPHID($adapter->getPHID()) + ->setAction($action->getAction()) + ->setTarget($action->getTarget()) + ->setRule($rule); + + $xscript = new HeraldApplyTranscript($effect, false); + + $this->transcript->addApplyTranscript($xscript); + + return $xscript; + } + + private function isForbidden( + HeraldRule $rule, + HeraldAdapter $adapter) { + + $forbidden = $adapter->getForbiddenActions(); + if (!$forbidden) { + return false; + } + + $forbidden = array_fuse($forbidden); + + $is_forbidden = false; + + foreach ($rule->getConditions() as $condition) { + $field_key = $condition->getFieldName(); + + if (!isset($this->forbiddenFields[$field_key])) { + $reason = null; + + try { + $states = $adapter->getRequiredFieldStates($field_key); + } catch (Exception $ex) { + $states = array(); + } + + foreach ($states as $state) { + if (!isset($forbidden[$state])) { + continue; + } + $reason = $adapter->getForbiddenReason($state); + break; + } + + $this->forbiddenFields[$field_key] = $reason; + } + + $forbidden_reason = $this->forbiddenFields[$field_key]; + if ($forbidden_reason !== null) { + $this->newConditionTranscript($rule, $condition) + ->setResult(HeraldConditionTranscript::RESULT_FORBIDDEN) + ->setNote($forbidden_reason); + + $is_forbidden = true; + } + } + + foreach ($rule->getActions() as $action_record) { + $action_key = $action_record->getAction(); + + if (!isset($this->forbiddenActions[$action_key])) { + $reason = null; + + try { + $states = $adapter->getRequiredActionStates($action_key); + } catch (Exception $ex) { + $states = array(); + } + + foreach ($states as $state) { + if (!isset($forbidden[$state])) { + continue; + } + $reason = $adapter->getForbiddenReason($state); + break; + } + + $this->forbiddenActions[$action_key] = $reason; + } + + $forbidden_reason = $this->forbiddenActions[$action_key]; + if ($forbidden_reason !== null) { + $this->newApplyTranscript($adapter, $rule, $action_record) + ->setAppliedReason( + array( + array( + 'type' => HeraldAction::DO_STANDARD_FORBIDDEN, + 'data' => $forbidden_reason, + ), + )); + + $is_forbidden = true; + } + } + + return $is_forbidden; + } + } diff --git a/src/applications/herald/field/HeraldField.php b/src/applications/herald/field/HeraldField.php index 2abed0ff1d..408a76f7fb 100644 --- a/src/applications/herald/field/HeraldField.php +++ b/src/applications/herald/field/HeraldField.php @@ -20,6 +20,10 @@ abstract class HeraldField extends Phobject { return null; } + public function getRequiredAdapterStates() { + return array(); + } + protected function getHeraldFieldStandardType() { throw new PhutilMethodNotImplementedException(); } diff --git a/src/applications/herald/state/HeraldBuildableState.php b/src/applications/herald/state/HeraldBuildableState.php new file mode 100644 index 0000000000..d19ae87c36 --- /dev/null +++ b/src/applications/herald/state/HeraldBuildableState.php @@ -0,0 +1,7 @@ +setAncestorClass(__CLASS__) + ->execute(); + } + + final public static function getExplanation($reason) { + $reasons = self::getAllReasons(); + + foreach ($reasons as $reason_implementation) { + $explanation = $reason_implementation->explainReason($reason); + if ($explanation !== null) { + return $explanation; + } + } + + return pht('Unknown reason ("%s").', $reason); + } + +} diff --git a/src/applications/herald/storage/transcript/HeraldConditionTranscript.php b/src/applications/herald/storage/transcript/HeraldConditionTranscript.php index 36a534e6b0..a7096b6880 100644 --- a/src/applications/herald/storage/transcript/HeraldConditionTranscript.php +++ b/src/applications/herald/storage/transcript/HeraldConditionTranscript.php @@ -10,6 +10,8 @@ final class HeraldConditionTranscript extends Phobject { protected $note; protected $result; + const RESULT_FORBIDDEN = 'forbidden'; + public function setRuleID($rule_id) { $this->ruleID = $rule_id; return $this; @@ -72,4 +74,9 @@ final class HeraldConditionTranscript extends Phobject { public function getResult() { return $this->result; } + + public function isForbidden() { + return ($this->getResult() === self::RESULT_FORBIDDEN); + } + } diff --git a/src/applications/herald/storage/transcript/HeraldRuleTranscript.php b/src/applications/herald/storage/transcript/HeraldRuleTranscript.php index d12f1f9dd9..8928a74cf6 100644 --- a/src/applications/herald/storage/transcript/HeraldRuleTranscript.php +++ b/src/applications/herald/storage/transcript/HeraldRuleTranscript.php @@ -9,6 +9,12 @@ final class HeraldRuleTranscript extends Phobject { protected $ruleName; protected $ruleOwner; + const RESULT_FORBIDDEN = 'forbidden'; + + public function isForbidden() { + return ($this->getResult() === self::RESULT_FORBIDDEN); + } + public function setResult($result) { $this->result = $result; return $this; diff --git a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php index 2b29fe2443..74fb879fe7 100644 --- a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php +++ b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php @@ -6,6 +6,12 @@ abstract class PhabricatorMetaMTAEmailHeraldAction const DO_SEND = 'do.send'; const DO_FORCE = 'do.force'; + public function getRequiredAdapterStates() { + return array( + HeraldMailableState::STATECONST, + ); + } + public function supportsObject($object) { // NOTE: This implementation lacks generality, but there's no great way to // figure out if something generates email right now. From 0da3f34728e313b0a2074984911cf320ef82c106 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 27 Oct 2017 11:27:53 -0700 Subject: [PATCH 20/72] Provide "differential.diff.search" Summary: See PHI90. For now, this only provides a limited amount of information, but should satisfy the use case in PHI90 and build toward a more complete version in the future. Test Plan: Used new Conduit method to retrieve information about diffs. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18744 --- src/__phutil_library_map__.php | 5 ++ ...DifferentialDiffSearchConduitAPIMethod.php | 18 +++++ ...DifferentialQueryDiffsConduitAPIMethod.php | 10 +++ .../query/DifferentialDiffQuery.php | 25 ++++++ .../query/DifferentialDiffSearchEngine.php | 79 ++++++++++++++++++ .../differential/storage/DifferentialDiff.php | 81 ++++++++++++++++++- 6 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 src/applications/differential/conduit/DifferentialDiffSearchConduitAPIMethod.php create mode 100644 src/applications/differential/query/DifferentialDiffSearchEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2040568496..ff1f00c821 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -439,6 +439,8 @@ phutil_register_library_map(array( 'DifferentialDiffQuery' => 'applications/differential/query/DifferentialDiffQuery.php', 'DifferentialDiffRepositoryHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryHeraldField.php', 'DifferentialDiffRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryProjectsHeraldField.php', + 'DifferentialDiffSearchConduitAPIMethod' => 'applications/differential/conduit/DifferentialDiffSearchConduitAPIMethod.php', + 'DifferentialDiffSearchEngine' => 'applications/differential/query/DifferentialDiffSearchEngine.php', 'DifferentialDiffTestCase' => 'applications/differential/storage/__tests__/DifferentialDiffTestCase.php', 'DifferentialDiffTransaction' => 'applications/differential/storage/DifferentialDiffTransaction.php', 'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php', @@ -5435,6 +5437,7 @@ phutil_register_library_map(array( 'HarbormasterBuildkiteBuildableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorConduitResultInterface', ), 'DifferentialDiffAffectedFilesHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffAuthorHeraldField' => 'DifferentialDiffHeraldField', @@ -5453,6 +5456,8 @@ phutil_register_library_map(array( 'DifferentialDiffQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DifferentialDiffRepositoryHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffRepositoryProjectsHeraldField' => 'DifferentialDiffHeraldField', + 'DifferentialDiffSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'DifferentialDiffSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DifferentialDiffTestCase' => 'PhutilTestCase', 'DifferentialDiffTransaction' => 'PhabricatorApplicationTransaction', 'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery', diff --git a/src/applications/differential/conduit/DifferentialDiffSearchConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialDiffSearchConduitAPIMethod.php new file mode 100644 index 0000000000..d4222510d7 --- /dev/null +++ b/src/applications/differential/conduit/DifferentialDiffSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ + 'optional list', diff --git a/src/applications/differential/query/DifferentialDiffQuery.php b/src/applications/differential/query/DifferentialDiffQuery.php index f779e40c26..3b1dd3ae62 100644 --- a/src/applications/differential/query/DifferentialDiffQuery.php +++ b/src/applications/differential/query/DifferentialDiffQuery.php @@ -6,6 +6,7 @@ final class DifferentialDiffQuery private $ids; private $phids; private $revisionIDs; + private $revisionPHIDs; private $commitPHIDs; private $hasRevision; @@ -27,6 +28,11 @@ final class DifferentialDiffQuery return $this; } + public function withRevisionPHIDs(array $revision_phids) { + $this->revisionPHIDs = $revision_phids; + return $this; + } + public function withCommitPHIDs(array $phids) { $this->commitPHIDs = $phids; return $this; @@ -160,6 +166,25 @@ final class DifferentialDiffQuery } } + if ($this->revisionPHIDs !== null) { + $viewer = $this->getViewer(); + + $revisions = id(new DifferentialRevisionQuery()) + ->setViewer($viewer) + ->setParentQuery($this) + ->withPHIDs($this->revisionPHIDs) + ->execute(); + $revision_ids = mpull($revisions, 'getID'); + if (!$revision_ids) { + throw new PhabricatorEmptyQueryException(); + } + + $where[] = qsprintf( + $conn, + 'revisionID IN (%Ls)', + $revision_ids); + } + return $where; } diff --git a/src/applications/differential/query/DifferentialDiffSearchEngine.php b/src/applications/differential/query/DifferentialDiffSearchEngine.php new file mode 100644 index 0000000000..89feeb5c3e --- /dev/null +++ b/src/applications/differential/query/DifferentialDiffSearchEngine.php @@ -0,0 +1,79 @@ +newQuery(); + + if ($map['revisionPHIDs']) { + $query->withRevisionPHIDs($map['revisionPHIDs']); + } + + return $query; + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorPHIDsSearchField()) + ->setLabel(pht('Revisions')) + ->setKey('revisionPHIDs') + ->setAliases(array('revision', 'revisions', 'revisionPHID')) + ->setDescription( + pht('Find diffs attached to a particular revision.')), + ); + } + + protected function getURI($path) { + return '/differential/diff/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array(); + + $names['all'] = pht('All Diffs'); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + $viewer = $this->requireViewer(); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $revisions, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($revisions, 'DifferentialDiff'); + + $viewer = $this->requireViewer(); + + // NOTE: This is only exposed to Conduit, so we don't currently render + // results. + + return id(new PhabricatorApplicationSearchResultView()); + } + +} diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index f7e85cd835..d70cdbbdf5 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -9,7 +9,8 @@ final class DifferentialDiff HarbormasterCircleCIBuildableInterface, HarbormasterBuildkiteBuildableInterface, PhabricatorApplicationTransactionInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorConduitResultInterface { protected $revisionID; protected $authorPHID; @@ -740,4 +741,82 @@ final class DifferentialDiff $this->saveTransaction(); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('revisionPHID') + ->setType('phid') + ->setDescription(pht('Associated revision PHID.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('authorPHID') + ->setType('phid') + ->setDescription(pht('Revision author PHID.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('repositoryPHID') + ->setType('phid') + ->setDescription(pht('Associated repository PHID.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('refs') + ->setType('map') + ->setDescription(pht('List of related VCS references.')), + ); + } + + public function getFieldValuesForConduit() { + $refs = array(); + + $branch = $this->getBranch(); + if (strlen($branch)) { + $refs[] = array( + 'type' => 'branch', + 'name' => $branch, + ); + } + + $onto = $this->loadTargetBranch(); + if (strlen($onto)) { + $refs[] = array( + 'type' => 'onto', + 'name' => $onto, + ); + } + + $base = $this->getSourceControlBaseRevision(); + if (strlen($base)) { + $refs[] = array( + 'type' => 'base', + 'identifier' => $base, + ); + } + + $bookmark = $this->getBookmark(); + if (strlen($bookmark)) { + $refs[] = array( + 'type' => 'bookmark', + 'name' => $bookmark, + ); + } + + $revision_phid = null; + if ($this->getRevisionID()) { + $revision_phid = $this->getRevision()->getPHID(); + } + + return array( + 'revisionPHID' => $revision_phid, + 'authorPHID' => $this->getAuthorPHID(), + 'repositoryPHID' => $this->getRepositoryPHID(), + 'refs' => $refs, + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + } From f5336cd6e760e6089f009130bb25f2a457c50c4e Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 27 Oct 2017 10:00:34 -0700 Subject: [PATCH 21/72] Return transactions from "differential.parsecommitmessage" Summary: Depends on D18740. Prepares `arc` to receive a `--draft` flag by letting us switch to "differential.revision.edit" instead of "differential.createrevision". To "differential.revision.edit", we need a transaction list, but we can't automatically construct this list from a field map. Return the transaction list alongside the field map. The next change uses this list (if available) to switch us to the modern API method. Test Plan: Ran `arc diff` on the experiemntal branch with followup changes, got a new revision. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18741 --- ...tialParseCommitMessageConduitAPIMethod.php | 2 ++ .../DifferentialCommitMessageParser.php | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php index 59401862f0..d1ad6b67e7 100644 --- a/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php @@ -36,6 +36,7 @@ final class DifferentialParseCommitMessageConduitAPIMethod $field_map = $parser->parseFields($corpus); $errors = $parser->getErrors(); + $xactions = $parser->getTransactions(); $revision_id_value = idx( $field_map, @@ -49,6 +50,7 @@ final class DifferentialParseCommitMessageConduitAPIMethod 'value' => $revision_id_value, 'validDomain' => $revision_id_valid_domain, ), + 'transactions' => $xactions, ); } diff --git a/src/applications/differential/parser/DifferentialCommitMessageParser.php b/src/applications/differential/parser/DifferentialCommitMessageParser.php index 7e5a50e61f..3a73428e4b 100644 --- a/src/applications/differential/parser/DifferentialCommitMessageParser.php +++ b/src/applications/differential/parser/DifferentialCommitMessageParser.php @@ -28,6 +28,7 @@ final class DifferentialCommitMessageParser extends Phobject { private $errors; private $commitMessageFields; private $raiseMissingFieldErrors = true; + private $xactions; public static function newStandardParser(PhabricatorUser $viewer) { $key_title = DifferentialTitleCommitMessageField::FIELDKEY; @@ -134,6 +135,7 @@ final class DifferentialCommitMessageParser extends Phobject { */ public function parseCorpus($corpus) { $this->errors = array(); + $this->xactions = array(); $label_map = $this->getLabelMap(); $key_title = $this->titleKey; @@ -284,12 +286,25 @@ final class DifferentialCommitMessageParser extends Phobject { try { $result = $field->parseFieldValue($text_value); $result_map[$field_key] = $result; + + try { + $xactions = $field->getFieldTransactions($result); + foreach ($xactions as $xaction) { + $this->xactions[] = $xaction; + } + } catch (Exception $ex) { + $this->errors[] = pht( + 'Error extracting field transactions from "%s": %s', + $field->getFieldName(), + $ex->getMessage()); + } } catch (DifferentialFieldParseException $ex) { $this->errors[] = pht( 'Error parsing field "%s": %s', $field->getFieldName(), $ex->getMessage()); } + } if ($this->getRaiseMissingFieldErrors()) { @@ -317,6 +332,14 @@ final class DifferentialCommitMessageParser extends Phobject { } + /** + * @task parse + */ + public function getTransactions() { + return $this->xactions; + } + + /* -( Support Methods )---------------------------------------------------- */ From 28cec2f8a2bf96df572c04360c412c8436356421 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 27 Oct 2017 08:55:56 -0700 Subject: [PATCH 22/72] Allow revisions to be held as drafts, even after builds finish Summary: Ref T2543. Instead of autosubmitting revisions to "Needs Review" when builds finish, allow them to be held in "Draft" indefinitely. There's currently no UI for this. I plan to just expose it as `arc diff --draft` for now, in a followup change. Test Plan: - Created a revision (via Conduit) with "hold as draft", saw it hold as draft after builds finished. - Created a revision (normally), saw it autosubmit after builds finished. - Requested review of a "hold as draft" revision to kick it out of draft state. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18737 --- src/__phutil_library_map__.php | 2 + .../customfield/DifferentialDraftField.php | 6 +++ .../editor/DifferentialRevisionEditEngine.php | 16 +++++++ .../editor/DifferentialTransactionEditor.php | 2 +- .../storage/DifferentialRevision.php | 9 ++++ ...fferentialRevisionHoldDraftTransaction.php | 47 +++++++++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ff1f00c821..7180a7ae04 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -550,6 +550,7 @@ phutil_register_library_map(array( 'DifferentialRevisionHasTaskRelationship' => 'applications/differential/relationships/DifferentialRevisionHasTaskRelationship.php', 'DifferentialRevisionHeraldField' => 'applications/differential/herald/DifferentialRevisionHeraldField.php', 'DifferentialRevisionHeraldFieldGroup' => 'applications/differential/herald/DifferentialRevisionHeraldFieldGroup.php', + 'DifferentialRevisionHoldDraftTransaction' => 'applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php', 'DifferentialRevisionIDCommitMessageField' => 'applications/differential/field/DifferentialRevisionIDCommitMessageField.php', 'DifferentialRevisionInlineTransaction' => 'applications/differential/xaction/DifferentialRevisionInlineTransaction.php', 'DifferentialRevisionInlinesController' => 'applications/differential/controller/DifferentialRevisionInlinesController.php', @@ -5592,6 +5593,7 @@ phutil_register_library_map(array( 'DifferentialRevisionHasTaskRelationship' => 'DifferentialRevisionRelationship', 'DifferentialRevisionHeraldField' => 'HeraldField', 'DifferentialRevisionHeraldFieldGroup' => 'HeraldFieldGroup', + 'DifferentialRevisionHoldDraftTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialRevisionIDCommitMessageField' => 'DifferentialCommitMessageField', 'DifferentialRevisionInlineTransaction' => 'PhabricatorModularTransactionType', 'DifferentialRevisionInlinesController' => 'DifferentialController', diff --git a/src/applications/differential/customfield/DifferentialDraftField.php b/src/applications/differential/customfield/DifferentialDraftField.php index e7ed2bedb2..5d625e3ce9 100644 --- a/src/applications/differential/customfield/DifferentialDraftField.php +++ b/src/applications/differential/customfield/DifferentialDraftField.php @@ -36,6 +36,12 @@ final class DifferentialDraftField return array(); } + // If the author has held this revision as a draft explicitly, don't + // show any misleading messages about it autosubmitting later. + if ($revision->getHoldAsDraft()) { + return array(); + } + $warnings = array(); $blocking_map = array( diff --git a/src/applications/differential/editor/DifferentialRevisionEditEngine.php b/src/applications/differential/editor/DifferentialRevisionEditEngine.php index 9aad031a7c..2bd3a5cdc3 100644 --- a/src/applications/differential/editor/DifferentialRevisionEditEngine.php +++ b/src/applications/differential/editor/DifferentialRevisionEditEngine.php @@ -235,6 +235,22 @@ final class DifferentialRevisionEditEngine $fields[] = $action->newEditField($object, $viewer); } + $fields[] = id(new PhabricatorBoolEditField()) + ->setKey('draft') + ->setLabel(pht('Hold as Draft')) + ->setIsConduitOnly(true) + ->setOptions( + pht('Autosubmit Once Builds Finish'), + pht('Hold as Draft')) + ->setTransactionType( + DifferentialRevisionHoldDraftTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Hold revision as as draft.')) + ->setConduitDescription( + pht( + 'Change autosubmission from draft state after builds finish.')) + ->setConduitTypeDescription(pht('New "Hold as Draft" setting.')) + ->setValue($object->getHoldAsDraft()); + return $fields; } diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 9ab30c3f06..edc8fd0ad3 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1540,7 +1540,7 @@ final class DifferentialTransactionEditor protected function didApplyTransactions($object, array $xactions) { // If a draft revision has no outstanding builds and we're automatically // making drafts public after builds finish, make the revision public. - $auto_undraft = true; + $auto_undraft = !$object->getHoldAsDraft(); if ($object->isDraft() && $auto_undraft) { $active_builds = $this->hasActiveBuilds($object); diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index da7e18ce05..467e503f6f 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -57,6 +57,7 @@ final class DifferentialRevision extends DifferentialDAO const RELATION_SUBSCRIBED = 'subd'; const PROPERTY_CLOSED_FROM_ACCEPTED = 'wasAcceptedBeforeClose'; + const PROPERTY_DRAFT_HOLD = 'draft.hold'; public static function initializeNewRevision(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) @@ -708,6 +709,14 @@ final class DifferentialRevision extends DifferentialDAO return false; } + public function getHoldAsDraft() { + return $this->getProperty(self::PROPERTY_DRAFT_HOLD, false); + } + + public function setHoldAsDraft($hold) { + return $this->setProperty(self::PROPERTY_DRAFT_HOLD, $hold); + } + public function loadActiveBuilds(PhabricatorUser $viewer) { $diff = $this->getActiveDiff(); diff --git a/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php b/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php new file mode 100644 index 0000000000..5bc257ab62 --- /dev/null +++ b/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php @@ -0,0 +1,47 @@ +getHoldAsDraft(); + } + + public function generateNewValue($object, $value) { + return (bool)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setHoldAsDraft($value); + } + + public function getTitle() { + if ($this->getNewValue()) { + return pht( + '%s held this revision as a draft.', + $this->renderAuthor()); + } else { + return pht( + '%s set this revision to automatically submit once builds complete.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + if ($this->getNewValue()) { + return pht( + '%s held %s as a draft.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s set %s to automatically submit once builds complete.', + $this->renderAuthor(), + $this->renderObject()); + } + } + +} From 7fa0d066bc459e47e7381476c6fd708355cb9104 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 31 Oct 2017 10:29:33 -0700 Subject: [PATCH 23/72] Don't run Herald build rules when Differential revisions are updated automatically Summary: Ref T2543. After D18731, Herald build rules run more often, but now incorrectly try to run builds when Diffusion closes a revision because a commit landed. Test Plan: Made some mundane updates locally; this is tricky to test comprehensively locally so I'm mostly planning to just push it to `secure`. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18745 --- .../editor/DifferentialTransactionEditor.php | 24 +++++++++++++++---- .../herald/DifferentialHeraldStateReasons.php | 4 ++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index edc8fd0ad3..70c3272a83 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1204,16 +1204,32 @@ final class DifferentialTransactionEditor // edited the title or changed subscribers), prevent "Run build plan" // and other similar rules from acting yet, since the build results will // not (or, at least, should not) change unless the actual source changes. + // We also don't run Differential builds if the update was caused by + // discovering a commit, as the expectation is that Diffusion builds take + // over once things land. $has_update = false; + $has_commit = false; + $type_update = DifferentialTransaction::TYPE_UPDATE; foreach ($xactions as $xaction) { - if ($xaction->getTransactionType() == $type_update) { - $has_update = true; - break; + if ($xaction->getTransactionType() != $type_update) { + continue; } + + if ($xaction->getMetadataValue('isCommitUpdate')) { + $has_commit = true; + } else { + $has_update = true; + } + + break; } - if (!$has_update) { + if ($has_commit) { + $adapter->setForbiddenAction( + HeraldBuildableState::STATECONST, + DifferentialHeraldStateReasons::REASON_LANDED); + } else if (!$has_update) { $adapter->setForbiddenAction( HeraldBuildableState::STATECONST, DifferentialHeraldStateReasons::REASON_UNCHANGED); diff --git a/src/applications/differential/herald/DifferentialHeraldStateReasons.php b/src/applications/differential/herald/DifferentialHeraldStateReasons.php index d3259560d5..92d2ca3067 100644 --- a/src/applications/differential/herald/DifferentialHeraldStateReasons.php +++ b/src/applications/differential/herald/DifferentialHeraldStateReasons.php @@ -5,6 +5,7 @@ final class DifferentialHeraldStateReasons const REASON_DRAFT = 'differential.draft'; const REASON_UNCHANGED = 'differential.unchanged'; + const REASON_LANDED = 'differential.landed'; public function explainReason($reason) { $reasons = array( @@ -14,6 +15,9 @@ final class DifferentialHeraldStateReasons self::REASON_UNCHANGED => pht( 'The update which triggered Herald did not update the diff for '. 'this revision, so builds will not run.'), + self::REASON_LANDED => pht( + 'The update which triggered Herald was an automatic update in '. + 'response to discovering a commit, so builds will not run.'), ); return idx($reasons, $reason); From 90d0f8ac6cf5eb12406f330a624bbf87c156f517 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 31 Oct 2017 10:49:19 -0700 Subject: [PATCH 24/72] Revert changes to Diffusion blame view Summary: Ref PHI174. This reverts most of these changes: - 37843127e94a878a7f5bf2c65c8e7004bc65c68a / D18481 - 94cad30ac3f052a711ececf7e370bf5c0071827f / D18474 - 12ae08b6b1a1b7c330593e76c32817f7cdbc87dd / D18473 - 0a013341721f8b1fc249047fe6db26062138b562 / D18462 - ac91ab1ef9196eee0deabfd70157ccc0d53d666e / D18452 These changes made the Diffusion blame view very similar to GitHub's blame view. See D18452 for a before/after of the bulk of these changes; the other revisions are bugfixes. I think this was generally a step backward, and not motivated by solving a specific problem. I've found the new UI less usable than the old one, and at least one install (see PHI174) also has. In particular, the revision/commit titles are very bulky and not terribly useful; the date column also isn't terribly useful; the "age" color actually IS pretty useful and was heavily de-emphasized. I've kept one bugfix here (missing `'a'` tag type) and kept the upgraded icon for "Skip Past This Commit". I'm going to follow this up with some additional changes: - Show a small author profile icon, similar to GitHub, to address PHI174 more directly. - Try a zebra-stripe on blocks of rows to make it more clear where changes affected by a particular commit begin and end. - Try a hue shift, not just a brightness/saturation shift, to make the "age" color more distinct. - Try computing colors as even steps, not based purely on age. Currently, if a file has one long-distant commit and several recent commits, all the recent ones show up as very bright green. I think this would probably be more useful if they were distributed more evenly across the available color bands. Test Plan: Viewed blame views in Diffusion, saw a more compact UI similar to the old UI. {F5251019} Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18746 --- resources/celerity/map.php | 4 +- .../controller/DiffusionBrowseController.php | 109 +++++++++++------- .../diffusion/diffusion-source.css | 59 +++------- 3 files changed, 88 insertions(+), 84 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ce069dbd11..517827a900 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -73,7 +73,7 @@ return array( 'rsrc/css/application/diffusion/diffusion-icons.css' => '0c15255e', 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', - 'rsrc/css/application/diffusion/diffusion-source.css' => '69ac9399', + 'rsrc/css/application/diffusion/diffusion-source.css' => '3a1056d8', 'rsrc/css/application/diffusion/diffusion.css' => '45727264', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', @@ -572,7 +572,7 @@ return array( 'diffusion-icons-css' => '0c15255e', 'diffusion-readme-css' => '419dd5b6', 'diffusion-repository-css' => 'ee6f20ec', - 'diffusion-source-css' => '69ac9399', + 'diffusion-source-css' => '3a1056d8', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index b4c0352d53..be9d4fbb9b 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1127,6 +1127,10 @@ final class DiffusionBrowseController extends DiffusionController { )); } + $skip_text = pht('Skip Past This Commit'); + $skip_icon = id(new PHUIIconView()) + ->setIcon('fa-caret-square-o-left'); + foreach ($display as $line_index => $line) { $row = array(); @@ -1142,14 +1146,12 @@ final class DiffusionBrowseController extends DiffusionController { $revision_link = null; $commit_link = null; $before_link = null; - $commit_date = null; - $style = 'border-right: 3px solid '.$line['color'].';'; + $style = 'background: '.$line['color'].';'; if ($identifier && !$line['duplicate']) { if (isset($commit_links[$identifier])) { - $commit_link = $commit_links[$identifier]['link']; - $commit_date = $commit_links[$identifier]['date']; + $commit_link = $commit_links[$identifier]; } if (isset($revision_map[$identifier])) { @@ -1160,10 +1162,6 @@ final class DiffusionBrowseController extends DiffusionController { } $skip_href = $line_href.'?before='.$identifier.'&view=blame'; - $skip_text = pht('Skip Past This Commit'); - $icon = id(new PHUIIconView()) - ->setIcon('fa-caret-square-o-left'); - $before_link = javelin_tag( 'a', array( @@ -1175,7 +1173,7 @@ final class DiffusionBrowseController extends DiffusionController { 'size' => 300, ), ), - $icon); + $skip_icon); } if ($show_blame) { @@ -1186,41 +1184,33 @@ final class DiffusionBrowseController extends DiffusionController { ), $before_link); - $row[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-rev-link', - ), - $commit_link); - - if ($revision_map) { - $row[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-blame-revision', - ), - $revision_link); + $object_links = array(); + $object_links[] = $commit_link; + if ($revision_link) { + $object_links[] = phutil_tag('span', array(), '/'); + $object_links[] = $revision_link; } $row[] = phutil_tag( 'th', array( - 'class' => 'diffusion-blame-date', + 'class' => 'diffusion-rev-link', ), - $commit_date); + $object_links); } $line_link = phutil_tag( 'a', array( 'href' => $line_href, + 'style' => $style, ), $line_number); $row[] = javelin_tag( 'th', array( - 'class' => 'diffusion-line-link ', + 'class' => 'diffusion-line-link', 'sigil' => 'phabricator-source-line', 'style' => $style, ), @@ -1536,6 +1526,33 @@ final class DiffusionBrowseController extends DiffusionController { return head($parents); } + private function renderRevisionTooltip( + DifferentialRevision $revision, + $handles) { + $viewer = $this->getRequest()->getUser(); + + $date = phabricator_date($revision->getDateModified(), $viewer); + $id = $revision->getID(); + $title = $revision->getTitle(); + $header = "D{$id} {$title}"; + + $author = $handles[$revision->getAuthorPHID()]->getName(); + + return "{$header}\n{$date} \xC2\xB7 {$author}"; + } + + private function renderCommitTooltip( + PhabricatorRepositoryCommit $commit, + $author) { + + $viewer = $this->getRequest()->getUser(); + + $date = phabricator_date($commit->getEpoch(), $viewer); + $summary = trim($commit->getSummary()); + + return "{$summary}\n{$date} \xC2\xB7 {$author}"; + } + protected function markupText($text) { $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); $engine->setConfig('viewer', $this->getRequest()->getUser()); @@ -1743,6 +1760,9 @@ final class DiffusionBrowseController extends DiffusionController { ->setViewer($viewer) ->withRepository($repository) ->withIdentifiers($identifiers) + // TODO: We only fetch this to improve author display behavior, but + // shouldn't really need to? + ->needCommitData(true) ->execute(); $commits = mpull($commits, null, 'getCommitIdentifier'); } else { @@ -1754,27 +1774,25 @@ final class DiffusionBrowseController extends DiffusionController { private function renderCommitLinks(array $commits, $handles) { $links = array(); - $viewer = $this->getViewer(); foreach ($commits as $identifier => $commit) { - $date = phabricator_date($commit->getEpoch(), $viewer); - $summary = trim($commit->getSummary()); + $tooltip = $this->renderCommitTooltip( + $commit, + $commit->renderAuthorShortName($handles)); - $commit_link = phutil_tag( + $commit_link = javelin_tag( 'a', array( 'href' => $commit->getURI(), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tooltip, + 'align' => 'E', + 'size' => 600, + ), ), - $summary); + $commit->getLocalName()); - $commit_date = phutil_tag( - 'a', - array( - 'href' => $commit->getURI(), - ), - $date); - - $links[$identifier]['link'] = $commit_link; - $links[$identifier]['date'] = $commit_date; + $links[$identifier] = $commit_link; } return $links; @@ -1785,10 +1803,19 @@ final class DiffusionBrowseController extends DiffusionController { foreach ($revisions as $revision) { $revision_id = $revision->getID(); - $revision_link = phutil_tag( + + $tooltip = $this->renderRevisionTooltip($revision, $handles); + + $revision_link = javelin_tag( 'a', array( 'href' => '/'.$revision->getMonogram(), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tooltip, + 'align' => 'E', + 'size' => 600, + ), ), $revision->getMonogram()); diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css index 3b8b9a8a16..9ade2d3022 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-source.css @@ -13,22 +13,24 @@ -webkit-overflow-scrolling: touch; } -.diffusion-source tr.phabricator-source-highlight th, -.diffusion-source tr.phabricator-source-highlight td { - background: {$gentle.highlight}; +.diffusion-source tr.phabricator-source-highlight { + background: {$sh-yellowbackground}; } .diffusion-source th { text-align: right; vertical-align: top; - color: {$darkbluetext}; + background: {$lightgreybackground}; + color: {$bluetext}; border-right: 1px solid {$thinblueborder}; } .diffusion-source td { vertical-align: top; white-space: pre-wrap; - padding: 3px 12px; + padding-top: 1px; + padding-bottom: 1px; + padding-left: 8px; width: 100%; word-break: break-all; } @@ -43,18 +45,12 @@ } .diffusion-blame-link, -.diffusion-rev-link, -.diffusion-blame-date { +.diffusion-rev-link { white-space: nowrap; } -.diffusion-blame-date, -.diffusion-blame-link, -.diffusion-blame-revision, -.diffusion-rev-link { - background: {$lightgreybackground}; - font: {$basefont}; - font-size: {$smallerfontsize}; +.diffusion-blame-link { + min-width: 28px; } .diffusion-source th.diffusion-rev-link { @@ -62,23 +58,16 @@ min-width: 130px; } -.diffusion-source a { +.diffusion-blame-link a, +.diffusion-rev-link a, +.diffusion-line-link a { color: {$darkbluetext}; } -.diffusion-rev-link a { - max-width: 300px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin: 3px 8px; - display: block; -} - -.diffusion-blame-date a, -.diffusion-blame-revision a { - float: right; - margin: 3px 8px; +.diffusion-rev-link a, +.diffusion-rev-link span { + margin: 2px 8px 0; + display: inline-block; } .diffusion-rev-link span { @@ -91,19 +80,7 @@ .diffusion-line-link a { /* Give the user a larger click target. */ display: block; - padding: 4px 8px 3px; -} - -.diffusion-line-link a { - color: {$lightgreytext}; -} - -.diffusion-blame-link a .phui-icon-view { - color: {$bluetext}; -} - -.diffusion-blame-link a:hover .phui-icon-view { - color: {$sky}; + padding: 2px 8px; } .diffusion-line-link { From bde71324f82ba0eaeabeeff8eb6db84505b1614f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 31 Oct 2017 11:28:31 -0700 Subject: [PATCH 25/72] Show small author portraits in Diffusion blame view Summary: Depends on D18746. See PHI174. Adds small author portraits next to each blame line (this is similar to GitHub). Test Plan: My local test data isn't that great since I don't have commits from a lot of accounts, but looks functional: {F5251056} Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18747 --- resources/celerity/map.php | 4 +- .../controller/DiffusionBrowseController.php | 49 +++++++++++++++++++ .../diffusion/diffusion-source.css | 12 ++++- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 517827a900..b7cb976a16 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -73,7 +73,7 @@ return array( 'rsrc/css/application/diffusion/diffusion-icons.css' => '0c15255e', 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', - 'rsrc/css/application/diffusion/diffusion-source.css' => '3a1056d8', + 'rsrc/css/application/diffusion/diffusion-source.css' => 'd96b3f9f', 'rsrc/css/application/diffusion/diffusion.css' => '45727264', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', @@ -572,7 +572,7 @@ return array( 'diffusion-icons-css' => '0c15255e', 'diffusion-readme-css' => '419dd5b6', 'diffusion-repository-css' => 'ee6f20ec', - 'diffusion-source-css' => '3a1056d8', + 'diffusion-source-css' => 'd96b3f9f', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index be9d4fbb9b..f4bd2bdebf 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -969,6 +969,24 @@ final class DiffusionBrowseController extends DiffusionController { $handles = $viewer->loadHandles($phids); + $author_phids = array(); + $author_map = array(); + foreach ($blame_commits as $commit) { + $commit_identifier = $commit->getCommitIdentifier(); + + $author_phid = ''; + if (isset($revision_map[$commit_identifier])) { + $revision_id = $revision_map[$commit_identifier]; + $revision = $revisions[$revision_id]; + $author_phid = $revision->getAuthorPHID(); + } else { + $author_phid = $commit->getAuthorPHID(); + } + + $author_map[$commit_identifier] = $author_phid; + $author_phids[$author_phid] = $author_phid; + } + $colors = array(); if ($blame_commits) { $epochs = array(); @@ -1113,6 +1131,7 @@ final class DiffusionBrowseController extends DiffusionController { // blame outputs. $commit_links = $this->renderCommitLinks($blame_commits, $handles); $revision_links = $this->renderRevisionLinks($revisions, $handles); + $author_links = $this->renderAuthorLinks($author_map, $handles); if ($this->coverage) { require_celerity_resource('differential-changeset-view-css'); @@ -1145,6 +1164,7 @@ final class DiffusionBrowseController extends DiffusionController { $revision_link = null; $commit_link = null; + $author_link = null; $before_link = null; $style = 'background: '.$line['color'].';'; @@ -1152,6 +1172,7 @@ final class DiffusionBrowseController extends DiffusionController { if ($identifier && !$line['duplicate']) { if (isset($commit_links[$identifier])) { $commit_link = $commit_links[$identifier]; + $author_link = $author_links[$author_map[$identifier]]; } if (isset($revision_map[$identifier])) { @@ -1185,6 +1206,7 @@ final class DiffusionBrowseController extends DiffusionController { $before_link); $object_links = array(); + $object_links[] = $author_link; $object_links[] = $commit_link; if ($revision_link) { $object_links[] = phutil_tag('span', array(), '/'); @@ -1772,6 +1794,33 @@ final class DiffusionBrowseController extends DiffusionController { return array($identifiers, $commits); } + private function renderAuthorLinks(array $authors, $handles) { + $links = array(); + + foreach ($authors as $phid) { + if (!strlen($phid)) { + // This means we couldn't identify an author for the commit or the + // revision. We just render a blank for alignment. + $style = null; + $href = null; + } else { + $src = $handles[$phid]->getImageURI(); + $style = 'background-image: url('.$src.');'; + $href = $handles[$phid]->getURI(); + } + + $links[$phid] = javelin_tag( + $href ? 'a' : 'span', + array( + 'class' => 'diffusion-author-link', + 'style' => $style, + 'href' => $href, + )); + } + + return $links; + } + private function renderCommitLinks(array $commits, $handles) { $links = array(); foreach ($commits as $identifier => $commit) { diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css index 9ade2d3022..06e7d74477 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-source.css @@ -66,7 +66,7 @@ .diffusion-rev-link a, .diffusion-rev-link span { - margin: 2px 8px 0; + margin: 0 8px 0 0; display: inline-block; } @@ -90,3 +90,13 @@ -ms-user-select: none; user-select: none; } + +.diffusion-rev-link .diffusion-author-link { + display: inline-block; + padding: 0; + margin: 2px 4px -4px 4px; + width: 16px; + height: 16px; + background-size: 100% 100%; + background-repeat: no-repeat; +} From a4b934cad2b520fb2de57a5f08eeffa6aff28ffe Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 31 Oct 2017 11:45:38 -0700 Subject: [PATCH 26/72] Clean up Differential draft mail behaviors Summary: Ref T2543. Fixes two relatively minor things: - When builds finish in Harbormaster, send mail "From" the author. - Set the `firstBroadcast` flag so that initial mail picks up earlier history (notably, the "reviewers" line). For now, I'm not setting `firstBroadcast` on explicit "Request Review" (but maybe we should), and not trying to deal with weird cases where you leave a bunch of comments on a draft. Those might be fine as-is or may get tweaked later. Test Plan: Created a revision with Harbormaster builds, ran builds, saw initial email come "From" the right user with more metadata. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18748 --- .../editor/DifferentialTransactionEditor.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 70c3272a83..79a582a43f 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -9,6 +9,7 @@ final class DifferentialTransactionEditor private $didExpandInlineState = false; private $hasReviewTransaction = false; private $affectedPaths; + private $firstBroadcast = false; public function getEditorApplicationClass() { return 'PhabricatorDifferentialApplication'; @@ -27,7 +28,7 @@ final class DifferentialTransactionEditor } public function isFirstBroadcast() { - return $this->getIsNewObject(); + return $this->firstBroadcast; } public function getDiffUpdateTransaction(array $xactions) { @@ -1449,11 +1450,13 @@ final class DifferentialTransactionEditor protected function getCustomWorkerState() { return array( 'changedPriorToCommitURI' => $this->changedPriorToCommitURI, + 'firstBroadcast' => $this->firstBroadcast, ); } protected function loadCustomWorkerState(array $state) { $this->changedPriorToCommitURI = idx($state, 'changedPriorToCommitURI'); + $this->firstBroadcast = idx($state, 'firstBroadcast'); return $this; } @@ -1566,6 +1569,19 @@ final class DifferentialTransactionEditor // natural and more useful. $author_phid = $object->getAuthorPHID(); + // Additionally, we change the acting PHID for the transaction set + // to the author if it isn't already a user so that mail comes from + // the natural author. + $acting_phid = $this->getActingAsPHID(); + $user_type = PhabricatorPeopleUserPHIDType::TYPECONST; + if (phid_get_type($acting_phid) != $user_type) { + $this->setActingAsPHID($author_phid); + } + + // Mark this as the first broadcast we're sending about the revision + // so mail can generate specially. + $this->firstBroadcast = true; + $xaction = $object->getApplicationTransactionTemplate() ->setAuthorPHID($author_phid) ->setTransactionType( From 80ebe401e5849799cbc5568f25cca927b7b1b621 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 31 Oct 2017 12:23:16 -0700 Subject: [PATCH 27/72] Tweak padding/spacing on Diffusion blame view for profile pictures Summary: Give profile images a little more space, fix "/" spacing, add a tooltip. Test Plan: {F5251205} Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18749 --- resources/celerity/map.php | 4 ++-- .../diffusion/controller/DiffusionBrowseController.php | 5 +++++ .../rsrc/css/application/diffusion/diffusion-source.css | 8 ++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b7cb976a16..c9e1a5b351 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -73,7 +73,7 @@ return array( 'rsrc/css/application/diffusion/diffusion-icons.css' => '0c15255e', 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', - 'rsrc/css/application/diffusion/diffusion-source.css' => 'd96b3f9f', + 'rsrc/css/application/diffusion/diffusion-source.css' => '5f35a3bd', 'rsrc/css/application/diffusion/diffusion.css' => '45727264', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', @@ -572,7 +572,7 @@ return array( 'diffusion-icons-css' => '0c15255e', 'diffusion-readme-css' => '419dd5b6', 'diffusion-repository-css' => 'ee6f20ec', - 'diffusion-source-css' => 'd96b3f9f', + 'diffusion-source-css' => '5f35a3bd', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index f4bd2bdebf..4985ac2bb7 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1815,6 +1815,11 @@ final class DiffusionBrowseController extends DiffusionController { 'class' => 'diffusion-author-link', 'style' => $style, 'href' => $href, + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $handles[$phid]->getName(), + 'align' => 'E', + ), )); } diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css index 06e7d74477..a2c67cf16d 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-source.css @@ -64,14 +64,14 @@ color: {$darkbluetext}; } -.diffusion-rev-link a, -.diffusion-rev-link span { +.diffusion-rev-link a { margin: 0 8px 0 0; display: inline-block; } .diffusion-rev-link span { - margin-right: -4px; + display: inline-block; + margin-right: 4px; margin-left: -4px; color: {$lightgreytext}; } @@ -94,7 +94,7 @@ .diffusion-rev-link .diffusion-author-link { display: inline-block; padding: 0; - margin: 2px 4px -4px 4px; + margin: 2px 6px -4px 8px; width: 16px; height: 16px; background-size: 100% 100%; From 2a7cdcf740ac4948b25a88fda3db9fb9d3f7337e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 31 Oct 2017 13:52:23 -0700 Subject: [PATCH 28/72] Fix an issue where the repository symbol index would incorrectly activate inside inline comments Summary: See PHI185. When looking at a revision, you can Command-Click (Mac) symbols to jump to their definitions (provided the symbol index has been built). Currently, the code works on any node inside the changeset list, so it activates when clicking links inside inline comments and opening them in a new window. To avoid this, don't activate if we're inside an inline comment. This technically prevents you from doing a symbol lookup on a symbol inside a codeblock inside an inline, but that seems fine/reasonable. Test Plan: Wrote `Dxxx` in an inline, command-clicked it. Before: got a symbol lookup. After: just a new tab with the revision. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18753 --- resources/celerity/map.php | 18 +++++++++--------- .../repository/repository-crossreference.js | 15 +++++++++++++-- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c9e1a5b351..30c04ffc92 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '4c79d74f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', - 'differential.pkg.js' => 'b71b8c5d', + 'differential.pkg.js' => 'ae6460e0', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -444,7 +444,7 @@ return array( 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', 'rsrc/js/application/releeph/releeph-request-state-change.js' => 'a0b57eb8', 'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f', - 'rsrc/js/application/repository/repository-crossreference.js' => 'e5339c43', + 'rsrc/js/application/repository/repository-crossreference.js' => '7fe9bc12', 'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e2e0a072', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08', 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f', @@ -690,7 +690,7 @@ return array( 'javelin-behavior-reorder-applications' => '76b9fc3e', 'javelin-behavior-reorder-columns' => 'e1d25dfb', 'javelin-behavior-reorder-profile-menu-items' => 'e2e0a072', - 'javelin-behavior-repository-crossreference' => 'e5339c43', + 'javelin-behavior-repository-crossreference' => '7fe9bc12', 'javelin-behavior-scrollbar' => '834a1173', 'javelin-behavior-search-reorder-queries' => 'e9581f08', 'javelin-behavior-select-content' => 'bf5374ef', @@ -1546,6 +1546,12 @@ return array( '7f243deb' => array( 'javelin-install', ), + '7fe9bc12' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-uri', + ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -2073,12 +2079,6 @@ return array( 'javelin-behavior', 'javelin-dom', ), - 'e5339c43' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-uri', - ), 'e5822781' => array( 'javelin-behavior', 'javelin-dom', diff --git a/webroot/rsrc/js/application/repository/repository-crossreference.js b/webroot/rsrc/js/application/repository/repository-crossreference.js index bfb849c759..138ce4bf3e 100644 --- a/webroot/rsrc/js/application/repository/repository-crossreference.js +++ b/webroot/rsrc/js/application/repository/repository-crossreference.js @@ -46,8 +46,19 @@ JX.behavior('repository-crossreference', function(config, statics) { if (!isSignalkey(e)) { return; } + + var target = e.getTarget(); + + try { + // If we're in an inline comment, don't link symbols. + if (JX.DOM.findAbove(target, 'div', 'differential-inline-comment')) { + return; + } + } catch (ex) { + // Continue if we're not inside an inline comment. + } + if (e.getType() === 'mouseover') { - var target = e.getTarget(); while (target !== document.body) { if (JX.DOM.isNode(target, 'span') && (target.className in class_map)) { @@ -58,7 +69,7 @@ JX.behavior('repository-crossreference', function(config, statics) { target = target.parentNode; } } else if (e.getType() === 'click') { - openSearch(e.getTarget(), lang); + openSearch(target, lang); } }); } From 6d36eb911309d4741ab26c7f7212b642826752b1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 1 Nov 2017 10:42:27 -0700 Subject: [PATCH 29/72] Denormalize Diff PHIDs onto Revisions Summary: Ref T12539. See PHI190. Currently, each Diff has a `revisionID`, but Revisions do not point at the current active diff. To find the active diff for a given revision, we need to issue a separate query. Furthermore, this query is inefficient for bulk loads: if we have a lot of revisions, we end up querying for all diff IDs for all those revisions first, then selecting the largest ones and querying again to get the actual diff objects. This strategy could likely be optimized but the query is a mess in any case. In several cases, it's useful to have the active diff PHID without needing to do a second query -- sometimes for convenience, and sometimes for performance. T12539 is an example of such a case: it would be nice to refine the bucketing logic (which only depends on active diff PHIDs), but it feels bad to make the page heavier to do it. For now, this is unused. I'll start using it to fix the bucketing issue, and then we can expand it gradually to address other performance/convenience issues. Test Plan: - Ran migrations, inspected database, saw sensible values. - Created a new revision, saw a sensible database value. - Updated an existing revision, saw database update properly. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12539 Differential Revision: https://secure.phabricator.com/D18756 --- .../autopatches/20171101.diff.01.active.sql | 2 ++ .../autopatches/20171101.diff.02.populate.php | 24 +++++++++++++++++++ .../editor/DifferentialTransactionEditor.php | 3 +-- .../storage/DifferentialRevision.php | 2 ++ 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 resources/sql/autopatches/20171101.diff.01.active.sql create mode 100644 resources/sql/autopatches/20171101.diff.02.populate.php diff --git a/resources/sql/autopatches/20171101.diff.01.active.sql b/resources/sql/autopatches/20171101.diff.01.active.sql new file mode 100644 index 0000000000..aee8c5aa13 --- /dev/null +++ b/resources/sql/autopatches/20171101.diff.01.active.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_differential.differential_revision + ADD activeDiffPHID VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20171101.diff.02.populate.php b/resources/sql/autopatches/20171101.diff.02.populate.php new file mode 100644 index 0000000000..b41b0aa51d --- /dev/null +++ b/resources/sql/autopatches/20171101.diff.02.populate.php @@ -0,0 +1,24 @@ +establishConnection('w'); +$diff_table = new DifferentialDiff(); + +foreach (new LiskMigrationIterator($table) as $revision) { + $revision_id = $revision->getID(); + + $diff_row = queryfx_one( + $conn, + 'SELECT phid FROM %T WHERE revisionID = %d ORDER BY id DESC LIMIT 1', + $diff_table->getTableName(), + $revision_id); + + if ($diff_row) { + queryfx( + $conn, + 'UPDATE %T SET activeDiffPHID = %s WHERE id = %d', + $table->getTableName(), + $diff_row['phid'], + $revision_id); + } +} diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 79a582a43f..3e6cca1edb 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -138,8 +138,7 @@ final class DifferentialTransactionEditor $object->setRepositoryPHID($diff->getRepositoryPHID()); } $object->attachActiveDiff($diff); - - // TODO: Update the `diffPHID` once we add that. + $object->setActiveDiffPHID($diff->getPHID()); return; } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 467e503f6f..61f934f13b 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -35,6 +35,8 @@ final class DifferentialRevision extends DifferentialDAO protected $mailKey; protected $branchName; protected $repositoryPHID; + protected $activeDiffPHID; + protected $viewPolicy = PhabricatorPolicies::POLICY_USER; protected $editPolicy = PhabricatorPolicies::POLICY_USER; protected $properties = array(); From 6ecdadb76a955854875fa25515fba266bb34cfa9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 1 Nov 2017 10:54:23 -0700 Subject: [PATCH 30/72] After "Request Review", move revisions with voided "Accepts" into "Ready to Review", not "Waiting on Other Reviewers" Summary: Depends on D18756. Fixes T12539. See PHI190. Currently, when this occurs: - Alice accepts. - Bailey requests review. - Alice views her dashboard. ...the revision appears in "Waiting on Other Reviewers" (regardless of whether other reviewers actually exist or not). Instead, ignore these voided/non-current accepts and let the revisions appear in "Ready to Review", which is more natural. Test Plan: Went through the steps above. On `master`, saw revision in "Waiting on Other Reviewers". After patch, saw it in "Ready to Review". Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12539 Differential Revision: https://secure.phabricator.com/D18757 --- ...fferentialRevisionRequiredActionResultBucket.php | 10 +++++++++- .../query/DifferentialRevisionResultBucket.php | 13 ++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php index f3971f8571..5f5f4008db 100644 --- a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php @@ -123,6 +123,14 @@ final class DifferentialRevisionRequiredActionResultBucket $reviewing = array( DifferentialReviewerStatus::STATUS_ADDED, DifferentialReviewerStatus::STATUS_COMMENTED, + + // If an author has used "Request Review" to put an accepted revision + // back into the "Needs Review" state, include "Accepted" reviewers + // whose reviews have been voided in the "Should Review" bucket. + + // If we don't do this, they end up in "Waiting on Other Reviewers", + // even if there are no other reviewers. + DifferentialReviewerStatus::STATUS_ACCEPTED, ); $reviewing = array_fuse($reviewing); @@ -130,7 +138,7 @@ final class DifferentialRevisionRequiredActionResultBucket $results = array(); foreach ($objects as $key => $object) { - if (!$this->hasReviewersWithStatus($object, $phids, $reviewing)) { + if (!$this->hasReviewersWithStatus($object, $phids, $reviewing, false)) { continue; } diff --git a/src/applications/differential/query/DifferentialRevisionResultBucket.php b/src/applications/differential/query/DifferentialRevisionResultBucket.php index 54705649eb..a0769ac349 100644 --- a/src/applications/differential/query/DifferentialRevisionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionResultBucket.php @@ -53,7 +53,8 @@ abstract class DifferentialRevisionResultBucket protected function hasReviewersWithStatus( DifferentialRevision $revision, array $phids, - array $statuses) { + array $statuses, + $current = null) { foreach ($revision->getReviewers() as $reviewer) { $reviewer_phid = $reviewer->getReviewerPHID(); @@ -66,6 +67,16 @@ abstract class DifferentialRevisionResultBucket continue; } + if ($current !== null) { + if ($status == DifferentialReviewerStatus::STATUS_ACCEPTED) { + $diff_phid = $revision->getActiveDiffPHID(); + $is_current = $reviewer->isAccepted($diff_phid); + if ($is_current !== $current) { + continue; + } + } + } + return true; } From 03d059dd26e27077de6c58847926cfe9d2e6590b Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 1 Nov 2017 11:08:19 -0700 Subject: [PATCH 31/72] Don't include resigned reviewers in the Differential "To" list Summary: Ref T12689. See PHI178. This isn't a complete solution (you may still get mailed via packages/projects) but should fix the obvious issue, where "Resigned" reviewers are incorrectly always sent mail directly. Test Plan: Had Alice resign, interacted as Bailey, no mail to Alice. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12689 Differential Revision: https://secure.phabricator.com/D18758 --- .../differential/editor/DifferentialTransactionEditor.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 3e6cca1edb..26c440e866 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -630,6 +630,10 @@ final class DifferentialTransactionEditor $phids = array(); $phids[] = $object->getAuthorPHID(); foreach ($object->getReviewers() as $reviewer) { + if ($reviewer->isResigned()) { + continue; + } + $phids[] = $reviewer->getReviewerPHID(); } return $phids; From cb98b60033260998a51821f71add07285fa0d3ef Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 6 Nov 2017 10:25:37 -0800 Subject: [PATCH 32/72] Fill in some straightforward Maniphest transactions for `transaction.search` Summary: See PHI197. Populates "status" transactions and a few other obvious types where there's no security/performance/payload/formatting issue I can come up with. The names here are the same as the names for editing with `maniphest.edit`. Test Plan: Used `transaction.search` to retrieve transactions of all new types. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18761 --- .../DifferentialRevisionStatusTransaction.php | 6 +++--- .../xaction/ManiphestTaskDescriptionTransaction.php | 10 ++++++++++ .../xaction/ManiphestTaskOwnerTransaction.php | 10 ++++++++++ .../xaction/ManiphestTaskPointsTransaction.php | 12 ++++++++++++ .../xaction/ManiphestTaskStatusTransaction.php | 11 +++++++++++ .../xaction/ManiphestTaskTitleTransaction.php | 11 +++++++++++ 6 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php b/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php index 615ce38bcf..9c8b3767df 100644 --- a/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php @@ -74,10 +74,10 @@ final class DifferentialRevisionStatusTransaction return 'status'; } - public function getFieldValuesForConduit($object, $data) { + public function getFieldValuesForConduit($xaction, $data) { return array( - 'old' => $object->getOldValue(), - 'new' => $object->getNewValue(), + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), ); } diff --git a/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php index 009327ed9b..fec1a87604 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php @@ -57,5 +57,15 @@ final class ManiphestTaskDescriptionTransaction return $changes; } + public function getTransactionTypeForConduit($xaction) { + return 'description'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } } diff --git a/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php index d510fe8fbc..caaf84f542 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php @@ -154,5 +154,15 @@ final class ManiphestTaskOwnerTransaction } + public function getTransactionTypeForConduit($xaction) { + return 'owner'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } } diff --git a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php index 8953664f27..5a69199874 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php @@ -107,4 +107,16 @@ final class ManiphestTaskPointsTransaction return $value; } + public function getTransactionTypeForConduit($xaction) { + return 'points'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } + + } diff --git a/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php index 5e1cd44611..a3780e81b9 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php @@ -229,4 +229,15 @@ final class ManiphestTaskStatusTransaction } + public function getTransactionTypeForConduit($xaction) { + return 'status'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } + } diff --git a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php index 506817b0fc..e4ec2a132f 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php @@ -72,4 +72,15 @@ final class ManiphestTaskTitleTransaction return $errors; } + public function getTransactionTypeForConduit($xaction) { + return 'title'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } + } From cc865e549bcddb68544c6ae7415c4e19a7db095d Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 6 Nov 2017 10:38:43 -0800 Subject: [PATCH 33/72] Provide revision parent/child edges in `edge.search`, and more information in `differential.revision.search` Summary: See PHI195. This bulks out these API methods since all the requests are pretty straightforward. Test Plan: Ran `edge.search` and `differential.revision.search`. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18762 --- ...entialRevisionDependedOnByRevisionEdgeType.php | 14 ++++++++++++++ ...ferentialRevisionDependsOnRevisionEdgeType.php | 13 +++++++++++++ .../differential/storage/DifferentialRevision.php | 15 +++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php b/src/applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php index 02a24effcf..a6940b536e 100644 --- a/src/applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php +++ b/src/applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php @@ -13,6 +13,20 @@ final class DifferentialRevisionDependedOnByRevisionEdgeType return true; } + public function getConduitKey() { + return 'revision.child'; + } + + public function getConduitName() { + return pht('Revision Has Child'); + } + + public function getConduitDescription() { + return pht( + 'The source revision makes changes required by the destination '. + 'revision.'); + } + public function getTransactionAddString( $actor, $add_count, diff --git a/src/applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php b/src/applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php index 4613ad8a34..e826f554c6 100644 --- a/src/applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php +++ b/src/applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php @@ -17,6 +17,19 @@ final class DifferentialRevisionDependsOnRevisionEdgeType return true; } + public function getConduitKey() { + return 'revision.parent'; + } + + public function getConduitName() { + return pht('Revision Has Parent'); + } + + public function getConduitDescription() { + return pht( + 'The source revision depends on changes in the destination revision.'); + } + public function getTransactionAddString( $actor, $add_count, diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 61f934f13b..bf4bec0abc 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -986,6 +986,18 @@ final class DifferentialRevision extends DifferentialDAO ->setKey('status') ->setType('map') ->setDescription(pht('Information about revision status.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('repositoryPHID') + ->setType('phid?') + ->setDescription(pht('Revision repository PHID.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('diffPHID') + ->setType('phid') + ->setDescription(pht('Active diff PHID.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('summary') + ->setType('string') + ->setDescription(pht('Revision summary.')), ); } @@ -1002,6 +1014,9 @@ final class DifferentialRevision extends DifferentialDAO 'title' => $this->getTitle(), 'authorPHID' => $this->getAuthorPHID(), 'status' => $status_info, + 'repositoryPHID' => $this->getRepositoryPHID(), + 'diffPHID' => $this->getActiveDiffPHID(), + 'summary' => $this->getSummary(), ); } From 587faa6f677c8d7e914051e5307ade00e2bf43b0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 6 Nov 2017 10:53:14 -0800 Subject: [PATCH 34/72] Remove some defunct old-style transaction policy checks Summary: Ref PHI193. This method of enforcing policy checks is now (mostly) obsolete, and they're generally checked at the Controller/API level instead. Notably, this method does not call `adjustObjectForPolicyChecks(...)` properly, so it can not handle special cases like "creating a project and taking its newly created members into account" for object policies like "Project Members". Just remove these checks, which are redundant with checks elsewhere. Test Plan: - Set Project application default edit policy to "Administrators and Project Members". - Tried to create a project as a non-administrator, adding myself. - Before patch: policy fatal on a VOID object (the project with no PHID generated yet). - After patch: object created properly. Got a sensible policy error if I didn't include myself as a member. - Also verified that other edit rules are still enforced/respected (I can't edit stuff I shouldn't be able to edit). - There's at least a bit of unit test coverage of this, too, which I updated to work via API (which hits the new broad capability checks) instead of via low-level transactions (which enforce only a subset of policy operations now). Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18763 --- .../PhabricatorProjectCoreTestCase.php | 20 ++++++++++++------- .../PhabricatorProjectTransactionEditor.php | 10 ---------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index d9356357b3..f34f224521 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -146,8 +146,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $user = $this->createUser(); $user->save(); - $user2 = $this->createUser(); - $user2->save(); + $user->setAllowInlineCacheGeneration(true); $proj = $this->createProject($user); @@ -1289,12 +1288,19 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $new_name = $proj->getName().' '.mt_rand(); - $xaction = new PhabricatorProjectTransaction(); - $xaction->setTransactionType( - PhabricatorProjectNameTransaction::TRANSACTIONTYPE); - $xaction->setNewValue($new_name); + $params = array( + 'objectIdentifier' => $proj->getID(), + 'transactions' => array( + array( + 'type' => 'name', + 'value' => $new_name, + ), + ), + ); - $this->applyTransactions($proj, $user, array($xaction)); + id(new ConduitCall('project.edit', $params)) + ->setUser($user) + ->execute(); return true; } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index adf4a3138b..2764ce6322 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -120,16 +120,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: - case PhabricatorProjectStatusTransaction::TRANSACTIONTYPE: - case PhabricatorProjectImageTransaction::TRANSACTIONTYPE: - case PhabricatorProjectIconTransaction::TRANSACTIONTYPE: - case PhabricatorProjectColorTransaction::TRANSACTIONTYPE: - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_EDIT); - return; case PhabricatorProjectLockTransaction::TRANSACTIONTYPE: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), From 12e6106a5907c5160dfca8b997e7b6796d707260 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 6 Nov 2017 11:51:59 -0800 Subject: [PATCH 35/72] Refine bucketing and display rules for voided "Accepts" in Differential Summary: See PHI190. This clarifies the ruleset a bit: - If you accepted, then the author used "Request Review" explicitly, we now show "Accepted Earlier" instead of "Accepted" in the "Reviewers" list on the main revision page. This makes it sligthly more clear why the revision is back in your review queue without picking through the transaction log. - Instead of moving all non-current accepts into "Ready to Review", move only voided accepts into "Ready to Review". This stops us from pulling older accepts which haven't been voided (which could have been incorrectly pulled) and correctly pulls older, voided accepts from before an update (for example: accept, then request review, then update) and generally aligns better with intent/expectation. Test Plan: - Accepted, requested review. - Saw reviewer as "Accepted Earlier". - Saw review in "Ready to Review" bucket. - Accepted, updated (with sticky accept). - Saw reviewer as "Accepted Prior Diff". - Saw review as "Waiting on Authors". Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18764 --- .../DifferentialRevisionRequiredActionResultBucket.php | 2 +- .../query/DifferentialRevisionResultBucket.php | 9 ++++----- .../differential/view/DifferentialReviewersView.php | 10 ++++++++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php index 5f5f4008db..711f70afb3 100644 --- a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php @@ -138,7 +138,7 @@ final class DifferentialRevisionRequiredActionResultBucket $results = array(); foreach ($objects as $key => $object) { - if (!$this->hasReviewersWithStatus($object, $phids, $reviewing, false)) { + if (!$this->hasReviewersWithStatus($object, $phids, $reviewing, true)) { continue; } diff --git a/src/applications/differential/query/DifferentialRevisionResultBucket.php b/src/applications/differential/query/DifferentialRevisionResultBucket.php index a0769ac349..c5cc5c0e6c 100644 --- a/src/applications/differential/query/DifferentialRevisionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionResultBucket.php @@ -54,7 +54,7 @@ abstract class DifferentialRevisionResultBucket DifferentialRevision $revision, array $phids, array $statuses, - $current = null) { + $include_voided = null) { foreach ($revision->getReviewers() as $reviewer) { $reviewer_phid = $reviewer->getReviewerPHID(); @@ -67,11 +67,10 @@ abstract class DifferentialRevisionResultBucket continue; } - if ($current !== null) { + if ($include_voided !== null) { if ($status == DifferentialReviewerStatus::STATUS_ACCEPTED) { - $diff_phid = $revision->getActiveDiffPHID(); - $is_current = $reviewer->isAccepted($diff_phid); - if ($is_current !== $current) { + $is_voided = (bool)$reviewer->getVoidedPHID(); + if ($is_voided !== $include_voided) { continue; } } diff --git a/src/applications/differential/view/DifferentialReviewersView.php b/src/applications/differential/view/DifferentialReviewersView.php index 33aad25289..f88669e539 100644 --- a/src/applications/differential/view/DifferentialReviewersView.php +++ b/src/applications/differential/view/DifferentialReviewersView.php @@ -47,6 +47,7 @@ final class DifferentialReviewersView extends AphrontView { $action_phid = $reviewer->getLastActionDiffPHID(); $is_current_action = $this->isCurrent($action_phid); + $is_voided = (bool)$reviewer->getVoidedPHID(); $comment_phid = $reviewer->getLastCommentDiffPHID(); $is_current_comment = $this->isCurrent($comment_phid); @@ -86,7 +87,7 @@ final class DifferentialReviewersView extends AphrontView { break; case DifferentialReviewerStatus::STATUS_ACCEPTED: - if ($is_current_action) { + if ($is_current_action && !$is_voided) { $icon = PHUIStatusItemView::ICON_ACCEPT; $color = 'green'; if ($authority_name !== null) { @@ -97,7 +98,12 @@ final class DifferentialReviewersView extends AphrontView { } else { $icon = 'fa-check-circle-o'; $color = 'bluegrey'; - if ($authority_name !== null) { + + if (!$is_current_action && $is_voided) { + // The reviewer accepted the revision, but later the author + // used "Request Review" to request an updated review. + $label = pht('Accepted Earlier'); + } else if ($authority_name !== null) { $label = pht('Accepted Prior Diff (by %s)', $authority_name); } else { $label = pht('Accepted Prior Diff'); From 759c757264c84b35abe603026a29191a2ddf3def Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 7 Nov 2017 05:12:11 -0800 Subject: [PATCH 36/72] Include "Draft" revisions in Differential legacy status queries Summary: See PHI199. Ref T2543. When you run a RevisionQuery with a legacy status constraint (via `differential.query`), we currently don't match "Draft" revisions. Use the actual complete map from `DifferentialRevisionStatus` instead of hard coding the status list so "Draft" is included. Test Plan: - Ran `differential.query` with `ids` and `status` for a draft revision. - Before patch: revision not returned in results. - After patch: revision returned in results. (Note that it returns as "Needs Review", for compatibility.) Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18765 --- .../constants/DifferentialLegacyQuery.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/applications/differential/constants/DifferentialLegacyQuery.php b/src/applications/differential/constants/DifferentialLegacyQuery.php index 26d2c4aee2..ff7944afe9 100644 --- a/src/applications/differential/constants/DifferentialLegacyQuery.php +++ b/src/applications/differential/constants/DifferentialLegacyQuery.php @@ -32,14 +32,7 @@ final class DifferentialLegacyQuery } private static function getMap() { - $all = array( - DifferentialRevisionStatus::NEEDS_REVIEW, - DifferentialRevisionStatus::NEEDS_REVISION, - DifferentialRevisionStatus::CHANGES_PLANNED, - DifferentialRevisionStatus::ACCEPTED, - DifferentialRevisionStatus::PUBLISHED, - DifferentialRevisionStatus::ABANDONED, - ); + $all = array_keys(DifferentialRevisionStatus::getAll()); $open = array(); $closed = array(); @@ -61,6 +54,9 @@ final class DifferentialLegacyQuery ), self::STATUS_NEEDS_REVIEW => array( DifferentialRevisionStatus::NEEDS_REVIEW, + + // For legacy callers, "Draft" is treated as "Needs Review". + DifferentialRevisionStatus::DRAFT, ), self::STATUS_NEEDS_REVISION => array( DifferentialRevisionStatus::NEEDS_REVISION, From a7921a4448093d00defa8bd18f35b8c8f8bf3314 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 9 Nov 2017 07:23:40 -0800 Subject: [PATCH 37/72] Filter and reject "--config" and "--debugger" flags to Mercurial in any position Summary: Ref T13012. These flags can be exploited by attackers to execute code remotely. See T13012 for discussion and context. Additionally, harden some Mercurial commands where possible (by using additional quoting or embedding arguments in other constructs) so they resist these flags and behave properly when passed arguments with these values. Test Plan: - Added unit tests. - Verified "--config" and "--debugger" commands are rejected. - Verified more commands now work properly even with branches and files named `--debugger`, although not all of them do. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13012 Differential Revision: https://secure.phabricator.com/D18769 --- src/__phutil_library_map__.php | 2 + .../DiffusionExistsQueryConduitAPIMethod.php | 2 +- .../DiffusionHistoryQueryConduitAPIMethod.php | 18 +++--- ...sionMergedCommitsQueryConduitAPIMethod.php | 2 +- .../DiffusionSearchQueryConduitAPIMethod.php | 2 +- ...ffusionMercurialFlagInjectionException.php | 3 + .../DiffusionMercurialCommandEngine.php | 21 +++++++ .../DiffusionCommandEngineTestCase.php | 56 +++++++++++++++++++ .../DiffusionMercurialRawDiffQuery.php | 4 +- 9 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 src/applications/diffusion/exception/DiffusionMercurialFlagInjectionException.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7180a7ae04..424d72dc72 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -768,6 +768,7 @@ phutil_register_library_map(array( 'DiffusionMercurialBlameQuery' => 'applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php', 'DiffusionMercurialCommandEngine' => 'applications/diffusion/protocol/DiffusionMercurialCommandEngine.php', 'DiffusionMercurialFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php', + 'DiffusionMercurialFlagInjectionException' => 'applications/diffusion/exception/DiffusionMercurialFlagInjectionException.php', 'DiffusionMercurialRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php', 'DiffusionMercurialRequest' => 'applications/diffusion/request/DiffusionMercurialRequest.php', 'DiffusionMercurialResponse' => 'applications/diffusion/response/DiffusionMercurialResponse.php', @@ -5814,6 +5815,7 @@ phutil_register_library_map(array( 'DiffusionMercurialBlameQuery' => 'DiffusionBlameQuery', 'DiffusionMercurialCommandEngine' => 'DiffusionCommandEngine', 'DiffusionMercurialFileContentQuery' => 'DiffusionFileContentQuery', + 'DiffusionMercurialFlagInjectionException' => 'Exception', 'DiffusionMercurialRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionMercurialRequest' => 'DiffusionRequest', 'DiffusionMercurialResponse' => 'AphrontResponse', diff --git a/src/applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php index 4280230002..2d4a221171 100644 --- a/src/applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php @@ -47,7 +47,7 @@ final class DiffusionExistsQueryConduitAPIMethod $commit = $request->getValue('commit'); list($err, $stdout) = $repository->execLocalCommand( 'id --rev %s', - $commit); + hgsprintf('%s', $commit)); return !$err; } diff --git a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php index 200be8567b..e36c0a124e 100644 --- a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php @@ -123,21 +123,23 @@ final class DiffusionHistoryQueryConduitAPIMethod // branches). if (strlen($path)) { - $path_arg = csprintf('-- %s', $path); - $branch_arg = ''; + $path_arg = csprintf('%s', $path); + $revset_arg = hgsprintf( + 'reverse(ancestors(%s))', + $commit_hash); } else { $path_arg = ''; - // NOTE: --branch used to be called --only-branch; use -b for - // compatibility. - $branch_arg = csprintf('-b %s', $drequest->getBranch()); + $revset_arg = hgsprintf( + 'branch(%s) and reverse(ancestors(%s))', + $drequest->getBranch(), + $commit_hash); } list($stdout) = $repository->execxLocalCommand( - 'log --debug --template %s --limit %d %C --rev %s %C', + 'log --debug --template %s --limit %d --rev %s -- %C', '{node};{parents}\\n', ($offset + $limit), // No '--skip' in Mercurial. - $branch_arg, - hgsprintf('reverse(ancestors(%s))', $commit_hash), + $revset_arg, $path_arg); $stdout = DiffusionMercurialCommandEngine::filterMercurialDebugOutput( diff --git a/src/applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php index 9d2d6caadc..79587a2e5e 100644 --- a/src/applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php @@ -77,7 +77,7 @@ final class DiffusionMergedCommitsQueryConduitAPIMethod list($parents) = $repository->execxLocalCommand( 'parents --template=%s --rev %s', '{node}\\n', - $commit); + hgsprintf('%s', $commit)); $parents = explode("\n", trim($parents)); if (count($parents) < 2) { diff --git a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php index 389f931c0f..af973f102d 100644 --- a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php @@ -97,7 +97,7 @@ final class DiffusionSearchQueryConduitAPIMethod $results = array(); $future = $repository->getLocalCommandFuture( - 'grep --rev %s --print0 --line-number %s %s', + 'grep --rev %s --print0 --line-number -- %s %s', hgsprintf('ancestors(%s)', $drequest->getStableCommit()), $grep, $path); diff --git a/src/applications/diffusion/exception/DiffusionMercurialFlagInjectionException.php b/src/applications/diffusion/exception/DiffusionMercurialFlagInjectionException.php new file mode 100644 index 0000000000..e60e735816 --- /dev/null +++ b/src/applications/diffusion/exception/DiffusionMercurialFlagInjectionException.php @@ -0,0 +1,3 @@ +splitArguments($test_command); + + foreach ($test_args as $test_arg) { + if (preg_match('/^--(config|debugger)/i', $test_arg)) { + throw new DiffusionMercurialFlagInjectionException( + pht( + 'Mercurial command appears to contain unsafe injected "--config" '. + 'or "--debugger": %s', + $test_command)); + } + } + // NOTE: Here, and in Git and Subversion, we override the SSH command even // if the repository does not use an SSH remote, since our SSH wrapper // defuses an attack against older versions of Mercurial, Git and diff --git a/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php b/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php index 1df3ea3522..50b7d4a5e2 100644 --- a/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php +++ b/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php @@ -123,6 +123,62 @@ final class DiffusionCommandEngineTestCase extends PhabricatorTestCase { 'argv' => 'xyz', 'protocol' => 'https', )); + + // Test that filtering defenses for "--config" and "--debugger" flag + // injections in Mercurial are functional. See T13012. + + $caught = null; + try { + $this->assertCommandEngineFormat( + '', + array(), + array( + 'vcs' => $type_hg, + 'argv' => '--debugger', + )); + } catch (DiffusionMercurialFlagInjectionException $ex) { + $caught = $ex; + } + + $this->assertTrue( + ($caught instanceof DiffusionMercurialFlagInjectionException), + pht('Expected "--debugger" injection in Mercurial to throw.')); + + + $caught = null; + try { + $this->assertCommandEngineFormat( + '', + array(), + array( + 'vcs' => $type_hg, + 'argv' => '--config=x', + )); + } catch (DiffusionMercurialFlagInjectionException $ex) { + $caught = $ex; + } + + $this->assertTrue( + ($caught instanceof DiffusionMercurialFlagInjectionException), + pht('Expected "--config" injection in Mercurial to throw.')); + + $caught = null; + try { + $this->assertCommandEngineFormat( + '', + array(), + array( + 'vcs' => $type_hg, + 'argv' => (string)csprintf('%s', '--config=x'), + )); + } catch (DiffusionMercurialFlagInjectionException $ex) { + $caught = $ex; + } + + $this->assertTrue( + ($caught instanceof DiffusionMercurialFlagInjectionException), + pht('Expected quoted "--config" injection in Mercurial to throw.')); + } private function assertCommandEngineFormat( diff --git a/src/applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php b/src/applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php index 39eed01226..9edcbb6380 100644 --- a/src/applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php +++ b/src/applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php @@ -16,14 +16,14 @@ final class DiffusionMercurialRawDiffQuery extends DiffusionRawDiffQuery { // If `$commit` has no parents (usually because it's the first commit // in the repository), we want to diff against `null`. This revset will // do that for us automatically. - $against = '('.$commit.'^ or null)'; + $against = hgsprintf('(%s^ or null)', $commit); } $future = $repository->getLocalCommandFuture( 'diff -U %d --git --rev %s --rev %s -- %s', $this->getLinesOfContext(), $against, - $commit, + hgsprintf('%s', $commit), $path); return $future; From 314e7266c3e5b071cb6e916d28c21cb143d6e013 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 14 Nov 2017 12:06:24 -0800 Subject: [PATCH 38/72] Restore "Summary" and "Test Plan" to initial mail for non-draft configurations Summary: See PHI210. Ref T2543. Currently, we don't set this flag if you have prototypes off and don't get any of the new draft stuff, so the mail drops some of the details it is supposed to have. Test Plan: Disabled prototypes, created a revision, saw summary / test plan in the initial mail. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18771 --- .../differential/editor/DifferentialTransactionEditor.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 26c440e866..7cea29359f 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1612,6 +1612,13 @@ final class DifferentialTransactionEditor $xactions[] = $xaction; } + } else { + // If this revision is being created into some state other than "Draft", + // this is the first broadcast and should include sections like "SUMMARY" + // and "TEST PLAN". + if ($this->getIsNewObject()) { + $this->firstBroadcast = true; + } } return $xactions; From 95a5b41b0ba2751bccdec0725b84561ba7000d09 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 14 Nov 2017 12:17:31 -0800 Subject: [PATCH 39/72] Allow "arc diff --draft" to create revisions as drafts more forcefully Summary: Depends on D18771. See PHI206. Currently, `arc diff --draft` only holds revisions in draft mode: it doesn't put them into draft mode if the install isn't configured to use draft mode. Instead, make it a bit more forceful so that `arc diff --draft` can create into draft mode explicitly even if protoypes are off. This aligns with expection a little more clearly. Test Plan: Ran `arc diff --draft` with prototypes off, got a revision held in draft mode. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18772 --- .../xaction/DifferentialRevisionHoldDraftTransaction.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php b/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php index 5bc257ab62..2562f18209 100644 --- a/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php @@ -16,6 +16,15 @@ final class DifferentialRevisionHoldDraftTransaction public function applyInternalEffects($object, $value) { $object->setHoldAsDraft($value); + + // If draft isn't the default state but we're creating a new revision + // and holding it as a draft, put it in draft mode. See PHI206. + // TODO: This can probably be removed once Draft is the universal default. + if ($this->isNewObject()) { + if ($object->isNeedsReview()) { + $object->setModernRevisionStatus(DifferentialRevisionStatus::DRAFT); + } + } } public function getTitle() { From a1f12b4ac7af59216b4a0feb1321542486b8be64 Mon Sep 17 00:00:00 2001 From: Mukunda Modell Date: Tue, 14 Nov 2017 17:03:16 -0600 Subject: [PATCH 40/72] Specify a null behavior for the callsign sort column. Summary: Fixes paging on the Diffusion Repository List. PhabricatorRepositoryQuery needs to specify a behavior for `null` on the OderableColumns definition for the `callsign` column. See https://phabricator.wikimedia.org/T180457 Test Plan: 1. On an instance with more than 100 repositories * some of which are missing a callsign 2. Attempt to sort by callsign. 3. See the sorted results Previously: 3. Exception: "Column "0" has null value, but does not specify a null behavior." Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18773 --- src/applications/repository/query/PhabricatorRepositoryQuery.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index ffa9fa1b59..035693ff75 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -425,6 +425,7 @@ final class PhabricatorRepositoryQuery 'type' => 'string', 'unique' => true, 'reverse' => true, + 'null' => 'tail', ), 'name' => array( 'table' => 'r', From 3700bcb638def188008839683feecd7b68729173 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 15 Nov 2017 09:21:52 -0800 Subject: [PATCH 41/72] Warn and prevent 1-up/2-up switch in Differential if the user is editing an inline Summary: See PHI180. Currently, if you begin creating or editing an inline and then swap display modes (for example, with "View Unified"), your edit is lost. Persisting the editor state is complicated and this is very rare, so just prevent the action and warn the user instead. Also make the warning persist for a little longer since a few of the messages, including this one, take a couple seconds to read now. Test Plan: - Edited a comment, tried to swap display modes, got a warning. - Swapped display modes normally with no comment being edited. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18774 --- resources/celerity/map.php | 14 +++++++------- .../view/DifferentialChangesetListView.php | 3 +++ .../js/application/diff/DiffChangesetList.js | 19 ++++++++++++++++++- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 30c04ffc92..78ae8bd461 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '4c79d74f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', - 'differential.pkg.js' => 'ae6460e0', + 'differential.pkg.js' => '500a75c5', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -396,7 +396,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '99abf4cd', - 'rsrc/js/application/diff/DiffChangesetList.js' => '8f1cd52c', + 'rsrc/js/application/diff/DiffChangesetList.js' => '3b77efdd', 'rsrc/js/application/diff/DiffInline.js' => 'e83d28f3', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -775,7 +775,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '99abf4cd', - 'phabricator-diff-changeset-list' => '8f1cd52c', + 'phabricator-diff-changeset-list' => '3b77efdd', 'phabricator-diff-inline' => 'e83d28f3', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1137,6 +1137,10 @@ return array( 'javelin-dom', 'javelin-magical-init', ), + '3b77efdd' => array( + 'javelin-install', + 'phuix-button-view', + ), '3cb0b2fc' => array( 'javelin-behavior', 'javelin-dom', @@ -1610,10 +1614,6 @@ return array( '8e1baf68' => array( 'phui-button-css', ), - '8f1cd52c' => array( - 'javelin-install', - 'phuix-button-view', - ), '8f29b364' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 307d424b0e..59f73b5465 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -301,6 +301,9 @@ final class DifferentialChangesetListView extends AphrontView { 'Hide or show all inline comments.' => pht('Hide or show all inline comments.'), + + 'Finish editing inline comments before changing display modes.' => + pht('Finish editing inline comments before changing display modes.'), ), )); diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 3f47f1ed35..ec0270ac12 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -465,7 +465,7 @@ JX.install('DiffChangesetList', { new JX.Notification() .setContent(message) .alterClassName('jx-notification-alert', true) - .setDuration(1000) + .setDuration(3000) .show(); }, @@ -691,6 +691,7 @@ JX.install('DiffChangesetList', { 'div', 'differential-changeset'); + var changeset_list = this; var changeset = this.getChangesetForNode(node); var menu = new JX.PHUIXDropdownMenu(button); @@ -738,6 +739,22 @@ JX.install('DiffChangesetList', { var up_item = new JX.PHUIXActionView() .setHandler(function(e) { if (changeset.isLoaded()) { + + // Don't let the user swap display modes if a comment is being + // edited, since they might lose their work. See PHI180. + var inlines = changeset.getInlines(); + for (var ii = 0; ii < inlines.length; ii++) { + if (inlines[ii].isEditing()) { + changeset_list._warnUser( + pht( + 'Finish editing inline comments before changing display ' + + 'modes.')); + e.prevent(); + menu.close(); + return; + } + } + var renderer = changeset.getRenderer(); if (renderer == '1up') { renderer = '2up'; From bea45e90d3240ac575f70d2ab46aef3d9c9b8634 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 15 Nov 2017 11:28:47 -0800 Subject: [PATCH 42/72] Add yaml files to differential.whitespace-matters Summary: Whitespace has semantic meaning for yaml files, so we shouldn't suppress whitespace-only lines of diff by default. Test Plan: Edited local config to include yaml files, saw expected whitespace changes. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18775 --- .../differential/config/PhabricatorDifferentialConfigOptions.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php index 7ff886664f..0d00207b43 100644 --- a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php +++ b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php @@ -86,6 +86,7 @@ EOHELP array( '/\.py$/', '/\.l?hs$/', + '/\.ya?ml$/', )) ->setDescription( pht( From d2cff6a2cf01396f6337edfadd1f7df7cce1277d Mon Sep 17 00:00:00 2001 From: Alex Vandiver Date: Thu, 16 Nov 2017 00:43:35 -0800 Subject: [PATCH 43/72] Transcode the HTML part of incoming email into UTF-8 as well Summary: D1093 did this for just the text/plain part of incoming email. Most text/html parts choose to either use entity encoding //or// are already UTF-8, thus obviating the need to transcode the HTML part. However, this is not always the case, and leads to dropped messages, by way of: ``` EXCEPTION: (Exception) Failed to JSON encode value (#5: Malformed UTF-8 characters, possibly incorrectly encoded): Dictionary value at key "html" is not valid UTF8, and cannot be JSON encoded: [snip HTML part of message content]``` Generalize the charset transcoding to not apply to just the text/plain part, but both text/plain and text/html parts. Test Plan: Fed in a Windows-1252-encoded text/html part with 0x92 bytes in it; verified that $content only contained valid UTF-8 after this change. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D18776 --- scripts/mail/mail_handler.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/scripts/mail/mail_handler.php b/scripts/mail/mail_handler.php index 2ff23adb0f..b76b3910df 100755 --- a/scripts/mail/mail_handler.php +++ b/scripts/mail/mail_handler.php @@ -35,16 +35,19 @@ $args->parse( $parser = new MimeMailParser(); $parser->setText(file_get_contents('php://stdin')); -$text_body = $parser->getMessageBody('text'); - -$text_body_headers = $parser->getMessageBodyHeaders('text'); -$content_type = idx($text_body_headers, 'content-type'); -if ( - !phutil_is_utf8($text_body) && - (preg_match('/charset="(.*?)"/', $content_type, $matches) || - preg_match('/charset=(\S+)/', $content_type, $matches)) -) { - $text_body = phutil_utf8_convert($text_body, 'UTF-8', $matches[1]); +$content = array(); +foreach (array('text', 'html') as $part) { + $part_body = $parser->getMessageBody($part); + $part_headers = $parser->getMessageBodyHeaders($part); + $content_type = idx($part_headers, 'content-type'); + if ( + !phutil_is_utf8($part_body) && + (preg_match('/charset="(.*?)"/', $content_type, $matches) || + preg_match('/charset=(\S+)/', $content_type, $matches)) + ) { + $part_body = phutil_utf8_convert($part_body, 'UTF-8', $matches[1]); + } + $content[$part] = $part_body; } $headers = $parser->getHeaders(); @@ -57,10 +60,7 @@ if ($args->getArg('process-duplicates')) { $received = new PhabricatorMetaMTAReceivedMail(); $received->setHeaders($headers); -$received->setBodies(array( - 'text' => $text_body, - 'html' => $parser->getMessageBody('html'), -)); +$received->setBodies($content); $attachments = array(); foreach ($parser->getAttachments() as $attachment) { From d321cc810aab52be00d75c9dcfa8b9cabd34828e Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Nov 2017 06:09:41 -0800 Subject: [PATCH 44/72] Freeze "maniphest.gettasktransactions" and make status/priority transactions more consistent Summary: Ref T13020. See PHI221. Freeze legacy method `maniphest.gettasktransactions` in favor of modern method `transaction.search`. Remove legacy "null on create" behavior from Maniphest status and priority transactions. This behavior is obsolete with EditEngine, and leads to inconsistent transaction sets in the transaction record. The desired behavior is that transactions which don't do anything (e.g., default value was not changed) don't appear in the transaction log. Test Plan: - Viewed API UI and saw `maniphest.gettasktransactions` marked as "Frozen". - Created a new task via web UI (without changing status/priority), queried transactions with `maniphest.gettasktransacitons`/`transaction.search`, no longer saw "null on create" no-op transactions in record. - Web UI is unchanged, since these transactions were hidden before and now do not exist. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13020 Differential Revision: https://secure.phabricator.com/D18777 --- .../ManiphestGetTaskTransactionsConduitAPIMethod.php | 10 ++++++++++ .../xaction/ManiphestTaskPriorityTransaction.php | 9 +++------ .../xaction/ManiphestTaskStatusTransaction.php | 3 --- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php index 8b0d0496cf..357d118bc4 100644 --- a/src/applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php @@ -21,6 +21,16 @@ final class ManiphestGetTaskTransactionsConduitAPIMethod return 'nonempty list>'; } + public function getMethodStatus() { + return self::METHOD_STATUS_FROZEN; + } + + public function getMethodStatusDescription() { + return pht( + 'This method is frozen and will eventually be deprecated. New code '. + 'should use "transaction.search" instead.'); + } + protected function execute(ConduitAPIRequest $request) { $results = array(); $task_ids = $request->getValue('ids'); diff --git a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php index 2ed7c4e15b..83f38fc659 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php @@ -6,20 +6,17 @@ final class ManiphestTaskPriorityTransaction const TRANSACTIONTYPE = 'priority'; public function generateOldValue($object) { - if ($this->isNewObject()) { - return null; - } - return $object->getPriority(); + return (string)$object->getPriority(); } public function generateNewValue($object, $value) { // `$value` is supposed to be a keyword, but if the priority // assigned to a task has been removed from the config, // no such keyword will be available. Other edits to the task - // should still be allowed, even if the priority is no longer + // should still be allowed, even if the priority is no longer // valid, so treat this as a no-op. if ($value === ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD) { - return $object->getPriority(); + return (string)$object->getPriority(); } return (string)ManiphestTaskPriority::getTaskPriorityFromKeyword($value); diff --git a/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php index a3780e81b9..dd51a63799 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php @@ -6,9 +6,6 @@ final class ManiphestTaskStatusTransaction const TRANSACTIONTYPE = 'status'; public function generateOldValue($object) { - if ($this->isNewObject()) { - return null; - } return $object->getStatus(); } From 1cb0d41367b7a79f0fdaa1be20e348ce5bd8b628 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Nov 2017 07:35:22 -0800 Subject: [PATCH 45/72] Fix inbound mail handling for messages with no HTML body part Summary: See D18776. See . The change in D18776 to improve handling of non-utf8 HTML parts broke handling of mail with //no// HTML parts. Partly, this is because MimeMailParser has a "traditional" PHP-style API where the return type is an exciting surprise. Test Plan: - Sent a text-only message in `Mail.app`. - Used "Show Raw" to copy it to `mail.txt`, verifying that the raw message contains ONLY a text body. - Ran `cat mail.txt | ./scripts/mail/mail_handler.php --trace --process-duplicates`. - Before patch: error about bad `idx()` on a non-array. - After patch: clean mail processing. - Did the same with a message with both HTML and text bodies to make sure I didn't break anything. Ideally we'd probably get test coverage on this, but it's been touched roughly once a year since 2013 so it'll probably hold. Reviewers: amckinley, alexmv Reviewed By: amckinley, alexmv Differential Revision: https://secure.phabricator.com/D18778 --- scripts/mail/mail_handler.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/scripts/mail/mail_handler.php b/scripts/mail/mail_handler.php index b76b3910df..1c3c71f305 100755 --- a/scripts/mail/mail_handler.php +++ b/scripts/mail/mail_handler.php @@ -38,15 +38,19 @@ $parser->setText(file_get_contents('php://stdin')); $content = array(); foreach (array('text', 'html') as $part) { $part_body = $parser->getMessageBody($part); - $part_headers = $parser->getMessageBodyHeaders($part); - $content_type = idx($part_headers, 'content-type'); - if ( - !phutil_is_utf8($part_body) && - (preg_match('/charset="(.*?)"/', $content_type, $matches) || - preg_match('/charset=(\S+)/', $content_type, $matches)) - ) { - $part_body = phutil_utf8_convert($part_body, 'UTF-8', $matches[1]); + + if (strlen($part_body) && !phutil_is_utf8($part_body)) { + $part_headers = $parser->getMessageBodyHeaders($part); + if (!is_array($part_headers)) { + $part_headers = array(); + } + $content_type = idx($part_headers, 'content-type'); + if (preg_match('/charset="(.*?)"/', $content_type, $matches) || + preg_match('/charset=(\S+)/', $content_type, $matches)) { + $part_body = phutil_utf8_convert($part_body, 'UTF-8', $matches[1]); + } } + $content[$part] = $part_body; } From 2d4a158356ef7fd7931495defe3ef3675f59f333 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Nov 2017 07:52:20 -0800 Subject: [PATCH 46/72] Fix a bad link target in Diffusion content search results Summary: See . This links to the display path, which is incorrect. Test Plan: - In any repository, browsed into a directory. - Used pattern search to search for something that hits results. - Clicked the title (filename/path) of a result table. - Before patch: URL omits path context, 404 or wrong result. - After patch: taken to proper page. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18779 --- .../diffusion/view/DiffusionPatternSearchView.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/applications/diffusion/view/DiffusionPatternSearchView.php b/src/applications/diffusion/view/DiffusionPatternSearchView.php index a679b51400..93ef42d46d 100644 --- a/src/applications/diffusion/view/DiffusionPatternSearchView.php +++ b/src/applications/diffusion/view/DiffusionPatternSearchView.php @@ -96,10 +96,11 @@ final class DiffusionPatternSearchView extends DiffusionView { $path_title = Filesystem::readablePath($this->path, $drequest->getPath()); - $href = $drequest->generateURI(array( - 'action' => 'browse', - 'path' => $path_title, - )); + $href = $drequest->generateURI( + array( + 'action' => 'browse', + 'path' => $this->path, + )); $title = phutil_tag('a', array('href' => $href), $path_title); From 2c72c2b924ffa3f8a49dbec636a2cdca3bae004f Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Nov 2017 09:21:37 -0800 Subject: [PATCH 47/72] Add basic support for OpenGraph header tags for public installs Summary: Ref T13018. This is easy to get working roughly, at least, and seems reasonable. Test Plan: Viewed page source, saw tags. Custom header logo still worked. Pretty hard to debug against a local install since Disqus / debugger tools can't hit it, but I'll see what it looks like in production and tweak it if I got anything horribly wrong. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13018 Differential Revision: https://secure.phabricator.com/D18780 --- .../PhabricatorCustomLogoConfigType.php | 33 +++++++++++++ src/view/page/PhabricatorStandardPageView.php | 46 ++++++++++++++++++- .../page/menu/PhabricatorMainMenuView.php | 34 +++----------- 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php index ff29050602..cf4dfda2b5 100644 --- a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php +++ b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php @@ -13,6 +13,39 @@ final class PhabricatorCustomLogoConfigType return idx($logo, 'wordmarkText'); } + public static function getLogoURI(PhabricatorUser $viewer) { + $logo_uri = null; + + $custom_header = self::getLogoImagePHID(); + if ($custom_header) { + $cache = PhabricatorCaches::getImmutableCache(); + $cache_key_logo = 'ui.custom-header.logo-phid.v3.'.$custom_header; + $logo_uri = $cache->getKey($cache_key_logo); + + if (!$logo_uri) { + // NOTE: If the file policy has been changed to be restrictive, we'll + // miss here and just show the default logo. The cache will fill later + // when someone who can see the file loads the page. This might be a + // little spooky, see T11982. + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($custom_header)) + ->execute(); + $file = head($files); + if ($file) { + $logo_uri = $file->getViewURI(); + $cache->setKey($cache_key_logo, $logo_uri); + } + } + } + + if (!$logo_uri) { + $logo_uri = celerity_get_resource_uri('/rsrc/image/logo/light-eye.png'); + } + + return $logo_uri; + } + public function validateOption(PhabricatorConfigOption $option, $value) { if (!is_array($value)) { throw new Exception( diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index 78ff716a44..d2b4d0d2c2 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -426,10 +426,11 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView } return hsprintf( - '%s%s%s', + '%s%s%s%s', parent::getHead(), $font_css, - $response->renderSingleResource('javelin-magical-init', 'phabricator')); + $response->renderSingleResource('javelin-magical-init', 'phabricator'), + $this->newOpenGraphTags()); } public function setGlyph($glyph) { @@ -911,4 +912,45 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView return $response; } + private function newOpenGraphTags() { + // If we don't allow public access, there's no point in emitting OpenGraph + // tags because external systems can't fetch pages. + if (!PhabricatorEnv::getEnvConfig('policy.allow-public')) { + return array(); + } + + $viewer = $this->getViewer(); + + $properties = array( + array( + 'og:title', + $this->getTitle(), + ), + array( + 'og:type', + 'website', + ), + array( + 'og:url', + PhabricatorEnv::getProductionURI($this->getRequest()->getRequestURI()), + ), + array( + 'og:image', + PhabricatorCustomLogoConfigType::getLogoURI($viewer), + ), + ); + + $tags = array(); + foreach ($properties as $property) { + $tags[] = phutil_tag( + 'meta', + array( + 'property' => $property[0], + 'content' => $property[1], + )); + } + + return $tags; + } + } diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index f9e4032d87..2e5b5614ec 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -262,35 +262,16 @@ final class PhabricatorMainMenuView extends AphrontView { } private function renderPhabricatorLogo() { - $custom_header = PhabricatorCustomLogoConfigType::getLogoImagePHID(); - $logo_style = array(); + + $custom_header = PhabricatorCustomLogoConfigType::getLogoImagePHID(); if ($custom_header) { - $cache = PhabricatorCaches::getImmutableCache(); - $cache_key_logo = 'ui.custom-header.logo-phid.v3.'.$custom_header; + $viewer = $this->getViewer(); + $logo_uri = PhabricatorCustomLogoConfigType::getLogoURI($viewer); - $logo_uri = $cache->getKey($cache_key_logo); - if (!$logo_uri) { - // NOTE: If the file policy has been changed to be restrictive, we'll - // miss here and just show the default logo. The cache will fill later - // when someone who can see the file loads the page. This might be a - // little spooky, see T11982. - $files = id(new PhabricatorFileQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs(array($custom_header)) - ->execute(); - $file = head($files); - if ($file) { - $logo_uri = $file->getViewURI(); - $cache->setKey($cache_key_logo, $logo_uri); - } - } - - if ($logo_uri) { - $logo_style[] = 'background-size: 40px 40px;'; - $logo_style[] = 'background-position: 0 0;'; - $logo_style[] = 'background-image: url('.$logo_uri.')'; - } + $logo_style[] = 'background-size: 40px 40px;'; + $logo_style[] = 'background-position: 0 0;'; + $logo_style[] = 'background-image: url('.$logo_uri.')'; } $logo_node = phutil_tag( @@ -300,7 +281,6 @@ final class PhabricatorMainMenuView extends AphrontView { 'style' => implode(' ', $logo_style), )); - $wordmark_text = PhabricatorCustomLogoConfigType::getLogoWordmark(); if (!strlen($wordmark_text)) { $wordmark_text = pht('Phabricator'); From c3d6c4b0ee8efed2ea20d4ece90b012ea19b7f00 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Nov 2017 11:25:51 -0800 Subject: [PATCH 48/72] Include OpenGraph prefix material in tag if OpenGraph is enabled Summary: Ref T13018. Discourse doesn't seem to be picking this up yet (see ) so maybe it really needs this meta-meta-XML stuff? Test Plan: Will push. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13018 Differential Revision: https://secure.phabricator.com/D18781 --- resources/celerity/map.php | 1 + src/view/page/AphrontPageView.php | 9 ++++++++- src/view/page/PhabricatorStandardPageView.php | 2 +- webroot/rsrc/favicons/opengraph-144x144.png | Bin 0 -> 13270 bytes 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 webroot/rsrc/favicons/opengraph-144x144.png diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 78ae8bd461..45da85c7f1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -292,6 +292,7 @@ return array( 'rsrc/favicons/mstile-310x150.png' => '4a49d3ee', 'rsrc/favicons/mstile-310x310.png' => 'a52ab264', 'rsrc/favicons/mstile-70x70.png' => '5edce7b8', + 'rsrc/favicons/opengraph-144x144.png' => '648fb0fc', 'rsrc/image/BFCFDA.png' => 'd5ec91f4', 'rsrc/image/actions/edit.png' => '2fc41442', 'rsrc/image/avatar.png' => '17d346a4', diff --git a/src/view/page/AphrontPageView.php b/src/view/page/AphrontPageView.php index 8f3704dca2..bea516a8cf 100644 --- a/src/view/page/AphrontPageView.php +++ b/src/view/page/AphrontPageView.php @@ -59,9 +59,15 @@ abstract class AphrontPageView extends AphrontView { ), array($body, $tail)); + if (PhabricatorEnv::getEnvConfig('policy.allow-public')) { + $html_open_tag = hsprintf(''); + } else { + $html_open_tag = hsprintf(''); + } + $response = hsprintf( ''. - ''. + '%s'. ''. ''. '%s'. @@ -69,6 +75,7 @@ abstract class AphrontPageView extends AphrontView { ''. '%s'. '', + $html_open_tag, $title, $head, $body); diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index d2b4d0d2c2..b4e706ab0b 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -936,7 +936,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView ), array( 'og:image', - PhabricatorCustomLogoConfigType::getLogoURI($viewer), + celerity_get_resource_uri('rsrc/favicons/opengraph-144x144.png'), ), ); diff --git a/webroot/rsrc/favicons/opengraph-144x144.png b/webroot/rsrc/favicons/opengraph-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..92f2114b205565d9029e10ede56cdf23827ee1e4 GIT binary patch literal 13270 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 bFYpXZa^goDzqnxc>i`sG)nqEAOuzjf>K7hx literal 0 HcmV?d00001 From 49b57eae7df52c189aef1d973823c697fc97fd4b Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Nov 2017 14:27:28 -0800 Subject: [PATCH 49/72] Revert partial/nonfunctional OpenGraph support Summary: Ref T13018. See that task and the Discourse thread for discussion. This doesn't work as-is and we need to `og:description` everything to make it work. I don't want to sink any more time into this so just back all the changes out for now. (The `` change is unnecessary anyway.) Test Plan: Strict revert. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13018 Differential Revision: https://secure.phabricator.com/D18782 --- resources/celerity/map.php | 1 - .../PhabricatorCustomLogoConfigType.php | 33 ------------- src/view/page/AphrontPageView.php | 9 +--- src/view/page/PhabricatorStandardPageView.php | 46 +----------------- .../page/menu/PhabricatorMainMenuView.php | 36 +++++++++++--- webroot/rsrc/favicons/opengraph-144x144.png | Bin 13270 -> 0 bytes 6 files changed, 31 insertions(+), 94 deletions(-) delete mode 100644 webroot/rsrc/favicons/opengraph-144x144.png diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 45da85c7f1..78ae8bd461 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -292,7 +292,6 @@ return array( 'rsrc/favicons/mstile-310x150.png' => '4a49d3ee', 'rsrc/favicons/mstile-310x310.png' => 'a52ab264', 'rsrc/favicons/mstile-70x70.png' => '5edce7b8', - 'rsrc/favicons/opengraph-144x144.png' => '648fb0fc', 'rsrc/image/BFCFDA.png' => 'd5ec91f4', 'rsrc/image/actions/edit.png' => '2fc41442', 'rsrc/image/avatar.png' => '17d346a4', diff --git a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php index cf4dfda2b5..ff29050602 100644 --- a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php +++ b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php @@ -13,39 +13,6 @@ final class PhabricatorCustomLogoConfigType return idx($logo, 'wordmarkText'); } - public static function getLogoURI(PhabricatorUser $viewer) { - $logo_uri = null; - - $custom_header = self::getLogoImagePHID(); - if ($custom_header) { - $cache = PhabricatorCaches::getImmutableCache(); - $cache_key_logo = 'ui.custom-header.logo-phid.v3.'.$custom_header; - $logo_uri = $cache->getKey($cache_key_logo); - - if (!$logo_uri) { - // NOTE: If the file policy has been changed to be restrictive, we'll - // miss here and just show the default logo. The cache will fill later - // when someone who can see the file loads the page. This might be a - // little spooky, see T11982. - $files = id(new PhabricatorFileQuery()) - ->setViewer($viewer) - ->withPHIDs(array($custom_header)) - ->execute(); - $file = head($files); - if ($file) { - $logo_uri = $file->getViewURI(); - $cache->setKey($cache_key_logo, $logo_uri); - } - } - } - - if (!$logo_uri) { - $logo_uri = celerity_get_resource_uri('/rsrc/image/logo/light-eye.png'); - } - - return $logo_uri; - } - public function validateOption(PhabricatorConfigOption $option, $value) { if (!is_array($value)) { throw new Exception( diff --git a/src/view/page/AphrontPageView.php b/src/view/page/AphrontPageView.php index bea516a8cf..8f3704dca2 100644 --- a/src/view/page/AphrontPageView.php +++ b/src/view/page/AphrontPageView.php @@ -59,15 +59,9 @@ abstract class AphrontPageView extends AphrontView { ), array($body, $tail)); - if (PhabricatorEnv::getEnvConfig('policy.allow-public')) { - $html_open_tag = hsprintf(''); - } else { - $html_open_tag = hsprintf(''); - } - $response = hsprintf( ''. - '%s'. + ''. ''. ''. '%s'. @@ -75,7 +69,6 @@ abstract class AphrontPageView extends AphrontView { ''. '%s'. '', - $html_open_tag, $title, $head, $body); diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index b4e706ab0b..78ff716a44 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -426,11 +426,10 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView } return hsprintf( - '%s%s%s%s', + '%s%s%s', parent::getHead(), $font_css, - $response->renderSingleResource('javelin-magical-init', 'phabricator'), - $this->newOpenGraphTags()); + $response->renderSingleResource('javelin-magical-init', 'phabricator')); } public function setGlyph($glyph) { @@ -912,45 +911,4 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView return $response; } - private function newOpenGraphTags() { - // If we don't allow public access, there's no point in emitting OpenGraph - // tags because external systems can't fetch pages. - if (!PhabricatorEnv::getEnvConfig('policy.allow-public')) { - return array(); - } - - $viewer = $this->getViewer(); - - $properties = array( - array( - 'og:title', - $this->getTitle(), - ), - array( - 'og:type', - 'website', - ), - array( - 'og:url', - PhabricatorEnv::getProductionURI($this->getRequest()->getRequestURI()), - ), - array( - 'og:image', - celerity_get_resource_uri('rsrc/favicons/opengraph-144x144.png'), - ), - ); - - $tags = array(); - foreach ($properties as $property) { - $tags[] = phutil_tag( - 'meta', - array( - 'property' => $property[0], - 'content' => $property[1], - )); - } - - return $tags; - } - } diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 2e5b5614ec..f9e4032d87 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -262,16 +262,35 @@ final class PhabricatorMainMenuView extends AphrontView { } private function renderPhabricatorLogo() { - $logo_style = array(); - $custom_header = PhabricatorCustomLogoConfigType::getLogoImagePHID(); - if ($custom_header) { - $viewer = $this->getViewer(); - $logo_uri = PhabricatorCustomLogoConfigType::getLogoURI($viewer); - $logo_style[] = 'background-size: 40px 40px;'; - $logo_style[] = 'background-position: 0 0;'; - $logo_style[] = 'background-image: url('.$logo_uri.')'; + $logo_style = array(); + if ($custom_header) { + $cache = PhabricatorCaches::getImmutableCache(); + $cache_key_logo = 'ui.custom-header.logo-phid.v3.'.$custom_header; + + $logo_uri = $cache->getKey($cache_key_logo); + if (!$logo_uri) { + // NOTE: If the file policy has been changed to be restrictive, we'll + // miss here and just show the default logo. The cache will fill later + // when someone who can see the file loads the page. This might be a + // little spooky, see T11982. + $files = id(new PhabricatorFileQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($custom_header)) + ->execute(); + $file = head($files); + if ($file) { + $logo_uri = $file->getViewURI(); + $cache->setKey($cache_key_logo, $logo_uri); + } + } + + if ($logo_uri) { + $logo_style[] = 'background-size: 40px 40px;'; + $logo_style[] = 'background-position: 0 0;'; + $logo_style[] = 'background-image: url('.$logo_uri.')'; + } } $logo_node = phutil_tag( @@ -281,6 +300,7 @@ final class PhabricatorMainMenuView extends AphrontView { 'style' => implode(' ', $logo_style), )); + $wordmark_text = PhabricatorCustomLogoConfigType::getLogoWordmark(); if (!strlen($wordmark_text)) { $wordmark_text = pht('Phabricator'); diff --git a/webroot/rsrc/favicons/opengraph-144x144.png b/webroot/rsrc/favicons/opengraph-144x144.png deleted file mode 100644 index 92f2114b205565d9029e10ede56cdf23827ee1e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13270 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 bFYpXZa^goDzqnxc>i`sG)nqEAOuzjf>K7hx From 00baa3c1dd9ba8b38a588c7dd1ca53fe5d666550 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 14:08:40 -0800 Subject: [PATCH 50/72] Hide archived projects only on workboards, not hovercards Summary: See PHI225. Previously, see D15335 / T10413. On workboards, we hide archived project tags since they aren't terribly useful in that context, at least most of the time. Originally, see T10349#159916 and D15297. However, hovercards reuse this display logic, and it's inconsistent/confusing to hide them there, since the actual "Tags" elements on task pages show them. Narrow the scope of this rule. Test Plan: - Viewed a hovercard for a task with an archived project tagged, saw archived project. - Viewed a workboard for the same task, saw only unarchived projects other than the current board tagged (this behavior is unchanged). Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18783 --- .../PhabricatorBoardRenderingEngine.php | 4 +++- .../project/view/ProjectBoardTaskCard.php | 20 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/applications/project/engine/PhabricatorBoardRenderingEngine.php b/src/applications/project/engine/PhabricatorBoardRenderingEngine.php index ca8b0633da..d76497bc21 100644 --- a/src/applications/project/engine/PhabricatorBoardRenderingEngine.php +++ b/src/applications/project/engine/PhabricatorBoardRenderingEngine.php @@ -67,7 +67,9 @@ final class PhabricatorBoardRenderingEngine extends Phobject { $project_phids = $object->getProjectPHIDs(); $project_handles = array_select_keys($this->handles, $project_phids); if ($project_handles) { - $card->setProjectHandles($project_handles); + $card + ->setHideArchivedProjects(true) + ->setProjectHandles($project_handles); } $cover_phid = $object->getCoverImageThumbnailPHID(); diff --git a/src/applications/project/view/ProjectBoardTaskCard.php b/src/applications/project/view/ProjectBoardTaskCard.php index 1a6807ec45..3a7016ca74 100644 --- a/src/applications/project/view/ProjectBoardTaskCard.php +++ b/src/applications/project/view/ProjectBoardTaskCard.php @@ -8,6 +8,7 @@ final class ProjectBoardTaskCard extends Phobject { private $owner; private $canEdit; private $coverImageFile; + private $hideArchivedProjects; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -35,6 +36,15 @@ final class ProjectBoardTaskCard extends Phobject { return $this->coverImageFile; } + public function setHideArchivedProjects($hide_archived_projects) { + $this->hideArchivedProjects = $hide_archived_projects; + return $this; + } + + public function getHideArchivedProjects() { + return $this->hideArchivedProjects; + } + public function setTask(ManiphestTask $task) { $this->task = $task; return $this; @@ -126,10 +136,12 @@ final class ProjectBoardTaskCard extends Phobject { $project_handles = $this->getProjectHandles(); // Remove any archived projects from the list. - if ($project_handles) { - foreach ($project_handles as $key => $handle) { - if ($handle->getStatus() == PhabricatorObjectHandle::STATUS_CLOSED) { - unset($project_handles[$key]); + if ($this->hideArchivedProjects) { + if ($project_handles) { + foreach ($project_handles as $key => $handle) { + if ($handle->getStatus() == PhabricatorObjectHandle::STATUS_CLOSED) { + unset($project_handles[$key]); + } } } } From 3d742eb50baf54433e41090d1ba9d06277fedebc Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 16:10:33 -0800 Subject: [PATCH 51/72] Lightly modernize LegalpadDocumentQuery Summary: Ref T13024. Updates LegalpadDocumentQuery to use slightly more modern constructions. Test Plan: Queried for Legalpad documents, got the same results. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18785 --- .../legalpad/query/LegalpadDocumentQuery.php | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/src/applications/legalpad/query/LegalpadDocumentQuery.php b/src/applications/legalpad/query/LegalpadDocumentQuery.php index 655da955e4..3d79e9f3a1 100644 --- a/src/applications/legalpad/query/LegalpadDocumentQuery.php +++ b/src/applications/legalpad/query/LegalpadDocumentQuery.php @@ -77,23 +77,12 @@ final class LegalpadDocumentQuery return $this; } + public function newResultObject() { + return new LegalpadDocument(); + } + protected function loadPage() { - $table = new LegalpadDocument(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT d.* FROM %T d %Q %Q %Q %Q %Q', - $table->getTableName(), - $this->buildJoinClause($conn_r), - $this->buildWhereClause($conn_r), - $this->buildGroupClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - $documents = $table->loadAllFromArray($data); - - return $documents; + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $documents) { @@ -134,12 +123,12 @@ final class LegalpadDocumentQuery return $documents; } - protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { - $joins = array(); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); if ($this->contributorPHIDs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN edge contributor ON contributor.src = d.phid AND contributor.type = %d', PhabricatorObjectHasContributorEdgeType::EDGECONST); @@ -147,79 +136,81 @@ final class LegalpadDocumentQuery if ($this->signerPHIDs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T signer ON signer.documentPHID = d.phid AND signer.signerPHID IN (%Ls)', id(new LegalpadDocumentSignature())->getTableName(), $this->signerPHIDs); } - return implode(' ', $joins); + return $joins; } - protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { - if ($this->contributorPHIDs || $this->signerPHIDs) { - return 'GROUP BY d.id'; - } else { - return ''; + protected function shouldGroupQueryResultRows() { + if ($this->contributorPHIDs) { + return true; } + + if ($this->signerPHIDs) { + return true; + } + + return parent::shouldGroupQueryResultRows(); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.phid IN (%Ls)', $this->phids); } if ($this->creatorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.creatorPHID IN (%Ls)', $this->creatorPHIDs); } if ($this->dateCreatedAfter !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.dateCreated >= %d', $this->dateCreatedAfter); } if ($this->dateCreatedBefore !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.dateCreated <= %d', $this->dateCreatedBefore); } if ($this->contributorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'contributor.dst IN (%Ls)', $this->contributorPHIDs); } if ($this->signatureRequired !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.requireSignature = %d', $this->signatureRequired); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } private function loadDocumentBodies(array $documents) { @@ -275,4 +266,8 @@ final class LegalpadDocumentQuery return 'PhabricatorLegalpadApplication'; } + protected function getPrimaryTableAlias() { + return 'd'; + } + } From 71406ca93bf4dbda8a3e0afd0115ed5e28a6c120 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 16:42:40 -0800 Subject: [PATCH 52/72] Lightly modernize LegalpadDocumentSearchEngine Summary: Depends on D18785. Ref T13024. While I'm in here, update this a bit to use the newer stuff. Test Plan: Searched for Legalpad documents, saw more modern support (subscribers, order by, viewer()). Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18786 --- .../query/LegalpadDocumentSearchEngine.php | 138 ++++++------------ 1 file changed, 47 insertions(+), 91 deletions(-) diff --git a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php index 8b218cf821..1b608b02e3 100644 --- a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php +++ b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php @@ -11,105 +11,66 @@ final class LegalpadDocumentSearchEngine return 'PhabricatorLegalpadApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - $saved->setParameter( - 'creatorPHIDs', - $this->readUsersFromRequest($request, 'creators')); - - $saved->setParameter( - 'contributorPHIDs', - $this->readUsersFromRequest($request, 'contributors')); - - $saved->setParameter( - 'withViewerSignature', - $request->getBool('withViewerSignature')); - - $saved->setParameter('createdStart', $request->getStr('createdStart')); - $saved->setParameter('createdEnd', $request->getStr('createdEnd')); - - return $saved; + public function newQuery() { + return id(new LegalpadDocumentQuery()) + ->needViewerSignatures(true); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new LegalpadDocumentQuery()) - ->needViewerSignatures(true); + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Signed By')) + ->setKey('signerPHIDs') + ->setAliases(array('signer', 'signers', 'signerPHID')) + ->setDescription( + pht('Search for documents signed by given users.')), + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Creators')) + ->setKey('creatorPHIDs') + ->setAliases(array('creator', 'creators', 'creatorPHID')) + ->setDescription( + pht('Search for documents with given creators.')), + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Contributors')) + ->setKey('contributorPHIDs') + ->setAliases(array('contributor', 'contributors', 'contributorPHID')) + ->setDescription( + pht('Search for documents with given contributors.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created After')) + ->setKey('createdStart'), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created Before')) + ->setKey('createdEnd'), + ); + } - $creator_phids = $saved->getParameter('creatorPHIDs', array()); - if ($creator_phids) { - $query->withCreatorPHIDs($creator_phids); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['signerPHIDs']) { + $query->withSignerPHIDs($map['signerPHIDs']); } - $contributor_phids = $saved->getParameter('contributorPHIDs', array()); - if ($contributor_phids) { - $query->withContributorPHIDs($contributor_phids); + if ($map['contributorPHIDs']) { + $query->withContributorPHIDs($map['creatorPHIDs']); } - if ($saved->getParameter('withViewerSignature')) { - $viewer_phid = $this->requireViewer()->getPHID(); - if ($viewer_phid) { - $query->withSignerPHIDs(array($viewer_phid)); - } + if ($map['creatorPHIDs']) { + $query->withCreatorPHIDs($map['creatorPHIDs']); } - $start = $this->parseDateTime($saved->getParameter('createdStart')); - $end = $this->parseDateTime($saved->getParameter('createdEnd')); - - if ($start) { - $query->withDateCreatedAfter($start); + if ($map['createdStart']) { + $query->withDateCreatedAfter($map['createdStart']); } - if ($end) { - $query->withDateCreatedBefore($end); + if ($map['createdEnd']) { + $query->withDateCreatedAfter($map['createdStart']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { - - $creator_phids = $saved_query->getParameter('creatorPHIDs', array()); - $contributor_phids = $saved_query->getParameter( - 'contributorPHIDs', array()); - - $viewer_signature = $saved_query->getParameter('withViewerSignature'); - if (!$this->requireViewer()->getPHID()) { - $viewer_signature = false; - } - - $form - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'withViewerSignature', - 1, - pht('Show only documents I have signed.'), - $viewer_signature) - ->setDisabled(!$this->requireViewer()->getPHID())) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('creators') - ->setLabel(pht('Creators')) - ->setValue($creator_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('contributors') - ->setLabel(pht('Contributors')) - ->setValue($contributor_phids)); - - $this->buildDateRange( - $form, - $saved_query, - 'createdStart', - pht('Created After'), - 'createdEnd', - pht('Created Before')); - } - protected function getURI($path) { return '/legalpad/'.$path; } @@ -130,10 +91,11 @@ final class LegalpadDocumentSearchEngine $query = $this->newSavedQuery(); $query->setQueryKey($query_key); + $viewer = $this->requireViewer(); + switch ($query_key) { case 'signed': - return $query - ->setParameter('withViewerSignature', true); + return $query->setParameter('signerPHIDs', array($viewer->getPHID())); case 'all': return $query; } @@ -141,12 +103,6 @@ final class LegalpadDocumentSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $documents, - PhabricatorSavedQuery $query) { - return array(); - } - protected function renderResultList( array $documents, PhabricatorSavedQuery $query, From ba4b9f71849f501fc95a204f16de7db871a3ce5a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 16:10:38 -0800 Subject: [PATCH 53/72] Refactor on-login Legalpad document signature requirement Summary: Depends on D18786. Ref T13024. I'm going to change the order this occurs in, but move it to a separate method and clean it up a little first. Test Plan: Added a new document as required, reloaded, signed it, got logged into a session. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18788 --- .../base/controller/PhabricatorController.php | 112 ++++++++++++------ 1 file changed, 73 insertions(+), 39 deletions(-) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index f923eb7b73..961e85effe 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -227,45 +227,9 @@ abstract class PhabricatorController extends AphrontController { } - if (!$this->shouldAllowLegallyNonCompliantUsers()) { - $legalpad_class = 'PhabricatorLegalpadApplication'; - $legalpad = id(new PhabricatorApplicationQuery()) - ->setViewer($user) - ->withClasses(array($legalpad_class)) - ->withInstalled(true) - ->execute(); - $legalpad = head($legalpad); - - $doc_query = id(new LegalpadDocumentQuery()) - ->setViewer($user) - ->withSignatureRequired(1) - ->needViewerSignatures(true); - - if ($user->hasSession() && - !$user->getSession()->getIsPartial() && - !$user->getSession()->getSignedLegalpadDocuments() && - $user->isLoggedIn() && - $legalpad) { - - $sign_docs = $doc_query->execute(); - $must_sign_docs = array(); - foreach ($sign_docs as $sign_doc) { - if (!$sign_doc->getUserSignature($user->getPHID())) { - $must_sign_docs[] = $sign_doc; - } - } - if ($must_sign_docs) { - $controller = new LegalpadDocumentSignController(); - $this->getRequest()->setURIMap(array( - 'id' => head($must_sign_docs)->getID(), - )); - $this->setCurrentApplication($legalpad); - return $this->delegateToController($controller); - } else { - $engine = id(new PhabricatorAuthSessionEngine()) - ->signLegalpadDocuments($user, $sign_docs); - } - } + $result = $this->requireLegalpadSignatures(); + if ($result !== null) { + return $result; } // NOTE: We do this last so that users get a login page instead of a 403 @@ -558,6 +522,76 @@ abstract class PhabricatorController extends AphrontController { return $this->buildApplicationCrumbs(); } + private function requireLegalpadSignatures() { + if ($this->shouldAllowLegallyNonCompliantUsers()) { + return null; + } + + $viewer = $this->getViewer(); + + if (!$viewer->hasSession()) { + return null; + } + + $session = $viewer->getSession(); + if ($session->getIsPartial()) { + // If the user hasn't made it through MFA yet, require they survive + // MFA first. + return null; + } + + if ($session->getSignedLegalpadDocuments()) { + return null; + } + + if (!$viewer->isLoggedIn()) { + return null; + } + + $legalpad_class = 'PhabricatorLegalpadApplication'; + $legalpad_installed = PhabricatorApplication::isClassInstalledForViewer( + $legalpad_class, + $viewer); + if (!$legalpad_installed) { + return null; + } + + $sign_docs = id(new LegalpadDocumentQuery()) + ->setViewer($viewer) + ->withSignatureRequired(1) + ->needViewerSignatures(true) + ->execute(); + + $must_sign_docs = array(); + foreach ($sign_docs as $sign_doc) { + if (!$sign_doc->getUserSignature($viewer->getPHID())) { + $must_sign_docs[] = $sign_doc; + } + } + + if (!$must_sign_docs) { + // If nothing needs to be signed (either because there are no documents + // which require a signature, or because the user has already signed + // all of them) mark the session as good and continue. + $engine = id(new PhabricatorAuthSessionEngine()) + ->signLegalpadDocuments($viewer, $sign_docs); + + return null; + } + + $request = $this->getRequest(); + $request->setURIMap( + array( + 'id' => head($must_sign_docs)->getID(), + )); + + $application = PhabricatorApplication::getByClass($legalpad_class); + $this->setCurrentApplication($application); + + $controller = new LegalpadDocumentSignController(); + return $this->delegateToController($controller); + } + /* -( Deprecated )--------------------------------------------------------- */ From e850bc6b957a8ac6c6faa02d57716ce5d60d42f6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 16:54:26 -0800 Subject: [PATCH 54/72] When requiring login signatures, order documents from oldest to newest Summary: Depends on D18788. Ref T13024. Currently, we prompt users to sign from newest to oldest. This seems less intuitive than oldest to newest. Test Plan: Dumped document order, saw it swap to oldest-first. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18789 --- src/applications/base/controller/PhabricatorController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 961e85effe..fb9c402083 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -560,6 +560,7 @@ abstract class PhabricatorController extends AphrontController { ->setViewer($viewer) ->withSignatureRequired(1) ->needViewerSignatures(true) + ->setOrder('oldest') ->execute(); $must_sign_docs = array(); From c7718d3a2126fe77da17467c933aa6bd95f59779 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 17:02:09 -0800 Subject: [PATCH 55/72] Ask users to sign Legalpad documents before requiring they enroll in MFA Summary: Depends on D18789. Ref T13024. See PHI223. Currently, if `security.require-multi-factor-auth` and Legalpad "Signature Required" documents are //both// set, it's not possible to survive account registration, since MFA is requiried to sign and signatures are required to add MFA. Instead, check for signatures before requiring MFA enrollment. This makes logical sense, since it's silly to add MFA if you don't agree to a Terms of Service or whatever. (Note that if you already have MFA, we prompt for that first, before either of these steps, which also makes sense.) Test Plan: Configured `security.require-multi-factor-auth`. Added a signature-required document. Loaded a page as a new user. Went through signature workflow, then through the MFA enrollment workflow. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18790 --- .../base/controller/PhabricatorController.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index fb9c402083..09aa24a42e 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -160,6 +160,15 @@ abstract class PhabricatorController extends AphrontController { } } + // Require users sign Legalpad documents before we check if they have + // MFA. If we don't do this, they can get stuck in a state where they + // can't add MFA until they sign, and can't sign until they add MFA. + // See T13024 and PHI223. + $result = $this->requireLegalpadSignatures(); + if ($result !== null) { + return $result; + } + // Check if the user needs to configure MFA. $need_mfa = $this->shouldRequireMultiFactorEnrollment(); $have_mfa = $user->getIsEnrolledInMultiFactor(); @@ -226,12 +235,6 @@ abstract class PhabricatorController extends AphrontController { } } - - $result = $this->requireLegalpadSignatures(); - if ($result !== null) { - return $result; - } - // NOTE: We do this last so that users get a login page instead of a 403 // if they need to login. if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) { @@ -523,6 +526,10 @@ abstract class PhabricatorController extends AphrontController { } private function requireLegalpadSignatures() { + if (!$this->shouldRequireLogin()) { + return null; + } + if ($this->shouldAllowLegallyNonCompliantUsers()) { return null; } From ab0f61aa323d3557b6eff806f257a53e2ca5e759 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 17:16:09 -0800 Subject: [PATCH 56/72] Tell users to "Wait Patiently" for admin account verification later in the registration process Summary: Depends on D18790. Ref T13024. Fixes T8335. Currently, "unapproved" and "disabled" users are bundled together. This prevents users from completing some registration steps (verification, legalpad documents, MFA enrollment) before approval. Separate approval out and move it to the end so users can do all the required enrollment stuff on their end before we roadblock them. Test Plan: Required approval, email verification, signatures, and MFA. Registered an account. Verified email, signed documents, enrolled in MFA, and then got prompted to wait for approval. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024, T8335 Differential Revision: https://secure.phabricator.com/D18791 --- .../PhabricatorAuthNeedsMultiFactorController.php | 12 ++++++++++++ .../base/controller/PhabricatorController.php | 15 +++++++++++---- .../PhabricatorAccessControlTestCase.php | 6 +++--- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php index f3e2562a1c..d295812d3e 100644 --- a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php +++ b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php @@ -9,9 +9,21 @@ final class PhabricatorAuthNeedsMultiFactorController return false; } + public function shouldRequireEnabledUser() { + // Users who haven't been approved yet are allowed to enroll in MFA. We'll + // kick disabled users out later. + return false; + } + public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); + if ($viewer->getIsDisabled()) { + // We allowed unapproved and disabled users to hit this controller, but + // want to kick out disabled users now. + return new Aphront400Response(); + } + $panel = id(new PhabricatorMultiFactorSettingsPanel()) ->setUser($viewer) ->setViewer($viewer) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 09aa24a42e..0fee880378 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -137,10 +137,6 @@ abstract class PhabricatorController extends AphrontController { } if ($this->shouldRequireEnabledUser()) { - if ($user->isLoggedIn() && !$user->getIsApproved()) { - $controller = new PhabricatorAuthNeedsApprovalController(); - return $this->delegateToController($controller); - } if ($user->getIsDisabled()) { $controller = new PhabricatorDisabledUserController(); return $this->delegateToController($controller); @@ -233,6 +229,17 @@ abstract class PhabricatorController extends AphrontController { ->withPHIDs(array($application->getPHID())) ->executeOne(); } + + // If users need approval, require they wait here. We do this near the + // end so they can take other actions (like verifying email, signing + // documents, and enrolling in MFA) while waiting for an admin to take a + // look at things. See T13024 for more discussion. + if ($this->shouldRequireEnabledUser()) { + if ($user->isLoggedIn() && !$user->getIsApproved()) { + $controller = new PhabricatorAuthNeedsApprovalController(); + return $this->delegateToController($controller); + } + } } // NOTE: We do this last so that users get a login page instead of a 403 diff --git a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php index b6c268d88c..98fa948722 100644 --- a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php +++ b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php @@ -159,10 +159,10 @@ final class PhabricatorAccessControlTestCase extends PhabricatorTestCase { $u_unverified, $u_admin, $u_public, + $u_notapproved, ), array( $u_disabled, - $u_notapproved, )); @@ -224,7 +224,7 @@ final class PhabricatorAccessControlTestCase extends PhabricatorTestCase { )); $this->checkAccess( - pht('Application Controller'), + pht('Application Controller, No Login Required'), id(clone $app_controller)->setConfig('login', false), $request, array( @@ -232,10 +232,10 @@ final class PhabricatorAccessControlTestCase extends PhabricatorTestCase { $u_unverified, $u_admin, $u_public, + $u_notapproved, ), array( $u_disabled, - $u_notapproved, )); } From dc62d18b473624a2547efb5751f2713ce0047256 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 17:34:43 -0800 Subject: [PATCH 57/72] Allow MFA enrollment before email verification Summary: Depends on D18791. Ref T13024. This clears up another initialization order issue, where an unverified address could prevent MFA enrollment. Test Plan: Configured both verification required and MFA required, clicked "Add Factor", got a dialog for the workflow. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18792 --- .../PhabricatorAuthNeedsMultiFactorController.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php index d295812d3e..27e03485ca 100644 --- a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php +++ b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php @@ -15,6 +15,12 @@ final class PhabricatorAuthNeedsMultiFactorController return false; } + public function shouldRequireEmailVerification() { + // Users who haven't verified their email addresses yet can still enroll + // in MFA. + return false; + } + public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); From e919233b3182d55b1d257df3b8ec369d702fff97 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 17:53:58 -0800 Subject: [PATCH 58/72] Don't show personalized menu items until users establish a full session Summary: Depends on D18792. Fixes T13024. Fixes T89198. Currently, when users are logging in initially (for example, need to enter MFA) we show more menu items than we should. Notably, we may show some personalized/private account details, like the number of unread notifications (probably not relevant) or a user's saved queries (possibly sensitive). At best these are misleading (they won't work yet) and there's an outside possibility they leak a little bit of private data. Instead, nuke everything except "Log Out" when users have partial sessions. Test Plan: Hit a partial session (MFA required, email verification required) and looked at the menu. Only saw "Log Out". {F5297713} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18793 --- .../PhabricatorFileDataController.php | 4 ++ .../PeopleMainMenuBarExtension.php | 62 ++++++++++--------- .../menu/PhabricatorMainMenuBarExtension.php | 14 +++++ .../page/menu/PhabricatorMainMenuView.php | 58 +++++++++++++++-- 4 files changed, 106 insertions(+), 32 deletions(-) diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php index da438730cd..98f2cdbd51 100644 --- a/src/applications/files/controller/PhabricatorFileDataController.php +++ b/src/applications/files/controller/PhabricatorFileDataController.php @@ -10,6 +10,10 @@ final class PhabricatorFileDataController extends PhabricatorFileController { return false; } + public function shouldAllowPartialSessions() { + return true; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $this->phid = $request->getURIData('phid'); diff --git a/src/applications/people/engineextension/PeopleMainMenuBarExtension.php b/src/applications/people/engineextension/PeopleMainMenuBarExtension.php index cd1cffa7a2..01f1ba7cc1 100644 --- a/src/applications/people/engineextension/PeopleMainMenuBarExtension.php +++ b/src/applications/people/engineextension/PeopleMainMenuBarExtension.php @@ -9,6 +9,10 @@ final class PeopleMainMenuBarExtension return $viewer->isLoggedIn(); } + public function shouldAllowPartialSessions() { + return true; + } + public function getExtensionOrder() { return 1200; } @@ -65,42 +69,44 @@ final class PeopleMainMenuBarExtension $view = id(new PhabricatorActionListView()) ->setViewer($viewer); - $view->addAction( - id(new PhabricatorActionView()) - ->appendChild($user_view)); + if ($this->getIsFullSession()) { + $view->addAction( + id(new PhabricatorActionView()) + ->appendChild($user_view)); - $view->addAction( - id(new PhabricatorActionView()) - ->setType(PhabricatorActionView::TYPE_DIVIDER)); + $view->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Profile')) - ->setHref('/p/'.$viewer->getUsername().'/')); + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Profile')) + ->setHref('/p/'.$viewer->getUsername().'/')); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Settings')) - ->setHref('/settings/user/'.$viewer->getUsername().'/')); + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Settings')) + ->setHref('/settings/user/'.$viewer->getUsername().'/')); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Manage')) - ->setHref('/people/manage/'.$viewer->getID().'/')); + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Manage')) + ->setHref('/people/manage/'.$viewer->getID().'/')); - if ($application) { - $help_links = $application->getHelpMenuItems($viewer); - if ($help_links) { - foreach ($help_links as $link) { - $view->addAction($link); + if ($application) { + $help_links = $application->getHelpMenuItems($viewer); + if ($help_links) { + foreach ($help_links as $link) { + $view->addAction($link); + } } } - } - $view->addAction( - id(new PhabricatorActionView()) - ->addSigil('logout-item') - ->setType(PhabricatorActionView::TYPE_DIVIDER)); + $view->addAction( + id(new PhabricatorActionView()) + ->addSigil('logout-item') + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + } $view->addAction( id(new PhabricatorActionView()) diff --git a/src/view/page/menu/PhabricatorMainMenuBarExtension.php b/src/view/page/menu/PhabricatorMainMenuBarExtension.php index fede2fdb85..d893cccac9 100644 --- a/src/view/page/menu/PhabricatorMainMenuBarExtension.php +++ b/src/view/page/menu/PhabricatorMainMenuBarExtension.php @@ -5,6 +5,7 @@ abstract class PhabricatorMainMenuBarExtension extends Phobject { private $viewer; private $application; private $controller; + private $isFullSession; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -33,6 +34,15 @@ abstract class PhabricatorMainMenuBarExtension extends Phobject { return $this->controller; } + public function setIsFullSession($is_full_session) { + $this->isFullSession = $is_full_session; + return $this; + } + + public function getIsFullSession() { + return $this->isFullSession; + } + final public function getExtensionKey() { return $this->getPhobjectClassConstant('MAINMENUBARKEY'); } @@ -41,6 +51,10 @@ abstract class PhabricatorMainMenuBarExtension extends Phobject { return true; } + public function shouldAllowPartialSessions() { + return false; + } + public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { if (!$viewer->isLoggedIn()) { return false; diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index f9e4032d87..ba4bda41ea 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -46,7 +46,9 @@ final class PhabricatorMainMenuView extends AphrontView { $app_button = ''; $aural = null; - if ($viewer->isLoggedIn() && $viewer->isUserActivated()) { + $is_full = $this->isFullSession($viewer); + + if ($is_full) { list($menu, $dropdowns, $aural) = $this->renderNotificationMenu(); if (array_filter($menu)) { $alerts[] = $menu; @@ -54,14 +56,18 @@ final class PhabricatorMainMenuView extends AphrontView { $menu_bar = array_merge($menu_bar, $dropdowns); $app_button = $this->renderApplicationMenuButton(); $search_button = $this->renderSearchMenuButton($header_id); - } else { + } else if (!$viewer->isLoggedIn()) { $app_button = $this->renderApplicationMenuButton(); if (PhabricatorEnv::getEnvConfig('policy.allow-public')) { $search_button = $this->renderSearchMenuButton($header_id); } } - $search_menu = $this->renderPhabricatorSearchMenu(); + if ($search_button) { + $search_menu = $this->renderPhabricatorSearchMenu(); + } else { + $search_menu = null; + } if ($alerts) { $alerts = javelin_tag( @@ -84,7 +90,9 @@ final class PhabricatorMainMenuView extends AphrontView { $extensions = PhabricatorMainMenuBarExtension::getAllEnabledExtensions(); foreach ($extensions as $extension) { - $extension->setViewer($viewer); + $extension + ->setViewer($viewer) + ->setIsFullSession($is_full); $controller = $this->getController(); if ($controller) { @@ -96,6 +104,14 @@ final class PhabricatorMainMenuView extends AphrontView { } } + if (!$is_full) { + foreach ($extensions as $key => $extension) { + if (!$extension->shouldAllowPartialSessions()) { + unset($extensions[$key]); + } + } + } + foreach ($extensions as $key => $extension) { if (!$extension->isExtensionEnabledForViewer($extension->getViewer())) { unset($extensions[$key]); @@ -677,4 +693,38 @@ final class PhabricatorMainMenuView extends AphrontView { ); } + private function isFullSession(PhabricatorUser $viewer) { + if (!$viewer->isLoggedIn()) { + return false; + } + + if (!$viewer->isUserActivated()) { + return false; + } + + if (!$viewer->hasSession()) { + return false; + } + + $session = $viewer->getSession(); + if ($session->getIsPartial()) { + return false; + } + + if (!$session->getSignedLegalpadDocuments()) { + return false; + } + + $mfa_key = 'security.require-multi-factor-auth'; + $need_mfa = PhabricatorEnv::getEnvConfig($mfa_key); + if ($need_mfa) { + $have_mfa = $viewer->getIsEnrolledInMultiFactor(); + if (!$have_mfa) { + return false; + } + } + + return true; + } + } From c7d6fd198cc98cf8b860288031eb4274bdcd94e0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 15:11:06 -0800 Subject: [PATCH 59/72] Support "Set X to" as an action in Herald for tokenizer/datasource custom fields Summary: See PHI173. Adds custom field support for Herald actions, and implements actions for "Datasource/Tokenizer" fields. The only action available for now is "set field to...". Other actions ("Add values", "Remove values") might make sense in the future for these fields, but there's currently no use case. For most other field types (text, select, checkbox, etc) only "Set to" makes sense. Test Plan: - Added a "datasource" custom field to the custom field definition in Config. - Added a "if field is empty, set field to default value X" rule to Herald. - Created a task with a nonempty field: no Herald trigger. - Created a task with an empty field: Herald fired. - Reviewed rule and transcripts for text strings. {F5297615} {F5297616} {F5297617} Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18784 --- src/__phutil_library_map__.php | 4 + .../field/PhabricatorCustomField.php | 55 +++++++++ .../PhabricatorCustomFieldHeraldAction.php | 105 ++++++++++++++++++ ...habricatorCustomFieldHeraldActionGroup.php | 16 +++ ...habricatorStandardCustomFieldTokenizer.php | 36 ++++++ 5 files changed, 216 insertions(+) create mode 100644 src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php create mode 100644 src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 424d72dc72..c4890116cf 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2535,6 +2535,8 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldEditField' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php', 'PhabricatorCustomFieldEditType' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php', 'PhabricatorCustomFieldFulltextEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php', + 'PhabricatorCustomFieldHeraldAction' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php', + 'PhabricatorCustomFieldHeraldActionGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php', 'PhabricatorCustomFieldHeraldField' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldField.php', 'PhabricatorCustomFieldHeraldFieldGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldFieldGroup.php', 'PhabricatorCustomFieldImplementationIncompleteException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldImplementationIncompleteException.php', @@ -7868,6 +7870,8 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldEditField' => 'PhabricatorEditField', 'PhabricatorCustomFieldEditType' => 'PhabricatorEditType', 'PhabricatorCustomFieldFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', + 'PhabricatorCustomFieldHeraldAction' => 'HeraldAction', + 'PhabricatorCustomFieldHeraldActionGroup' => 'HeraldActionGroup', 'PhabricatorCustomFieldHeraldField' => 'HeraldField', 'PhabricatorCustomFieldHeraldFieldGroup' => 'HeraldFieldGroup', 'PhabricatorCustomFieldImplementationIncompleteException' => 'Exception', diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php index fba299fc8b..f7f4403402 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -34,6 +34,7 @@ abstract class PhabricatorCustomField extends Phobject { const ROLE_CONDUIT = 'conduit'; const ROLE_HERALD = 'herald'; const ROLE_EDITENGINE = 'EditEngine'; + const ROLE_HERALDACTION = 'herald.action'; /* -( Building Applications with Custom Fields )--------------------------- */ @@ -293,6 +294,8 @@ abstract class PhabricatorCustomField extends Phobject { return $this->shouldAppearInTransactionMail(); case self::ROLE_HERALD: return $this->shouldAppearInHerald(); + case self::ROLE_HERALDACTION: + return $this->shouldAppearInHeraldActions(); case self::ROLE_EDITENGINE: return $this->shouldAppearInEditView() || $this->shouldAppearInEditEngine(); @@ -1476,4 +1479,56 @@ abstract class PhabricatorCustomField extends Phobject { } + public function shouldAppearInHeraldActions() { + if ($this->proxy) { + return $this->proxy->shouldAppearInHeraldActions(); + } + return false; + } + + + public function getHeraldActionName() { + if ($this->proxy) { + return $this->proxy->getHeraldActionName(); + } + + return null; + } + + + public function getHeraldActionStandardType() { + if ($this->proxy) { + return $this->proxy->getHeraldActionStandardType(); + } + + return null; + } + + + public function getHeraldActionDescription($value) { + if ($this->proxy) { + return $this->proxy->getHeraldActionDescription($value); + } + + return null; + } + + + public function getHeraldActionEffectDescription($value) { + if ($this->proxy) { + return $this->proxy->getHeraldActionEffectDescription($value); + } + + return null; + } + + + public function getHeraldActionDatasource() { + if ($this->proxy) { + return $this->proxy->getHeraldActionDatasource(); + } + + return null; + } + } diff --git a/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php b/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php new file mode 100644 index 0000000000..1d35ba59a2 --- /dev/null +++ b/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php @@ -0,0 +1,105 @@ +customField = $custom_field; + return $this; + } + + public function getCustomField() { + return $this->customField; + } + + public function getActionGroupKey() { + return PhabricatorCustomFieldHeraldActionGroup::ACTIONGROUPKEY; + } + + public function supportsObject($object) { + return ($object instanceof PhabricatorCustomFieldInterface); + } + + public function supportsRuleType($rule_type) { + return true; + } + + public function getActionsForObject($object) { + $viewer = PhabricatorUser::getOmnipotentUser(); + $role = PhabricatorCustomField::ROLE_HERALDACTION; + + $field_list = PhabricatorCustomField::getObjectFields($object, $role) + ->setViewer($viewer) + ->readFieldsFromStorage($object); + + $map = array(); + foreach ($field_list->getFields() as $field) { + $key = $field->getFieldKey(); + $map[$key] = id(new self()) + ->setCustomField($field); + } + + return $map; + } + + public function applyEffect($object, HeraldEffect $effect) { + $field = $this->getCustomField(); + $value = $effect->getTarget(); + $adapter = $this->getAdapter(); + + $old_value = $field->getOldValueForApplicationTransactions(); + $new_value = id(clone $field) + ->setValueFromApplicationTransactions($value) + ->getValueForStorage(); + + $xaction = $adapter->newTransaction() + ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) + ->setMetadataValue('customfield:key', $field->getFieldKey()) + ->setOldValue($old_value) + ->setNewValue($new_value); + + $adapter->queueTransaction($xaction); + + $this->logEffect(self::DO_SET_FIELD, $value); + } + + public function getHeraldActionName() { + return $this->getCustomField()->getHeraldActionName(); + } + + public function getHeraldActionStandardType() { + return $this->getCustomField()->getHeraldActionStandardType(); + } + + protected function getDatasource() { + return $this->getCustomField()->getHeraldActionDatasource(); + } + + public function renderActionDescription($value) { + return $this->getCustomField()->getHeraldActionDescription($value); + } + + protected function getActionEffectMap() { + return array( + self::DO_SET_FIELD => array( + 'icon' => 'fa-pencil', + 'color' => 'green', + 'name' => pht('Set Field Value'), + ), + ); + } + + protected function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_SET_FIELD: + return $this->getCustomField()->getHeraldActionEffectDescription($data); + } + } + + +} diff --git a/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php b/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php new file mode 100644 index 0000000000..b8f16ab202 --- /dev/null +++ b/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php @@ -0,0 +1,16 @@ +getFieldName()); + } + + public function getHeraldActionDescription($value) { + $list = $this->renderHeraldHandleList($value); + return pht('Set "%s" to: %s.', $this->getFieldName(), $list); + } + + public function getHeraldActionEffectDescription($value) { + return $this->renderHeraldHandleList($value); + } + + public function getHeraldActionStandardType() { + return HeraldAction::STANDARD_PHID_LIST; + } + + public function getHeraldActionDatasource() { + return $this->getDatasource(); + } + + private function renderHeraldHandleList($value) { + if (!is_array($value)) { + return pht('(Invalid List)'); + } else { + return $this->getViewer() + ->renderHandleList($value) + ->setAsInline(true) + ->render(); + } + } + } From 54f131dc4baad39f54c164ea57135151c4d38542 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 28 Nov 2017 10:42:16 -0800 Subject: [PATCH 60/72] Make "first broadcast" rules for Differential drafts more general Summary: See PHI228. Ref T2543. The current logic gets this slightly wrong: prototypes are off, you create a draft with `--draft`, then promote it with "Request Review". This misses both branches. Instead, test these conditions a little more broadly. We also need to store broadcast state since `getIsNewObject()` isn't good enough with this workflow. Test Plan: - With prototypes on and autopromotion, got a rich email after builds finished. - With prototypes off, got a rich email immediately. - With prototypes off and `--draft`, got a rich email after "Request Review". Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18801 --- .../editor/DifferentialTransactionEditor.php | 36 ++++++++++++++----- .../storage/DifferentialRevision.php | 10 ++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 7cea29359f..ae652ba0ec 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -10,6 +10,7 @@ final class DifferentialTransactionEditor private $hasReviewTransaction = false; private $affectedPaths; private $firstBroadcast = false; + private $wasDraft = false; public function getEditorApplicationClass() { return 'PhabricatorDifferentialApplication'; @@ -166,6 +167,8 @@ final class DifferentialTransactionEditor } } + $this->wasDraft = $object->isDraft(); + return parent::expandTransactions($object, $xactions); } @@ -1581,10 +1584,6 @@ final class DifferentialTransactionEditor $this->setActingAsPHID($author_phid); } - // Mark this as the first broadcast we're sending about the revision - // so mail can generate specially. - $this->firstBroadcast = true; - $xaction = $object->getApplicationTransactionTemplate() ->setAuthorPHID($author_phid) ->setTransactionType( @@ -1612,12 +1611,31 @@ final class DifferentialTransactionEditor $xactions[] = $xaction; } - } else { - // If this revision is being created into some state other than "Draft", - // this is the first broadcast and should include sections like "SUMMARY" - // and "TEST PLAN". - if ($this->getIsNewObject()) { + } + + // If the revision is new or was a draft, and is no longer a draft, we + // might be sending the first email about it. + + // This might mean it was created directly into a non-draft state, or + // it just automatically undrafted after builds finished, or a user + // explicitly promoted it out of the draft state with an action like + // "Request Review". + + // If we haven't sent any email about it yet, mark this email as the first + // email so the mail gets enriched with "SUMMARY" and "TEST PLAN". + + $is_new = $this->getIsNewObject(); + $was_draft = $this->wasDraft; + + if (!$object->isDraft() && ($was_draft || $is_new)) { + if (!$object->getHasBroadcast()) { + // Mark this as the first broadcast we're sending about the revision + // so mail can generate specially. $this->firstBroadcast = true; + + $object + ->setHasBroadcast(true) + ->save(); } } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index bf4bec0abc..73f22c91e4 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -60,6 +60,7 @@ final class DifferentialRevision extends DifferentialDAO const PROPERTY_CLOSED_FROM_ACCEPTED = 'wasAcceptedBeforeClose'; const PROPERTY_DRAFT_HOLD = 'draft.hold'; + const PROPERTY_HAS_BROADCAST = 'draft.broadcast'; public static function initializeNewRevision(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) @@ -719,6 +720,15 @@ final class DifferentialRevision extends DifferentialDAO return $this->setProperty(self::PROPERTY_DRAFT_HOLD, $hold); } + public function getHasBroadcast() { + return $this->getProperty(self::PROPERTY_HAS_BROADCAST, false); + } + + public function setHasBroadcast($has_broadcast) { + return $this->setProperty(self::PROPERTY_HAS_BROADCAST, $has_broadcast); + } + + public function loadActiveBuilds(PhabricatorUser $viewer) { $diff = $this->getActiveDiff(); From 1b76250f8995744c016c4b8149263e0bc4727b73 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 28 Nov 2017 11:05:15 -0800 Subject: [PATCH 61/72] Disallow "Accept" on drafts, allow "Resign" Summary: Depends on D18801. Ref T2543. See PHI229. I missed "Accept" before, but intended to disallow it (like "Reject") since I don't want drafts to be reviewable. However, "Resign" seems fine to allow? So let's allow that for now. Test Plan: Was no longer offered "Accept" on draft revisions. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18802 --- .../xaction/DifferentialRevisionAcceptTransaction.php | 5 +++++ .../xaction/DifferentialRevisionResignTransaction.php | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php index d52e2dbeb7..190a6a4920 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php @@ -162,6 +162,11 @@ final class DifferentialRevisionAcceptTransaction 'closed. Only open revisions can be accepted.')); } + if ($object->isDraft()) { + throw new Exception( + pht('You can not accept a draft revision.')); + } + $config_key = 'differential.allow-self-accept'; if (!PhabricatorEnv::getEnvConfig($config_key)) { if ($this->isViewerRevisionAuthor($object, $viewer)) { diff --git a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php index 40a512202d..53b0b8da01 100644 --- a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php @@ -64,11 +64,6 @@ final class DifferentialRevisionResignTransaction 'been closed. You can only resign from open revisions.')); } - if ($object->isDraft()) { - throw new Exception( - pht('You can not resign from a draft revision.')); - } - $resigned = DifferentialReviewerStatus::STATUS_RESIGNED; if ($this->getViewerReviewerStatus($object, $viewer) == $resigned) { throw new Exception( From d8f2630d5c0f9eb5221964944e45b9197753a988 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Thu, 30 Nov 2017 15:07:49 +0000 Subject: [PATCH 62/72] Modernize QuickSearch typeahead Summary: Use ClassQuery to find datasources for the quick-search. Mostly, this allows extensions to add quicksearches. Test Plan: using `/typeahead/class/`, tested several search terms that make sense. Removed the tag interface from a datasource, which removed it from results. Reviewers: epriestley, amckinley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18760 --- src/__phutil_library_map__.php | 14 ++++++++++++++ .../DiffusionQuickSearchEngineExtension.php | 12 ++++++++++++ ...orQuickSearchApplicationEngineExtension.php | 11 +++++++++++ ...ricatorPeopleQuickSearchEngineExtension.php | 11 +++++++++++ .../ProjectQuickSearchEngineExtension.php | 11 +++++++++++ .../engine/PhabricatorQuickSearchEngine.php | 8 ++++++++ .../PhabricatorQuickSearchEngineExtension.php | 18 ++++++++++++++++++ .../typeahead/PhabricatorSearchDatasource.php | 10 ++-------- ...catorMonogramQuickSearchEngineExtension.php | 11 +++++++++++ 9 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php create mode 100644 src/applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php create mode 100644 src/applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php create mode 100644 src/applications/project/engineextension/ProjectQuickSearchEngineExtension.php create mode 100644 src/applications/search/engine/PhabricatorQuickSearchEngine.php create mode 100644 src/applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php create mode 100644 src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c4890116cf..53bbf9a749 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -831,6 +831,7 @@ phutil_register_library_map(array( 'DiffusionQueryCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php', 'DiffusionQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php', 'DiffusionQueryPathsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php', + 'DiffusionQuickSearchEngineExtension' => 'applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php', 'DiffusionRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionRawDiffQuery.php', 'DiffusionRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRawDiffQueryConduitAPIMethod.php', 'DiffusionReadmeView' => 'applications/diffusion/view/DiffusionReadmeView.php', @@ -3202,6 +3203,7 @@ phutil_register_library_map(array( 'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php', 'PhabricatorModularTransaction' => 'applications/transactions/storage/PhabricatorModularTransaction.php', 'PhabricatorModularTransactionType' => 'applications/transactions/storage/PhabricatorModularTransactionType.php', + 'PhabricatorMonogramQuickSearchEngineExtension' => 'applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php', 'PhabricatorMonospacedFontSetting' => 'applications/settings/setting/PhabricatorMonospacedFontSetting.php', 'PhabricatorMonospacedTextareasSetting' => 'applications/settings/setting/PhabricatorMonospacedTextareasSetting.php', 'PhabricatorMotivatorProfileMenuItem' => 'applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php', @@ -3525,6 +3527,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileTasksController' => 'applications/people/controller/PhabricatorPeopleProfileTasksController.php', 'PhabricatorPeopleProfileViewController' => 'applications/people/controller/PhabricatorPeopleProfileViewController.php', 'PhabricatorPeopleQuery' => 'applications/people/query/PhabricatorPeopleQuery.php', + 'PhabricatorPeopleQuickSearchEngineExtension' => 'applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php', 'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php', 'PhabricatorPeopleRevisionsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php', 'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php', @@ -3785,6 +3788,9 @@ phutil_register_library_map(array( 'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php', 'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php', 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', + 'PhabricatorQuickSearchApplicationEngineExtension' => 'applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php', + 'PhabricatorQuickSearchEngine' => 'applications/search/engine/PhabricatorQuickSearchEngine.php', + 'PhabricatorQuickSearchEngineExtension' => 'applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php', 'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php', 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', @@ -4817,6 +4823,7 @@ phutil_register_library_map(array( 'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php', 'ProjectEditConduitAPIMethod' => 'applications/project/conduit/ProjectEditConduitAPIMethod.php', 'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php', + 'ProjectQuickSearchEngineExtension' => 'applications/project/engineextension/ProjectQuickSearchEngineExtension.php', 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', 'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php', 'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php', @@ -5880,6 +5887,7 @@ phutil_register_library_map(array( 'DiffusionQueryCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryPathsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', + 'DiffusionQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'DiffusionRawDiffQuery' => 'DiffusionFileFutureQuery', 'DiffusionRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionReadmeView' => 'DiffusionView', @@ -8612,6 +8620,7 @@ phutil_register_library_map(array( 'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorModularTransactionType' => 'Phobject', + 'PhabricatorMonogramQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'PhabricatorMonospacedFontSetting' => 'PhabricatorStringSetting', 'PhabricatorMonospacedTextareasSetting' => 'PhabricatorSelectSetting', 'PhabricatorMotivatorProfileMenuItem' => 'PhabricatorProfileMenuItem', @@ -9004,6 +9013,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileTasksController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileViewController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorPeopleQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController', 'PhabricatorPeopleRevisionsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine', @@ -9319,6 +9329,9 @@ phutil_register_library_map(array( 'Phobject', 'Iterator', ), + 'PhabricatorQuickSearchApplicationEngineExtension' => 'PhabricatorQuickSearchEngineExtension', + 'PhabricatorQuickSearchEngine' => 'Phobject', + 'PhabricatorQuickSearchEngineExtension' => 'Phobject', 'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRedirectController' => 'PhabricatorController', @@ -10605,6 +10618,7 @@ phutil_register_library_map(array( 'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability', 'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod', + 'ProjectQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase', 'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', diff --git a/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php new file mode 100644 index 0000000000..80c705b4f8 --- /dev/null +++ b/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php @@ -0,0 +1,12 @@ +setAncestorClass(__CLASS__) + ->execute(); + + $datasources = array(); + foreach ($extensions as $extension) { + $datasources[] = $extension->newQuickSearchDatasources(); + } + return array_mergev($datasources); + } +} diff --git a/src/applications/search/typeahead/PhabricatorSearchDatasource.php b/src/applications/search/typeahead/PhabricatorSearchDatasource.php index 2a17ad01eb..ed17a31185 100644 --- a/src/applications/search/typeahead/PhabricatorSearchDatasource.php +++ b/src/applications/search/typeahead/PhabricatorSearchDatasource.php @@ -16,14 +16,8 @@ final class PhabricatorSearchDatasource } public function getComponentDatasources() { - $sources = array( - new PhabricatorPeopleDatasource(), - new PhabricatorProjectDatasource(), - new PhabricatorApplicationDatasource(), - new PhabricatorTypeaheadMonogramDatasource(), - new DiffusionRepositoryDatasource(), - new DiffusionSymbolDatasource(), - ); + $sources = id(new PhabricatorQuickSearchEngine()) + ->getAllDatasources(); // These results are always rendered in the full browse display mode, so // set the browse flag on all component sources. diff --git a/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php b/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php new file mode 100644 index 0000000000..078cb44685 --- /dev/null +++ b/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php @@ -0,0 +1,11 @@ + Date: Thu, 30 Nov 2017 08:13:10 -0800 Subject: [PATCH 63/72] Add an explicit warning in the Differential transaction log when users skip review Summary: Ref T10233. See PHI231. When users ignore the `arc land` prompt about bad revision states, make it explicitly clear in the transaction log that they broke the rules. You can currently figure this out by noticing that there's no "This revision is accepted and ready to land." message, but it's unrealistic to expect non-expert users to look for the //absence// of a message to indicate something, and this state change is often relevant. Test Plan: {F5302351} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T10233 Differential Revision: https://secure.phabricator.com/D18808 --- resources/celerity/map.php | 6 +-- src/__phutil_library_map__.php | 2 + .../DifferentialDiffExtractionEngine.php | 10 +++++ ...ferentialRevisionWrongStateTransaction.php | 41 +++++++++++++++++++ webroot/rsrc/css/phui/phui-timeline-view.css | 4 ++ 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 src/applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 78ae8bd461..0ea66bec26 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', - 'core.pkg.css' => '1a4e0c25', + 'core.pkg.css' => 'fdb27ef9', 'core.pkg.js' => '4c79d74f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -176,7 +176,7 @@ return array( 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => 'b4719c50', - 'rsrc/css/phui/phui-timeline-view.css' => 'f21db7ca', + 'rsrc/css/phui/phui-timeline-view.css' => 'e2ef62b1', 'rsrc/css/phui/phui-two-column-view.css' => '44ec4951', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', @@ -871,7 +871,7 @@ return array( 'phui-status-list-view-css' => 'd5263e49', 'phui-tag-view-css' => 'b4719c50', 'phui-theme-css' => '9f261c6b', - 'phui-timeline-view-css' => 'f21db7ca', + 'phui-timeline-view-css' => 'e2ef62b1', 'phui-two-column-view-css' => '44ec4951', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 53bbf9a749..11be8a50f0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -594,6 +594,7 @@ phutil_register_library_map(array( 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/DifferentialRevisionUpdateHistoryView.php', 'DifferentialRevisionViewController' => 'applications/differential/controller/DifferentialRevisionViewController.php', 'DifferentialRevisionVoidTransaction' => 'applications/differential/xaction/DifferentialRevisionVoidTransaction.php', + 'DifferentialRevisionWrongStateTransaction' => 'applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php', 'DifferentialSchemaSpec' => 'applications/differential/storage/DifferentialSchemaSpec.php', 'DifferentialSetDiffPropertyConduitAPIMethod' => 'applications/differential/conduit/DifferentialSetDiffPropertyConduitAPIMethod.php', 'DifferentialStoredCustomField' => 'applications/differential/customfield/DifferentialStoredCustomField.php', @@ -5647,6 +5648,7 @@ phutil_register_library_map(array( 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'DifferentialRevisionVoidTransaction' => 'DifferentialRevisionTransactionType', + 'DifferentialRevisionWrongStateTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DifferentialSetDiffPropertyConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialStoredCustomField' => 'DifferentialCustomField', diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php index 952c76c63f..c426c411e7 100644 --- a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -268,6 +268,16 @@ final class DifferentialDiffExtractionEngine extends Phobject { $xactions = array(); + // If the revision isn't closed or "Accepted", write a warning into the + // transaction log. This makes it more clear when users bend the rules. + if (!$revision->isClosed() && !$revision->isAccepted()) { + $wrong_type = DifferentialRevisionWrongStateTransaction::TRANSACTIONTYPE; + + $xactions[] = id(new DifferentialTransaction()) + ->setTransactionType($wrong_type) + ->setNewValue($revision->getModernRevisionStatus()); + } + $xactions[] = id(new DifferentialTransaction()) ->setTransactionType(DifferentialTransaction::TYPE_UPDATE) ->setIgnoreOnNoEffect(true) diff --git a/src/applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php b/src/applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php new file mode 100644 index 0000000000..e19e54199c --- /dev/null +++ b/src/applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php @@ -0,0 +1,41 @@ +getNewValue(); + + $status = DifferentialRevisionStatus::newForStatus($new_value); + + return pht( + 'This revision was not accepted when it landed; it landed in state %s.', + $this->renderValue($status->getDisplayName())); + } + + public function getTitleForFeed() { + return null; + } +} diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index 33e0f8ed0c..b0ae9c0044 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -315,6 +315,10 @@ background-color: {$violet}; } +.phui-timeline-icon-fill-pink { + background-color: {$pink}; +} + .phui-timeline-icon-fill-grey { background-color: #888; } From f786c86a6a428b580ccd5f2ce8c5df3bf7a19fa8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 30 Nov 2017 13:41:44 -0800 Subject: [PATCH 64/72] Don't require the "gd" extension be installed in order to run unit tests Summary: Ref T10405. If `gd` is not installed, a number of unit tests currently fail because they generate synthetic users and try to composite profile pictures for them. Instead, just fall back to a static default if `gd` is not available. Test Plan: Faked this pathway, created a new user, saw a default profile picture. Reviewers: amckinley, lpriestley, avivey Reviewed By: avivey Maniphest Tasks: T10405 Differential Revision: https://secure.phabricator.com/D18812 --- .../builtin/PhabricatorFilesComposeAvatarBuiltinFile.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php b/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php index 4e461ec3f5..fcd6f97601 100644 --- a/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php +++ b/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php @@ -58,6 +58,14 @@ final class PhabricatorFilesComposeAvatarBuiltinFile } private function composeImage($color, $image, $border) { + // If we don't have the GD extension installed, just return a static + // default profile image rather than trying to compose a dynamic one. + if (!function_exists('imagecreatefromstring')) { + $root = dirname(phutil_get_library_root('phabricator')); + $default_path = $root.'/resources/builtin/profile.png'; + return Filesystem::readFile($default_path); + } + $color_const = hexdec(trim($color, '#')); $true_border = self::rgba2gd($border); $image_map = self::getImageMap(); From 14cc0abeb37fd411aceed31db22a820537264fe5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 30 Nov 2017 12:13:07 -0800 Subject: [PATCH 65/72] Fix several safety issues with repository URIs Summary: See PHI234. Several issues here: - The warning about observing a repository in Read/Write mode checks the raw I/O type, not the effective I/O type. That means we can fail to warn if other URIs are set to "Default", and "Default" is "Read/Write" in practice. - There's just an actual typo which prevents the "Observe" version of this error from triggering properly. Additionally, add more forceful warnings that "Observe" and "Mirror" mean that you want to //replace// a repository with another one, not that we somehow merge branches selectively. It isn't necessarily obvious that "Observe" doesn't mean "merge/union", since the reasons it can't in the general case are somewhat subtle (conflicts between refs with the same names, detecting ref deletion). Test Plan: Read documentation. Hit the error locally by trying to "Observe" while in Read/Write mode: {F5302655} Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18810 --- .../diffusion/editor/DiffusionURIEditor.php | 4 ++-- src/docs/user/userguide/diffusion_uris.diviner | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php index 674efbc158..90ced865f0 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditor.php +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -347,11 +347,11 @@ final class DiffusionURIEditor continue; } - $io_type = $uri->getIoType(); + $io_type = $uri->getEffectiveIOType(); if ($io_type == PhabricatorRepositoryURI::IO_READWRITE) { if ($no_readwrite) { - $readwite_conflict = $uri; + $readwrite_conflict = $uri; break; } } diff --git a/src/docs/user/userguide/diffusion_uris.diviner b/src/docs/user/userguide/diffusion_uris.diviner index 140948f55e..a19d38d3a5 100644 --- a/src/docs/user/userguide/diffusion_uris.diviner +++ b/src/docs/user/userguide/diffusion_uris.diviner @@ -239,6 +239,11 @@ You can not add a URI in Observe mode if an existing builtin URI is in authorities: the observed remote copy and the hosted local copy. Take the other URI out of //Read/Write// mode first. +WARNING: If you observe a remote repository, the entire state of the working +copy that Phabricator maintains will be deleted and replaced with the state of +the remote. If some changes are present only in Phabricator's working copy, +they will be unrecoverably destroyed. + **Mirror**: Phabricator will push any changes made to this repository to the remote URI, keeping a read-only mirror hosted at that URI up to date. @@ -251,6 +256,11 @@ It is possible to mirror a repository to another repository that is also hosted by Phabricator by adding that other repository's URI, although this is silly and probably very rarely of any use. +WARNING: If you mirror to a remote repository, the entire state of that remote +will be replaced with the state of the working copy Phabricator maintains. If +some changes are present only in the remote, they will be unrecoverably +destroyed. + **None**: Phabricator will not fetch changes from or push changes to this URI. For builtin URIs, it will not let users fetch changes from or push changes to this URI. From 5240cffd9c20c4f759b4ab2327ec4f6fbe2063ab Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 30 Nov 2017 12:33:02 -0800 Subject: [PATCH 66/72] Fix an issue where Diffusion could fatal if the default branch was deleted Summary: See PHI234. In T12931 we improved the behavior of Diffusion when a repository's default branch is set to a branch that does not exist, but in T11823 the way refcursors work changed, and we can now get a cursor (just with no positions) back for a deleted branch. When we did, we didn't handle things gracefully. Test Plan: - Set default branch to a deleted branch, saw nice error instead of fatal. - Set default branch to a nonexistent branch which never existed, saw nice error. - Set default branch to existing "master", saw repository normally. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18811 --- .../controller/DiffusionRepositoryController.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index f64b72e1f8..35fce7f03d 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -46,8 +46,20 @@ final class DiffusionRepositoryController extends DiffusionController { ->withRepositoryPHIDs(array($repository->getPHID())) ->withRefTypes(array(PhabricatorRepositoryRefCursor::TYPE_BRANCH)) ->withRefNames(array($drequest->getBranch())) + ->needPositions(true) ->execute(); - if ($ref_cursors) { + + // It's possible that this branch previously existed, but has been + // deleted. Make sure we have valid cursor positions, not just cursors. + $any_positions = false; + foreach ($ref_cursors as $ref_cursor) { + if ($ref_cursor->getPositions()) { + $any_positions = true; + break; + } + } + + if ($any_positions) { // This is a valid branch, so we necessarily have some content. $page_has_content = true; } else { From 42034e6739eb2507839dda708963219c430c7e76 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 1 Dec 2017 18:16:26 +0000 Subject: [PATCH 67/72] Property list view on Diffusion commits should show build status but not Subscriptions, Projects, or Tokens Summary: Ref T13019, adds build status back to Diffusion commits Test Plan: Open a Diffusion commit that has a build status, property list view should show the build status, but not Subscriptions, Projects, or Tokens. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Maniphest Tasks: T13019 Differential Revision: https://secure.phabricator.com/D18813 --- .../diffusion/controller/DiffusionCommitController.php | 3 ++- .../project/events/PhabricatorProjectUIEventListener.php | 8 ++++++++ .../events/PhabricatorSubscriptionsUIEventListener.php | 8 ++++++++ .../tokens/event/PhabricatorTokenUIEventListener.php | 8 ++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index d7734856f0..cdce8702ca 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -438,7 +438,8 @@ final class DiffusionCommitController extends DiffusionController { $repository = $drequest->getRepository(); $view = id(new PHUIPropertyListView()) - ->setUser($this->getRequest()->getUser()); + ->setUser($this->getRequest()->getUser()) + ->setObject($commit); $edge_query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($commit_phid)) diff --git a/src/applications/project/events/PhabricatorProjectUIEventListener.php b/src/applications/project/events/PhabricatorProjectUIEventListener.php index afbc0aab4e..104084bbf7 100644 --- a/src/applications/project/events/PhabricatorProjectUIEventListener.php +++ b/src/applications/project/events/PhabricatorProjectUIEventListener.php @@ -8,8 +8,16 @@ final class PhabricatorProjectUIEventListener } public function handleEvent(PhutilEvent $event) { + $object = $event->getValue('object'); + switch ($event->getType()) { case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES: + // Hacky solution so that property list view on Diffusion + // commits shows build status, but not Projects, Subscriptions, + // or Tokens. + if ($object instanceof PhabricatorRepositoryCommit) { + return; + } $this->handlePropertyEvent($event); break; } diff --git a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php index 09b26819c0..5f371d69f3 100644 --- a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php +++ b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php @@ -9,11 +9,19 @@ final class PhabricatorSubscriptionsUIEventListener } public function handleEvent(PhutilEvent $event) { + $object = $event->getValue('object'); + switch ($event->getType()) { case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: $this->handleActionEvent($event); break; case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES: + // Hacky solution so that property list view on Diffusion + // commits shows build status, but not Projects, Subscriptions, + // or Tokens. + if ($object instanceof PhabricatorRepositoryCommit) { + return; + } $this->handlePropertyEvent($event); break; } diff --git a/src/applications/tokens/event/PhabricatorTokenUIEventListener.php b/src/applications/tokens/event/PhabricatorTokenUIEventListener.php index bbf3438b62..047c5504bf 100644 --- a/src/applications/tokens/event/PhabricatorTokenUIEventListener.php +++ b/src/applications/tokens/event/PhabricatorTokenUIEventListener.php @@ -9,11 +9,19 @@ final class PhabricatorTokenUIEventListener } public function handleEvent(PhutilEvent $event) { + $object = $event->getValue('object'); + switch ($event->getType()) { case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: $this->handleActionEvent($event); break; case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES: + // Hacky solution so that property list view on Diffusion + // commits shows build status, but not Projects, Subscriptions, + // or Tokens. + if ($object instanceof PhabricatorRepositoryCommit) { + return; + } $this->handlePropertyEvent($event); break; } From c924351a58e40fe391c587899e322be006e98c7f Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Dec 2017 16:11:58 -0800 Subject: [PATCH 68/72] Mark sessions as "signed all documents" when Legalpad has been uninstalled See PHI238. When an install uninstalls "Legalpad", we were incorrectly failing to mark sessions as "Signed All Required Documents" by bailing early. Test Plan: Uninstalled Legalpad, logged in. --- .../base/controller/PhabricatorController.php | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 0fee880378..5952a806ed 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -562,25 +562,25 @@ abstract class PhabricatorController extends AphrontController { return null; } + $must_sign_docs = array(); + $sign_docs = array(); + $legalpad_class = 'PhabricatorLegalpadApplication'; $legalpad_installed = PhabricatorApplication::isClassInstalledForViewer( $legalpad_class, $viewer); - if (!$legalpad_installed) { - return null; - } + if ($legalpad_installed) { + $sign_docs = id(new LegalpadDocumentQuery()) + ->setViewer($viewer) + ->withSignatureRequired(1) + ->needViewerSignatures(true) + ->setOrder('oldest') + ->execute(); - $sign_docs = id(new LegalpadDocumentQuery()) - ->setViewer($viewer) - ->withSignatureRequired(1) - ->needViewerSignatures(true) - ->setOrder('oldest') - ->execute(); - - $must_sign_docs = array(); - foreach ($sign_docs as $sign_doc) { - if (!$sign_doc->getUserSignature($viewer->getPHID())) { - $must_sign_docs[] = $sign_doc; + foreach ($sign_docs as $sign_doc) { + if (!$sign_doc->getUserSignature($viewer->getPHID())) { + $must_sign_docs[] = $sign_doc; + } } } From 46d496b8cc061db11954bcd2c03b882a5573eaad Mon Sep 17 00:00:00 2001 From: lkassianik Date: Tue, 5 Dec 2017 19:24:57 +0000 Subject: [PATCH 69/72] Fix error for URL's that could mean several commits Summary: Ref T13001, URLs that return multiple commits should show a list of those commits. Not sure if the actual list looks very pretty this way, but was wondering if this approach was vaguely correct. Test Plan: - Navigate to `install/rPbd3c23` - User should see a list view providing links to `install/rPbd3c2355e8e2b220ae5e3cbfe4a057c8088c6a38` and `install/rPbd3c239d5aada68a31db5742bbb8ec099074a561` Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Maniphest Tasks: T13001 Differential Revision: https://secure.phabricator.com/D18816 --- .../controller/DiffusionCommitController.php | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index cdce8702ca..11463ef97d 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -39,20 +39,23 @@ final class DiffusionCommitController extends DiffusionController { return $this->buildRawDiffResponse($drequest); } - $commit = id(new DiffusionCommitQuery()) + $commits = id(new DiffusionCommitQuery()) ->setViewer($viewer) ->withRepository($repository) ->withIdentifiers(array($commit_identifier)) ->needCommitData(true) ->needAuditRequests(true) - ->executeOne(); + ->setLimit(100) + ->execute(); + + $multiple_results = count($commits) > 1; $crumbs = $this->buildCrumbs(array( - 'commit' => true, + 'commit' => !$multiple_results, )); $crumbs->setBorder(true); - if (!$commit) { + if (!$commits) { if (!$this->getCommitExists()) { return new Aphront404Response(); } @@ -70,7 +73,40 @@ final class DiffusionCommitController extends DiffusionController { ->setTitle($title) ->setCrumbs($crumbs) ->appendChild($error); + } else if ($multiple_results) { + $warning_message = + pht( + 'The identifier %s is ambiguous and matches more than one commit.', + phutil_tag( + 'strong', + array(), + $commit_identifier)); + + $error = id(new PHUIInfoView()) + ->setTitle(pht('Ambiguous Commit')) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->appendChild($warning_message); + + $list = id(new DiffusionCommitListView()) + ->setViewer($viewer) + ->setCommits($commits) + ->setNoDataString(pht('No recent commits.')); + + $crumbs->addTextCrumb(pht('Ambiguous Commit')); + + $matched_commits = id(new PHUITwoColumnView()) + ->setFooter(array( + $error, + $list, + )); + + return $this->newPage() + ->setTitle(pht('Ambiguous Commit')) + ->setCrumbs($crumbs) + ->appendChild($matched_commits); + } else { + $commit = head($commits); } $audit_requests = $commit->getAudits(); From a989dd181dd1f1115d728595c2adb482784c4ce0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Dec 2017 05:35:06 -0800 Subject: [PATCH 70/72] Fix Mercurial commit history ordering Summary: See . In D18769, I rewrote this from using the `--branch` flag (which is unsafe and does not function on branches named `--config=x.y` and such). However, this rewrite accidentally changed the result order, which impacted Mercurial commit hisotry lists and graphs. Swap the order of the constraints so we get newest-to-oldest again, as expected. Test Plan: Viewed a Mercurial repository's history graph, saw sensible chronology after the patch. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18817 --- .../diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php index e36c0a124e..4c1d39e8c8 100644 --- a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php @@ -130,7 +130,7 @@ final class DiffusionHistoryQueryConduitAPIMethod } else { $path_arg = ''; $revset_arg = hgsprintf( - 'branch(%s) and reverse(ancestors(%s))', + 'reverse(ancestors(%s)) and branch(%s)', $drequest->getBranch(), $commit_hash); } From 4f8340c05f2bf51d6b86e1e589153fc82ddbb708 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Dec 2017 05:42:03 -0800 Subject: [PATCH 71/72] Restore the "Log In" menubar action Summary: See . In T13024, I rewrote the main menu bar to hide potentially sensitive items (like notification and message counts and saved search filters) until users fully log in. However, the "Log In" item got caught in this too. For clarity, rename `shouldAllowPartialSessions()` to `shouldRequireFullSession()` (since logged-out users don't have any session at all, so it would be a bit misleading to say that "Log In" "allows" a partial session). Then let "Log In" work again for logged-out users. (In most cases, users are prompted to log in when they take an action which requires them to be logged in -- like creating or editing an object, or adding comments -- so this item doesn't really need to exist. However, it aligns better with user expectations in many cases to have it present, and some reasonable operations like "Check if I have notifications/messages" don't have an obvious thing to click otherwise.) Test Plan: Viewed site in an incognito window, saw "Log In" button again. Browsed normally, saw normal menu. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18818 --- .../auth/extension/PhabricatorAuthMainMenuBarExtension.php | 4 ++++ .../people/engineextension/PeopleMainMenuBarExtension.php | 4 ++-- src/view/page/menu/PhabricatorMainMenuBarExtension.php | 4 ++-- src/view/page/menu/PhabricatorMainMenuView.php | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php b/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php index d9d251ab07..d9fb5d013b 100644 --- a/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php +++ b/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php @@ -9,6 +9,10 @@ final class PhabricatorAuthMainMenuBarExtension return true; } + public function shouldRequireFullSession() { + return false; + } + public function getExtensionOrder() { return 900; } diff --git a/src/applications/people/engineextension/PeopleMainMenuBarExtension.php b/src/applications/people/engineextension/PeopleMainMenuBarExtension.php index 01f1ba7cc1..bed9dde44e 100644 --- a/src/applications/people/engineextension/PeopleMainMenuBarExtension.php +++ b/src/applications/people/engineextension/PeopleMainMenuBarExtension.php @@ -9,8 +9,8 @@ final class PeopleMainMenuBarExtension return $viewer->isLoggedIn(); } - public function shouldAllowPartialSessions() { - return true; + public function shouldRequireFullSession() { + return false; } public function getExtensionOrder() { diff --git a/src/view/page/menu/PhabricatorMainMenuBarExtension.php b/src/view/page/menu/PhabricatorMainMenuBarExtension.php index d893cccac9..e66b1f2c7d 100644 --- a/src/view/page/menu/PhabricatorMainMenuBarExtension.php +++ b/src/view/page/menu/PhabricatorMainMenuBarExtension.php @@ -51,8 +51,8 @@ abstract class PhabricatorMainMenuBarExtension extends Phobject { return true; } - public function shouldAllowPartialSessions() { - return false; + public function shouldRequireFullSession() { + return true; } public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index ba4bda41ea..ad9510f348 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -106,7 +106,7 @@ final class PhabricatorMainMenuView extends AphrontView { if (!$is_full) { foreach ($extensions as $key => $extension) { - if (!$extension->shouldAllowPartialSessions()) { + if ($extension->shouldRequireFullSession()) { unset($extensions[$key]); } } From 6d3baa92f90813b77b2acbc1e6860ada5f28f60c Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Dec 2017 06:47:48 -0800 Subject: [PATCH 72/72] Execute Herald again after promoting revisions out of the "Draft" state Summary: Fixes T13027. Ref T2543. When revisions promote from "Draft" because builds finish or no builds are configured, the status currently switches from "Draft" to "Needs Review" without re-running Herald. This means that some rules -- notably, "Send me an email" rules -- don't fire as soon as they should. Instead of applying this promotion in a hacky way inline, queue it and apply it normally in a second edit, after the current group finishes. Test Plan: - Created a revision, reviewed Herald transcripts. - Saw three Herald passes: - First pass (revision creation) triggered builds and no email. - Second pass (builds finished) did not trigger builds (no update) and did not trigger email (revision still a draft). - Third pass (after promotion out of 'draft') did not trigger builds (no update) but did trigger email (revision no longer a draft). Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13027, T2543 Differential Revision: https://secure.phabricator.com/D18819 --- .../editor/DifferentialTransactionEditor.php | 16 ++------ ...entialRevisionRequestReviewTransaction.php | 14 ++++--- ...habricatorApplicationTransactionEditor.php | 39 +++++++++++++++++++ 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index ae652ba0ec..b2fc179002 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1588,11 +1588,8 @@ final class DifferentialTransactionEditor ->setAuthorPHID($author_phid) ->setTransactionType( DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE) - ->setOldValue(false) ->setNewValue(true); - $xaction = $this->populateTransaction($object, $xaction); - // If we're creating this revision and immediately moving it out of // the draft state, mark this as a create transaction so it gets // hidden in the timeline and mail, since it isn't interesting: it @@ -1601,15 +1598,10 @@ final class DifferentialTransactionEditor $xaction->setIsCreateTransaction(true); } - $object->openTransaction(); - $object - ->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW) - ->save(); - - $xaction->save(); - $object->saveTransaction(); - - $xactions[] = $xaction; + // Queue this transaction and apply it separately after the current + // batch of transactions finishes so that Herald can fire on the new + // revision state. See T13027 for discussion. + $this->queueTransaction($xaction); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php index 465b5234d5..ed0d20a0b6 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php @@ -57,11 +57,15 @@ final class DifferentialRevisionRequestReviewTransaction 'revisions.')); } - if (!$this->isViewerRevisionAuthor($object, $viewer)) { - throw new Exception( - pht( - 'You can not request review of this revision because you are not '. - 'the author of the revision.')); + // When revisions automatically promote out of "Draft" after builds finish, + // the viewer may be acting as the Harbormaster application. + if (!$viewer->isOmnipotent()) { + if (!$this->isViewerRevisionAuthor($object, $viewer)) { + throw new Exception( + pht( + 'You can not request review of this revision because you are not '. + 'the author of the revision.')); + } } } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 175a5775ee..8e6d816769 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -70,6 +70,8 @@ abstract class PhabricatorApplicationTransactionEditor private $feedRelatedPHIDs = array(); private $modularTypes; + private $transactionQueue = array(); + const STORAGE_ENCODING_BINARY = 'binary'; /** @@ -1174,6 +1176,8 @@ abstract class PhabricatorApplicationTransactionEditor 'priority' => PhabricatorWorker::PRIORITY_ALERTS, )); + $this->flushTransactionQueue($object); + return $xactions; } @@ -3864,4 +3868,39 @@ abstract class PhabricatorApplicationTransactionEditor return pht('%s created an object: %s.', $author, $object); } +/* -( Queue )-------------------------------------------------------------- */ + + protected function queueTransaction( + PhabricatorApplicationTransaction $xaction) { + $this->transactionQueue[] = $xaction; + return $this; + } + + private function flushTransactionQueue($object) { + if (!$this->transactionQueue) { + return; + } + + $xactions = $this->transactionQueue; + $this->transactionQueue = array(); + + $editor = $this->newQueueEditor(); + + return $editor->applyTransactions($object, $xactions); + } + + private function newQueueEditor() { + $editor = id(newv(get_class($this), array())) + ->setActor($this->getActor()) + ->setContentSource($this->getContentSource()) + ->setContinueOnNoEffect($this->getContinueOnNoEffect()) + ->setContinueOnMissingFields($this->getContinueOnMissingFields()); + + if ($this->actingAsPHID !== null) { + $editor->setActingAsPHID($this->actingAsPHID); + } + + return $editor; + } + }