Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/differential/constants/action/DifferentialAction.php b/src/applications/differential/constants/action/DifferentialAction.php
index c39d10803e..5cc6c38944 100644
--- a/src/applications/differential/constants/action/DifferentialAction.php
+++ b/src/applications/differential/constants/action/DifferentialAction.php
@@ -1,84 +1,85 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class DifferentialAction {
const ACTION_COMMIT = 'commit';
const ACTION_COMMENT = 'none';
const ACTION_ACCEPT = 'accept';
const ACTION_REJECT = 'reject';
const ACTION_RETHINK = 'rethink';
const ACTION_ABANDON = 'abandon';
const ACTION_REQUEST = 'request_review';
const ACTION_RECLAIM = 'reclaim';
const ACTION_UPDATE = 'update';
const ACTION_RESIGN = 'resign';
const ACTION_SUMMARIZE = 'summarize';
const ACTION_TESTPLAN = 'testplan';
const ACTION_CREATE = 'create';
const ACTION_ADDREVIEWERS = 'add_reviewers';
const ACTION_ADDCCS = 'add_ccs';
public static function getActionPastTenseVerb($action) {
static $verbs = array(
self::ACTION_COMMENT => 'commented on',
self::ACTION_ACCEPT => 'accepted',
self::ACTION_REJECT => 'requested changes to',
self::ACTION_RETHINK => 'planned changes to',
self::ACTION_ABANDON => 'abandoned',
self::ACTION_COMMIT => 'committed',
self::ACTION_REQUEST => 'requested a review of',
self::ACTION_RECLAIM => 'reclaimed',
self::ACTION_UPDATE => 'updated',
self::ACTION_RESIGN => 'resigned from',
self::ACTION_SUMMARIZE => 'summarized',
self::ACTION_TESTPLAN => 'explained the test plan for',
self::ACTION_CREATE => 'created',
self::ACTION_ADDREVIEWERS => 'added reviewers to',
self::ACTION_ADDCCS => 'added CCs to',
);
if (!empty($verbs[$action])) {
return $verbs[$action];
} else {
return 'brazenly "'.$action.'ed"';
}
}
public static function getActionVerb($action) {
static $verbs = array(
self::ACTION_COMMENT => 'Comment',
self::ACTION_ACCEPT => "Accept Revision \xE2\x9C\x94",
self::ACTION_REJECT => "Request Changes \xE2\x9C\x98",
self::ACTION_RETHINK => "Plan Changes \xE2\x9C\x98",
self::ACTION_ABANDON => 'Abandon Revision',
self::ACTION_REQUEST => 'Request Review',
self::ACTION_RECLAIM => 'Reclaim Revision',
self::ACTION_RESIGN => 'Resign as Reviewer',
self::ACTION_ADDREVIEWERS => 'Add Reviewers',
self::ACTION_ADDCCS => 'Add CCs',
+ self::ACTION_COMMIT => 'Mark Committed',
);
if (!empty($verbs[$action])) {
return $verbs[$action];
} else {
return 'brazenly '.$action;
}
}
}
diff --git a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php
index 2b40f8dc24..e35c7370b7 100644
--- a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php
+++ b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php
@@ -1,677 +1,678 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class DifferentialRevisionViewController extends DifferentialController {
private $revisionID;
public function shouldRequireLogin() {
return !$this->allowsAnonymousAccess();
}
public function willProcessRequest(array $data) {
$this->revisionID = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$viewer_is_anonymous = !$user->isLoggedIn();
$revision = id(new DifferentialRevision())->load($this->revisionID);
if (!$revision) {
return new Aphront404Response();
}
$revision->loadRelationships();
$diffs = $revision->loadDiffs();
if (!$diffs) {
throw new Exception(
"This revision has no diffs. Something has gone quite wrong.");
}
$diff_vs = $request->getInt('vs');
$target = end($diffs);
$target_id = $request->getInt('id');
if ($target_id) {
if (isset($diffs[$target_id])) {
$target = $diffs[$target_id];
}
}
$diffs = mpull($diffs, null, 'getID');
if (empty($diffs[$diff_vs])) {
$diff_vs = null;
}
list($aux_fields, $props) = $this->loadAuxiliaryFieldsAndProperties(
$revision,
$target,
array(
'local:commits',
));
list($changesets, $vs_map, $rendering_references) =
$this->loadChangesetsAndVsMap($diffs, $diff_vs, $target);
$comments = $revision->loadComments();
$comments = array_merge(
$this->getImplicitComments($revision),
$comments);
$all_changesets = $changesets;
$inlines = $this->loadInlineComments($comments, $all_changesets);
$object_phids = array_merge(
$revision->getReviewers(),
$revision->getCCPHIDs(),
$revision->loadCommitPHIDs(),
array(
$revision->getAuthorPHID(),
$user->getPHID(),
),
mpull($comments, 'getAuthorPHID'));
foreach ($comments as $comment) {
$metadata = $comment->getMetadata();
$added_reviewers = idx(
$metadata,
DifferentialComment::METADATA_ADDED_REVIEWERS);
if ($added_reviewers) {
foreach ($added_reviewers as $phid) {
$object_phids[] = $phid;
}
}
$added_ccs = idx(
$metadata,
DifferentialComment::METADATA_ADDED_CCS);
if ($added_ccs) {
foreach ($added_ccs as $phid) {
$object_phids[] = $phid;
}
}
}
foreach ($revision->getAttached() as $type => $phids) {
foreach ($phids as $phid => $info) {
$object_phids[] = $phid;
}
}
$aux_phids = array();
foreach ($aux_fields as $key => $aux_field) {
$aux_phids[$key] = $aux_field->getRequiredHandlePHIDsForRevisionView();
}
$object_phids = array_merge($object_phids, array_mergev($aux_phids));
$object_phids = array_unique($object_phids);
$handles = id(new PhabricatorObjectHandleData($object_phids))
->loadHandles();
foreach ($aux_fields as $key => $aux_field) {
// Make sure each field only has access to handles it specifically
// requested, not all handles. Otherwise you can get a field which works
// only in the presence of other fields.
$aux_field->setHandles(array_select_keys($handles, $aux_phids[$key]));
}
$reviewer_warning = null;
$has_live_reviewer = false;
foreach ($revision->getReviewers() as $reviewer) {
if (!$handles[$reviewer]->isDisabled()) {
$has_live_reviewer = true;
}
}
if (!$has_live_reviewer) {
$reviewer_warning = new AphrontErrorView();
$reviewer_warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$reviewer_warning->setTitle('No Active Reviewers');
if ($revision->getReviewers()) {
$reviewer_warning->appendChild(
'<p>All specified reviewers are disabled. You may want to add '.
'some new reviewers.</p>');
} else {
$reviewer_warning->appendChild(
'<p>This revision has no specified reviewers. You may want to '.
'add some.</p>');
}
}
$request_uri = $request->getRequestURI();
$limit = 100;
$large = $request->getStr('large');
if (count($changesets) > $limit && !$large) {
$count = number_format(count($changesets));
$warning = new AphrontErrorView();
$warning->setTitle('Very Large Diff');
$warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$warning->setWidth(AphrontErrorView::WIDTH_WIDE);
$warning->appendChild(
"<p>This diff is very large and affects {$count} files. Use ".
"Table of Contents to open files in a standalone view. ".
"<strong>".
phutil_render_tag(
'a',
array(
'href' => $request_uri->alter('large', 'true'),
),
'Show All Files Inline').
"</strong>");
$warning = $warning->render();
$visible_changesets = array();
} else {
$warning = null;
$visible_changesets = $changesets;
}
$revision_detail = new DifferentialRevisionDetailView();
$revision_detail->setRevision($revision);
$revision_detail->setAuxiliaryFields($aux_fields);
$actions = $this->getRevisionActions($revision);
$custom_renderer_class = PhabricatorEnv::getEnvConfig(
'differential.revision-custom-detail-renderer');
if ($custom_renderer_class) {
// TODO: build a better version of the action links and deprecate the
// whole DifferentialRevisionDetailRenderer class.
PhutilSymbolLoader::loadClass($custom_renderer_class);
$custom_renderer =
newv($custom_renderer_class, array());
$actions = array_merge(
$actions,
$custom_renderer->generateActionLinks($revision, $target));
}
$whitespace = $request->getStr(
'whitespace',
DifferentialChangesetParser::WHITESPACE_IGNORE_ALL);
$arc_project = $target->loadArcanistProject();
if ($arc_project) {
$symbol_indexes = $this->buildSymbolIndexes(
$target,
$arc_project,
$visible_changesets);
$repository = $arc_project->loadRepository();
} else {
$symbol_indexes = array();
$repository = null;
}
$revision_detail->setActions($actions);
$revision_detail->setUser($user);
$comment_view = new DifferentialRevisionCommentListView();
$comment_view->setComments($comments);
$comment_view->setHandles($handles);
$comment_view->setInlineComments($inlines);
$comment_view->setChangesets($all_changesets);
$comment_view->setUser($user);
$comment_view->setTargetDiff($target);
$comment_view->setVersusDiffID($diff_vs);
$changeset_view = new DifferentialChangesetListView();
$changeset_view->setChangesets($visible_changesets);
$changeset_view->setEditable(!$viewer_is_anonymous);
$changeset_view->setStandaloneViews(true);
$changeset_view->setUser($user);
$changeset_view->setRevision($revision);
$changeset_view->setDiff($target);
$changeset_view->setRenderingReferences($rendering_references);
$changeset_view->setVsMap($vs_map);
$changeset_view->setWhitespace($whitespace);
if ($repository) {
$changeset_view->setRepository($repository, $target);
}
$changeset_view->setSymbolIndexes($symbol_indexes);
$diff_history = new DifferentialRevisionUpdateHistoryView();
$diff_history->setDiffs($diffs);
$diff_history->setSelectedVersusDiffID($diff_vs);
$diff_history->setSelectedDiffID($target->getID());
$diff_history->setSelectedWhitespace($whitespace);
$diff_history->setUser($user);
$local_view = new DifferentialLocalCommitsView();
$local_view->setUser($user);
$local_view->setLocalCommits(idx($props, 'local:commits'));
$toc_view = new DifferentialDiffTableOfContentsView();
$toc_view->setChangesets($changesets);
if ($repository) {
$toc_view->setRepository($repository);
}
$toc_view->setDiff($target);
$toc_view->setUser($user);
$toc_view->setStandaloneViewLink(empty($visible_changesets));
$toc_view->setVsMap($vs_map);
$toc_view->setRevisionID($revision->getID());
$toc_view->setWhitespace($whitespace);
if (!$viewer_is_anonymous) {
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
'differential-comment-'.$revision->getID());
if ($draft) {
$draft = $draft->getDraft();
} else {
$draft = null;
}
$comment_form = new DifferentialAddCommentView();
$comment_form->setRevision($revision);
$comment_form->setActions($this->getRevisionCommentActions($revision));
$comment_form->setActionURI('/differential/comment/save/');
$comment_form->setUser($user);
$comment_form->setDraft($draft);
}
$pane_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'differential-keyboard-navigation',
array(
'haunt' => $pane_id,
));
$page_pane = id(new DifferentialPrimaryPaneView())
->setLineWidthFromChangesets($changesets)
->setID($pane_id)
->appendChild($reviewer_warning)
->appendChild(
$revision_detail->render().
$comment_view->render().
$diff_history->render().
$warning.
$local_view->render().
$toc_view->render().
$changeset_view->render());
if ($comment_form) {
$page_pane->appendChild($comment_form->render());
}
return $this->buildStandardPageResponse(
$page_pane,
array(
'title' => 'D'.$revision->getID().' '.$revision->getTitle(),
));
}
private function getImplicitComments(DifferentialRevision $revision) {
$template = new DifferentialComment();
$template->setAuthorPHID($revision->getAuthorPHID());
$template->setRevisionID($revision->getID());
$template->setDateCreated($revision->getDateCreated());
$comments = array();
if (strlen($revision->getSummary())) {
$summary_comment = clone $template;
$summary_comment->setContent($revision->getSummary());
$summary_comment->setAction(DifferentialAction::ACTION_SUMMARIZE);
$comments[] = $summary_comment;
}
if (strlen($revision->getTestPlan())) {
$testplan_comment = clone $template;
$testplan_comment->setContent($revision->getTestPlan());
$testplan_comment->setAction(DifferentialAction::ACTION_TESTPLAN);
$comments[] = $testplan_comment;
}
return $comments;
}
private function getRevisionActions(DifferentialRevision $revision) {
$viewer_phid = $this->getRequest()->getUser()->getPHID();
$viewer_is_owner = ($revision->getAuthorPHID() == $viewer_phid);
$viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
$viewer_is_cc = in_array($viewer_phid, $revision->getCCPHIDs());
$viewer_is_anonymous = !$this->getRequest()->getUser()->isLoggedIn();
$status = $revision->getStatus();
$revision_id = $revision->getID();
$revision_phid = $revision->getPHID();
$links = array();
if ($viewer_is_owner) {
$links[] = array(
'class' => 'revision-edit',
'href' => "/differential/revision/edit/{$revision_id}/",
'name' => 'Edit Revision',
);
}
if (!$viewer_is_anonymous) {
if (!$viewer_is_owner && !$viewer_is_reviewer) {
$action = $viewer_is_cc ? 'rem' : 'add';
$links[] = array(
'class' => $viewer_is_cc ? 'subscribe-rem' : 'subscribe-add',
'href' => "/differential/subscribe/{$action}/{$revision_id}/",
'name' => $viewer_is_cc ? 'Unsubscribe' : 'Subscribe',
'instant' => true,
);
} else {
$links[] = array(
'class' => 'subscribe-rem unavailable',
'name' => 'Automatically Subscribed',
);
}
require_celerity_resource('phabricator-object-selector-css');
require_celerity_resource('javelin-behavior-phabricator-object-selector');
$links[] = array(
'class' => 'action-dependencies',
'name' => 'Edit Dependencies',
'href' => "/search/attach/{$revision_phid}/DREV/dependencies/",
'sigil' => 'workflow',
);
if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
$links[] = array(
'class' => 'attach-maniphest',
'name' => 'Edit Maniphest Tasks',
'href' => "/search/attach/{$revision_phid}/TASK/",
'sigil' => 'workflow',
);
}
$links[] = array(
'class' => 'transcripts-metamta',
'name' => 'MetaMTA Transcripts',
'href' => "/mail/?phid={$revision_phid}",
);
$links[] = array(
'class' => 'transcripts-herald',
'name' => 'Herald Transcripts',
'href' => "/herald/transcript/?phid={$revision_phid}",
);
}
return $links;
}
private function getRevisionCommentActions(DifferentialRevision $revision) {
$actions = array(
DifferentialAction::ACTION_COMMENT => true,
);
$admin_actions = array();
$viewer = $this->getRequest()->getUser();
$viewer_phid = $viewer->getPHID();
$viewer_is_admin = $viewer->getIsAdmin();
$viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID());
$viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
$viewer_did_accept = ($viewer_phid === $revision->loadReviewedBy());
if ($viewer_is_owner) {
switch ($revision->getStatus()) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_REQUEST] = true;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_REQUEST] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
+ $actions[DifferentialAction::ACTION_COMMIT] = true;
break;
case ArcanistDifferentialRevisionStatus::COMMITTED:
break;
case ArcanistDifferentialRevisionStatus::ABANDONED:
$actions[DifferentialAction::ACTION_RECLAIM] = true;
break;
}
} else {
switch ($revision->getStatus()) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$admin_actions[DifferentialAction::ACTION_ABANDON] = $viewer_is_admin;
$actions[DifferentialAction::ACTION_ACCEPT] = true;
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$admin_actions[DifferentialAction::ACTION_ABANDON] = $viewer_is_admin;
$actions[DifferentialAction::ACTION_ACCEPT] = true;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$admin_actions[DifferentialAction::ACTION_ABANDON] = $viewer_is_admin;
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] =
$viewer_is_reviewer && !$viewer_did_accept;
break;
case ArcanistDifferentialRevisionStatus::COMMITTED:
case ArcanistDifferentialRevisionStatus::ABANDONED:
break;
}
}
$actions[DifferentialAction::ACTION_ADDREVIEWERS] = true;
$actions[DifferentialAction::ACTION_ADDCCS] = true;
$actions = array_keys(array_filter($actions));
$admin_actions = array_keys(array_filter($admin_actions));
$actions_dict = array();
foreach ($actions as $action) {
$actions_dict[$action] = DifferentialAction::getActionVerb($action);
}
foreach ($admin_actions as $action) {
$actions_dict[$action] =
'(Admin) ' . DifferentialAction::getActionVerb($action);
}
return $actions_dict;
}
private function loadInlineComments(array $comments, array &$changesets) {
$inline_comments = array();
$comment_ids = array_filter(mpull($comments, 'getID'));
if (!$comment_ids) {
return $inline_comments;
}
$inline_comments = id(new DifferentialInlineComment())
->loadAllWhere(
'commentID in (%Ld)',
$comment_ids);
$load_changesets = array();
foreach ($inline_comments as $inline) {
$changeset_id = $inline->getChangesetID();
if (isset($changesets[$changeset_id])) {
continue;
}
$load_changesets[$changeset_id] = true;
}
$more_changesets = array();
if ($load_changesets) {
$changeset_ids = array_keys($load_changesets);
$more_changesets += id(new DifferentialChangeset())
->loadAllWhere(
'id IN (%Ld)',
$changeset_ids);
}
if ($more_changesets) {
$changesets += $more_changesets;
$changesets = msort($changesets, 'getSortKey');
}
return $inline_comments;
}
private function loadChangesetsAndVsMap(array $diffs, $diff_vs, $target) {
$load_ids = array();
if ($diff_vs) {
$load_ids[] = $diff_vs;
}
$load_ids[] = $target->getID();
$raw_changesets = id(new DifferentialChangeset())
->loadAllWhere(
'diffID IN (%Ld)',
$load_ids);
$changeset_groups = mgroup($raw_changesets, 'getDiffID');
$changesets = idx($changeset_groups, $target->getID(), array());
$changesets = mpull($changesets, null, 'getID');
$refs = array();
foreach ($changesets as $changeset) {
$refs[$changeset->getID()] = $changeset->getID();
}
$vs_map = array();
if ($diff_vs) {
$vs_changesets = idx($changeset_groups, $diff_vs, array());
$vs_changesets = mpull($vs_changesets, null, 'getFilename');
foreach ($changesets as $key => $changeset) {
$file = $changeset->getFilename();
if (isset($vs_changesets[$file])) {
$vs_map[$changeset->getID()] = $vs_changesets[$file]->getID();
$refs[$changeset->getID()] =
$changeset->getID().'/'.$vs_changesets[$file]->getID();
unset($vs_changesets[$file]);
} else {
$refs[$changeset->getID()] = $changeset->getID();
}
}
foreach ($vs_changesets as $changeset) {
$changesets[$changeset->getID()] = $changeset;
$vs_map[$changeset->getID()] = -1;
$refs[$changeset->getID()] = $changeset->getID().'/-1';
}
}
$changesets = msort($changesets, 'getSortKey');
return array($changesets, $vs_map, $refs);
}
private function loadAuxiliaryFieldsAndProperties(
DifferentialRevision $revision,
DifferentialDiff $diff,
array $special_properties) {
$aux_fields = DifferentialFieldSelector::newSelector()
->getFieldSpecifications();
foreach ($aux_fields as $key => $aux_field) {
if (!$aux_field->shouldAppearOnRevisionView()) {
unset($aux_fields[$key]);
}
}
$aux_fields = DifferentialAuxiliaryField::loadFromStorage(
$revision,
$aux_fields);
$aux_props = array();
foreach ($aux_fields as $key => $aux_field) {
$aux_field->setDiff($diff);
$aux_props[$key] = $aux_field->getRequiredDiffProperties();
}
$required_properties = array_mergev($aux_props);
$required_properties = array_merge(
$required_properties,
$special_properties);
$property_map = array();
if ($required_properties) {
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID = %d AND name IN (%Ls)',
$diff->getID(),
$required_properties);
$property_map = mpull($properties, 'getData', 'getName');
}
foreach ($aux_fields as $key => $aux_field) {
// Give each field only the properties it specifically required, and
// set 'null' for each requested key which we didn't actually load a
// value for (otherwise, getDiffProperty() will throw).
if ($aux_props[$key]) {
$props = array_select_keys($property_map, $aux_props[$key]) +
array_fill_keys($aux_props[$key], null);
} else {
$props = array();
}
$aux_field->setDiffProperties($props);
}
return array(
$aux_fields,
array_select_keys(
$property_map,
$special_properties));
}
private function buildSymbolIndexes(
DifferentialDiff $target,
PhabricatorRepositoryArcanistProject $arc_project,
array $visible_changesets) {
$engine = PhabricatorSyntaxHighlighter::newEngine();
$langs = $arc_project->getSymbolIndexLanguages();
if (!$langs) {
return array();
}
$symbol_indexes = array();
$project_phids = array_merge(
array($arc_project->getPHID()),
nonempty($arc_project->getSymbolIndexProjects(), array()));
$indexed_langs = array_fill_keys($langs, true);
foreach ($visible_changesets as $key => $changeset) {
$lang = $engine->getLanguageFromFilename($changeset->getFilename());
if (isset($indexed_langs[$lang])) {
$symbol_indexes[$key] = array(
'lang' => $lang,
'projects' => $project_phids,
);
}
}
return $symbol_indexes;
}
}
diff --git a/src/applications/differential/editor/comment/DifferentialCommentEditor.php b/src/applications/differential/editor/comment/DifferentialCommentEditor.php
index c6a6afa062..0ea5f93255 100644
--- a/src/applications/differential/editor/comment/DifferentialCommentEditor.php
+++ b/src/applications/differential/editor/comment/DifferentialCommentEditor.php
@@ -1,575 +1,583 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class DifferentialCommentEditor {
protected $revision;
protected $actorPHID;
protected $action;
protected $attachInlineComments;
protected $message;
protected $changedByCommit;
protected $addedReviewers = array();
private $addedCCs = array();
private $parentMessageID;
private $contentSource;
public function __construct(
DifferentialRevision $revision,
$actor_phid,
$action) {
$this->revision = $revision;
$this->actorPHID = $actor_phid;
$this->action = $action;
}
public function setParentMessageID($parent_message_id) {
$this->parentMessageID = $parent_message_id;
return $this;
}
public function setMessage($message) {
$this->message = $message;
return $this;
}
public function setAttachInlineComments($attach) {
$this->attachInlineComments = $attach;
return $this;
}
public function setChangedByCommit($changed_by_commit) {
$this->changedByCommit = $changed_by_commit;
return $this;
}
public function getChangedByCommit() {
return $this->changedByCommit;
}
public function setAddedReviewers($added_reviewers) {
$this->addedReviewers = $added_reviewers;
return $this;
}
public function getAddedReviewers() {
return $this->addedReviewers;
}
public function setAddedCCs($added_ccs) {
$this->addedCCs = $added_ccs;
return $this;
}
public function getAddedCCs() {
return $this->addedCCs;
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source;
return $this;
}
public function save() {
$revision = $this->revision;
$action = $this->action;
$actor_phid = $this->actorPHID;
$actor = id(new PhabricatorUser())->loadOneWhere('PHID = %s', $actor_phid);
$actor_is_author = ($actor_phid == $revision->getAuthorPHID());
$actor_is_admin = $actor->getIsAdmin();
$revision_status = $revision->getStatus();
$revision->loadRelationships();
$reviewer_phids = $revision->getReviewers();
if ($reviewer_phids) {
$reviewer_phids = array_combine($reviewer_phids, $reviewer_phids);
}
$metadata = array();
$inline_comments = array();
if ($this->attachInlineComments) {
$inline_comments = id(new DifferentialInlineComment())->loadAllWhere(
'authorPHID = %s AND revisionID = %d AND commentID IS NULL',
$this->actorPHID,
$revision->getID());
}
switch ($action) {
case DifferentialAction::ACTION_COMMENT:
if (!$this->message && !$inline_comments) {
throw new DifferentialActionHasNoEffectException(
"You are submitting an empty comment with no action: ".
"you must act on the revision or post a comment.");
}
break;
case DifferentialAction::ACTION_RESIGN:
if ($actor_is_author) {
throw new Exception('You can not resign from your own revision!');
}
if (empty($reviewer_phids[$actor_phid])) {
throw new DifferentialActionHasNoEffectException(
"You can not resign from this revision because you are not ".
"a reviewer.");
}
DifferentialRevisionEditor::alterReviewers(
$revision,
$reviewer_phids,
$rem = array($actor_phid),
$add = array(),
$actor_phid);
break;
case DifferentialAction::ACTION_ABANDON:
if (!($actor_is_author || $actor_is_admin)) {
throw new Exception('You can only abandon your own revisions.');
}
if ($revision_status == ArcanistDifferentialRevisionStatus::COMMITTED) {
throw new DifferentialActionHasNoEffectException(
"You can not abandon this revision because it has already ".
"been committed.");
}
if ($revision_status == ArcanistDifferentialRevisionStatus::ABANDONED) {
throw new DifferentialActionHasNoEffectException(
"You can not abandon this revision because it has already ".
"been abandoned.");
}
$revision->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED);
break;
case DifferentialAction::ACTION_ACCEPT:
if ($actor_is_author) {
throw new Exception('You can not accept your own revision.');
}
if (($revision_status !=
ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) &&
($revision_status !=
ArcanistDifferentialRevisionStatus::NEEDS_REVISION)) {
switch ($revision_status) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
throw new DifferentialActionHasNoEffectException(
"You can not accept this revision because someone else ".
"already accepted it.");
case ArcanistDifferentialRevisionStatus::ABANDONED:
throw new DifferentialActionHasNoEffectException(
"You can not accept this revision because it has been ".
"abandoned.");
case ArcanistDifferentialRevisionStatus::COMMITTED:
throw new DifferentialActionHasNoEffectException(
"You can not accept this revision because it has already ".
"been committed.");
default:
throw new Exception(
"Unexpected revision state '{$revision_status}'!");
}
}
$revision
->setStatus(ArcanistDifferentialRevisionStatus::ACCEPTED);
if (!isset($reviewer_phids[$actor_phid])) {
DifferentialRevisionEditor::alterReviewers(
$revision,
$reviewer_phids,
$rem = array(),
$add = array($actor_phid),
$actor_phid);
}
break;
case DifferentialAction::ACTION_REQUEST:
if (!$actor_is_author) {
throw new Exception('You must own a revision to request review.');
}
switch ($revision_status) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$revision->setStatus(
ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
throw new DifferentialActionHasNoEffectException(
"You can not request review of this revision because it has ".
"been abandoned.");
case ArcanistDifferentialRevisionStatus::ABANDONED:
throw new DifferentialActionHasNoEffectException(
"You can not request review of this revision because it has ".
"been abandoned.");
case ArcanistDifferentialRevisionStatus::COMMITTED:
throw new DifferentialActionHasNoEffectException(
"You can not request review of this revision because it has ".
"already been committed.");
default:
throw new Exception(
"Unexpected revision state '{$revision_status}'!");
}
$added_reviewers = $this->addReviewers();
if ($added_reviewers) {
$key = DifferentialComment::METADATA_ADDED_REVIEWERS;
$metadata[$key] = $added_reviewers;
}
break;
case DifferentialAction::ACTION_REJECT:
if ($actor_is_author) {
throw new Exception(
'You can not request changes to your own revision.');
}
switch ($revision_status) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
// NOTE: We allow you to reject an already-rejected revision
// because it doesn't create any ambiguity and avoids a rather
// needless dialog.
break;
case ArcanistDifferentialRevisionStatus::ABANDONED:
throw new DifferentialActionHasNoEffectException(
"You can not request changes to this revision because it has ".
"been abandoned.");
case ArcanistDifferentialRevisionStatus::COMMITTED:
throw new DifferentialActionHasNoEffectException(
"You can not request changes to this revision because it has ".
"already been committed.");
default:
throw new Exception(
"Unexpected revision state '{$revision_status}'!");
}
if (!isset($reviewer_phids[$actor_phid])) {
DifferentialRevisionEditor::alterReviewers(
$revision,
$reviewer_phids,
$rem = array(),
$add = array($actor_phid),
$actor_phid);
}
$revision
->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVISION);
break;
case DifferentialAction::ACTION_RETHINK:
if (!$actor_is_author) {
throw new Exception(
"You can not plan changes to somebody else's revision");
}
switch ($revision_status) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
break;
case ArcanistDifferentialRevisionStatus::ABANDONED:
throw new DifferentialActionHasNoEffectException(
"You can not plan changes to this revision because it has ".
"been abandoned.");
case ArcanistDifferentialRevisionStatus::COMMITTED:
throw new DifferentialActionHasNoEffectException(
"You can not plan changes to this revision because it has ".
"already been committed.");
default:
throw new Exception(
"Unexpected revision state '{$revision_status}'!");
}
$revision
->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVISION);
break;
case DifferentialAction::ACTION_RECLAIM:
if (!$actor_is_author) {
throw new Exception('You can not reclaim a revision you do not own.');
}
if ($revision_status != ArcanistDifferentialRevisionStatus::ABANDONED) {
throw new DifferentialActionHasNoEffectException(
"You can not reclaim this revision because it is not abandoned.");
}
$revision
->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
break;
case DifferentialAction::ACTION_COMMIT:
+
+ // TODO: We allow this from any state because the daemons are
+ // considered authoritative. However, this technically means that anyone
+ // can mark anything committed at any time with the right POST.
+ // Ideally we should set a flag on the editor to tell it whether it
+ // should enforce the web workflow rules (actor must be author and
+ // state must be 'accepted') or the daemon workflow rules (no rules).
+
$revision
->setStatus(ArcanistDifferentialRevisionStatus::COMMITTED);
break;
case DifferentialAction::ACTION_ADDREVIEWERS:
$added_reviewers = $this->addReviewers();
if ($added_reviewers) {
$key = DifferentialComment::METADATA_ADDED_REVIEWERS;
$metadata[$key] = $added_reviewers;
} else {
$user_tried_to_add = count($this->getAddedReviewers());
if ($user_tried_to_add == 0) {
throw new DifferentialActionHasNoEffectException(
"You can not add reviewers, because you did not specify any ".
"reviewers.");
} else if ($user_tried_to_add == 1) {
throw new DifferentialActionHasNoEffectException(
"You can not add that reviewer, because they are already an ".
"author or reviewer.");
} else {
throw new DifferentialActionHasNoEffectException(
"You can not add those reviewers, because they are all already ".
"authors or reviewers.");
}
}
break;
case DifferentialAction::ACTION_ADDCCS:
$added_ccs = $this->getAddedCCs();
$user_tried_to_add = count($added_ccs);
$added_ccs = $this->filterAddedCCs($added_ccs);
if ($added_ccs) {
foreach ($added_ccs as $cc) {
DifferentialRevisionEditor::addCC(
$revision,
$cc,
$this->actorPHID);
}
$key = DifferentialComment::METADATA_ADDED_CCS;
$metadata[$key] = $added_ccs;
} else {
if ($user_tried_to_add == 0) {
throw new DifferentialActionHasNoEffectException(
"You can not add CCs, because you did not specify any ".
"CCs.");
} else if ($user_tried_to_add == 1) {
throw new DifferentialActionHasNoEffectException(
"You can not add that CC, because they are already an ".
"author, reviewer or CC.");
} else {
throw new DifferentialActionHasNoEffectException(
"You can not add those CCs, because they are all already ".
"authors, reviewers or CCs.");
}
}
break;
default:
throw new Exception('Unsupported action.');
}
// Update information about reviewer in charge.
if ($action == DifferentialAction::ACTION_ACCEPT ||
$action == DifferentialAction::ACTION_REJECT) {
$revision->setLastReviewerPHID($actor_phid);
}
// Always save the revision (even if we didn't actually change any of its
// properties) so that it jumps to the top of the revision list when sorted
// by "updated". Notably, this allows "ping" comments to push it to the
// top of the action list.
$revision->save();
if ($action != DifferentialAction::ACTION_RESIGN &&
$this->actorPHID != $revision->getAuthorPHID() &&
!in_array($this->actorPHID, $revision->getReviewers())) {
DifferentialRevisionEditor::addCC(
$revision,
$this->actorPHID,
$this->actorPHID);
}
$comment = id(new DifferentialComment())
->setAuthorPHID($this->actorPHID)
->setRevisionID($revision->getID())
->setAction($action)
->setContent((string)$this->message)
->setMetadata($metadata);
if ($this->contentSource) {
$comment->setContentSource($this->contentSource);
}
$comment->save();
$changesets = array();
if ($inline_comments) {
$load_ids = mpull($inline_comments, 'getChangesetID');
if ($load_ids) {
$load_ids = array_unique($load_ids);
$changesets = id(new DifferentialChangeset())->loadAllWhere(
'id in (%Ld)',
$load_ids);
}
foreach ($inline_comments as $inline) {
$inline->setCommentID($comment->getID());
$inline->save();
}
}
// Find any "@mentions" in the comment blocks.
$content_blocks = array($comment->getContent());
foreach ($inline_comments as $inline) {
$content_blocks[] = $inline->getContent();
}
$mention_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions(
$content_blocks);
if ($mention_ccs) {
$mention_ccs = $this->filterAddedCCs($mention_ccs);
if ($mention_ccs) {
$metadata = $comment->getMetadata();
$metacc = idx(
$metadata,
DifferentialComment::METADATA_ADDED_CCS,
array());
foreach ($mention_ccs as $cc_phid) {
DifferentialRevisionEditor::addCC(
$revision,
$cc_phid,
$this->actorPHID);
$metacc[] = $cc_phid;
}
$metadata[DifferentialComment::METADATA_ADDED_CCS] = $metacc;
$comment->setMetadata($metadata);
$comment->save();
}
}
$phids = array($this->actorPHID);
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$actor_handle = $handles[$this->actorPHID];
$xherald_header = HeraldTranscript::loadXHeraldRulesHeader(
$revision->getPHID());
id(new DifferentialCommentMail(
$revision,
$actor_handle,
$comment,
$changesets,
$inline_comments))
->setToPHIDs(
array_merge(
$revision->getReviewers(),
array($revision->getAuthorPHID())))
->setCCPHIDs($revision->getCCPHIDs())
->setChangedByCommit($this->getChangedByCommit())
->setXHeraldRulesHeader($xherald_header)
->setParentMessageID($this->parentMessageID)
->send();
$event_data = array(
'revision_id' => $revision->getID(),
'revision_phid' => $revision->getPHID(),
'revision_name' => $revision->getTitle(),
'revision_author_phid' => $revision->getAuthorPHID(),
'action' => $comment->getAction(),
'feedback_content' => $comment->getContent(),
'actor_phid' => $this->actorPHID,
);
id(new PhabricatorTimelineEvent('difx', $event_data))
->recordEvent();
// TODO: Move to a daemon?
id(new PhabricatorFeedStoryPublisher())
->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_DIFFERENTIAL)
->setStoryData($event_data)
->setStoryTime(time())
->setStoryAuthorPHID($this->actorPHID)
->setRelatedPHIDs(
array(
$revision->getPHID(),
$this->actorPHID,
$revision->getAuthorPHID(),
))
->publish();
// TODO: Move to a daemon?
PhabricatorSearchDifferentialIndexer::indexRevision($revision);
return $comment;
}
private function filterAddedCCs(array $ccs) {
$revision = $this->revision;
$current_ccs = $revision->getCCPHIDs();
$current_ccs = array_fill_keys($current_ccs, true);
$reviewer_phids = $revision->getReviewers();
$reviewer_phids = array_fill_keys($reviewer_phids, true);
foreach ($ccs as $key => $cc) {
if (isset($current_ccs[$cc])) {
unset($ccs[$key]);
}
if (isset($reviewer_phids[$cc])) {
unset($ccs[$key]);
}
if ($cc == $revision->getAuthorPHID()) {
unset($ccs[$key]);
}
}
return $ccs;
}
private function addReviewers() {
$revision = $this->revision;
$added_reviewers = $this->getAddedReviewers();
$reviewer_phids = $revision->getReviewers();
foreach ($added_reviewers as $k => $user_phid) {
if ($user_phid == $revision->getAuthorPHID()) {
unset($added_reviewers[$k]);
}
if (!empty($reviewer_phids[$user_phid])) {
unset($added_reviewers[$k]);
}
}
$added_reviewers = array_unique($added_reviewers);
if ($added_reviewers) {
DifferentialRevisionEditor::alterReviewers(
$revision,
$reviewer_phids,
$rem = array(),
$added_reviewers,
$this->actorPHID);
}
return $added_reviewers;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Dec 3, 10:26 AM (10 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
433279
Default Alt Text
(46 KB)

Event Timeline