diff --git a/src/applications/differential/controller/DifferentialCommentPreviewController.php b/src/applications/differential/controller/DifferentialCommentPreviewController.php index 66d1cc9338..706c2dcaf2 100644 --- a/src/applications/differential/controller/DifferentialCommentPreviewController.php +++ b/src/applications/differential/controller/DifferentialCommentPreviewController.php @@ -1,145 +1,139 @@ id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $id = $request->getURIData('id'); $revision = id(new DifferentialRevisionQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$revision) { return new Aphront404Response(); } $type_comment = PhabricatorTransactions::TYPE_COMMENT; $type_action = DifferentialTransaction::TYPE_ACTION; $type_edge = PhabricatorTransactions::TYPE_EDGE; $type_subscribers = PhabricatorTransactions::TYPE_SUBSCRIBERS; $xactions = array(); $action = $request->getStr('action'); switch ($action) { case DifferentialAction::ACTION_COMMENT: case DifferentialAction::ACTION_ADDREVIEWERS: case DifferentialAction::ACTION_ADDCCS: break; default: $xactions[] = id(new DifferentialTransaction()) ->setTransactionType($type_action) ->setNewValue($action); break; } $edge_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST; $reviewers = $request->getStrList('reviewers'); if (DifferentialAction::allowReviewers($action) && $reviewers) { $faux_edges = array(); foreach ($reviewers as $phid) { $faux_edges[$phid] = array( 'src' => $revision->getPHID(), 'type' => $edge_reviewer, 'dst' => $phid, ); } $xactions[] = id(new DifferentialTransaction()) ->setTransactionType($type_edge) ->setMetadataValue('edge:type', $edge_reviewer) ->setOldValue(array()) ->setNewValue($faux_edges); } $ccs = $request->getStrList('ccs'); if ($ccs) { $xactions[] = id(new DifferentialTransaction()) ->setTransactionType($type_subscribers) ->setOldValue(array()) ->setNewValue(array_fuse($ccs)); } // Add a comment transaction if there's nothing, so we'll generate a // nonempty result. if (strlen($request->getStr('content')) || !$xactions) { $xactions[] = id(new DifferentialTransaction()) ->setTransactionType($type_comment) ->attachComment( id(new ManiphestTransactionComment()) ->setContent($request->getStr('content'))); } foreach ($xactions as $xaction) { $xaction->setAuthorPHID($viewer->getPHID()); } $engine = new PhabricatorMarkupEngine(); $engine->setViewer($request->getUser()); foreach ($xactions as $xaction) { if ($xaction->hasComment()) { $engine->addObject( $xaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } } $engine->process(); $phids = mpull($xactions, 'getRequiredHandlePHIDs'); $phids = array_mergev($phids); $handles = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs($phids) ->execute(); foreach ($xactions as $xaction) { $xaction->setHandles($handles); } $view = id(new DifferentialTransactionView()) ->setUser($viewer) ->setTransactions($xactions) ->setIsPreview(true); $metadata = array( 'reviewers' => $reviewers, 'ccs' => $ccs, ); if ($action != DifferentialAction::ACTION_COMMENT) { $metadata['action'] = $action; } - $draft_key = 'differential-comment-'.$this->id; + $draft_key = 'differential-comment-'.$id; $draft = id(new PhabricatorDraft()) ->setAuthorPHID($viewer->getPHID()) ->setDraftKey($draft_key) ->setDraft($request->getStr('content')) ->setMetadata($metadata) ->replaceOrDelete(); if ($draft->isDeleted()) { DifferentialDraft::deleteHasDraft( $viewer->getPHID(), $revision->getPHID(), $draft_key); } else { DifferentialDraft::markHasDraft( $viewer->getPHID(), $revision->getPHID(), $draft_key); } return id(new AphrontAjaxResponse()) ->setContent((string)phutil_implode_html('', $view->buildEvents())); } } diff --git a/src/applications/differential/controller/DifferentialCommentSaveController.php b/src/applications/differential/controller/DifferentialCommentSaveController.php index 7d918a792c..831d7c90c8 100644 --- a/src/applications/differential/controller/DifferentialCommentSaveController.php +++ b/src/applications/differential/controller/DifferentialCommentSaveController.php @@ -1,149 +1,143 @@ id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $id = $request->getURIData('id'); if (!$request->isFormPost()) { return new Aphront400Response(); } $revision = id(new DifferentialRevisionQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->needReviewerStatus(true) ->needReviewerAuthority(true) ->executeOne(); if (!$revision) { return new Aphront404Response(); } $type_action = DifferentialTransaction::TYPE_ACTION; $type_subscribers = PhabricatorTransactions::TYPE_SUBSCRIBERS; $type_edge = PhabricatorTransactions::TYPE_EDGE; $type_comment = PhabricatorTransactions::TYPE_COMMENT; $type_inline = DifferentialTransaction::TYPE_INLINE; $edge_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST; $xactions = array(); $action = $request->getStr('action'); switch ($action) { case DifferentialAction::ACTION_COMMENT: case DifferentialAction::ACTION_ADDREVIEWERS: case DifferentialAction::ACTION_ADDCCS: // These transaction types have no direct effect, they just // accompany other transaction types which can have an effect. break; default: $xactions[] = id(new DifferentialTransaction()) ->setTransactionType($type_action) ->setNewValue($request->getStr('action')); break; } $ccs = $request->getArr('ccs'); if ($ccs) { $xactions[] = id(new DifferentialTransaction()) ->setTransactionType($type_subscribers) ->setNewValue(array('+' => $ccs)); } $current_reviewers = mpull( $revision->getReviewerStatus(), null, 'getReviewerPHID'); $reviewer_edges = array(); $add_reviewers = $request->getArr('reviewers'); foreach ($add_reviewers as $reviewer_phid) { if (isset($current_reviewers[$reviewer_phid])) { continue; } $reviewer = new DifferentialReviewer( $reviewer_phid, array( 'status' => DifferentialReviewerStatus::STATUS_ADDED, )); $reviewer_edges[$reviewer_phid] = array( 'data' => $reviewer->getEdgeData(), ); } if ($add_reviewers) { $xactions[] = id(new DifferentialTransaction()) ->setTransactionType($type_edge) ->setMetadataValue('edge:type', $edge_reviewer) ->setNewValue(array('+' => $reviewer_edges)); } $inlines = DifferentialTransactionQuery::loadUnsubmittedInlineComments( $viewer, $revision); foreach ($inlines as $inline) { $xactions[] = id(new DifferentialTransaction()) ->setTransactionType($type_inline) ->attachComment($inline); } // NOTE: If there are no other transactions, add an empty comment // transaction so that we'll raise a more user-friendly error message, // to the effect of "you can not post an empty comment". $no_xactions = !$xactions; $comment = $request->getStr('comment'); if (strlen($comment) || $no_xactions) { $xactions[] = id(new DifferentialTransaction()) ->setTransactionType($type_comment) ->attachComment( id(new DifferentialTransactionComment()) ->setRevisionPHID($revision->getPHID()) ->setContent($comment)); } $editor = id(new DifferentialTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnMissingFields(true) ->setContinueOnNoEffect($request->isContinueRequest()); $revision_uri = '/D'.$revision->getID(); try { $editor->applyTransactions($revision, $xactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse()) ->setCancelURI($revision_uri) ->setException($ex); } catch (PhabricatorApplicationTransactionValidationException $ex) { return id(new PhabricatorApplicationTransactionValidationResponse()) ->setCancelURI($revision_uri) ->setException($ex); } $user = $request->getUser(); $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $user->getPHID(), 'differential-comment-'.$revision->getID()); if ($draft) { $draft->delete(); } DifferentialDraft::deleteAllDrafts($user->getPHID(), $revision->getPHID()); return id(new AphrontRedirectResponse()) ->setURI('/D'.$revision->getID()); } } diff --git a/src/applications/differential/controller/DifferentialDiffViewController.php b/src/applications/differential/controller/DifferentialDiffViewController.php index 6ffd57396c..716a183b5b 100644 --- a/src/applications/differential/controller/DifferentialDiffViewController.php +++ b/src/applications/differential/controller/DifferentialDiffViewController.php @@ -1,199 +1,193 @@ id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $id = $request->getURIData('id'); $diff = id(new DifferentialDiffQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$diff) { return new Aphront404Response(); } if ($diff->getRevisionID()) { return id(new AphrontRedirectResponse()) ->setURI('/D'.$diff->getRevisionID().'?id='.$diff->getID()); } $diff_phid = $diff->getPHID(); $buildables = id(new HarbormasterBuildableQuery()) ->setViewer($viewer) ->withBuildablePHIDs(array($diff_phid)) ->withManualBuildables(false) ->needBuilds(true) ->needTargets(true) ->execute(); $buildables = mpull($buildables, null, 'getBuildablePHID'); $diff->attachBuildable(idx($buildables, $diff_phid)); // TODO: implement optgroup support in AphrontFormSelectControl? $select = array(); $select[] = hsprintf('', pht('Create New Revision')); $select[] = phutil_tag( 'option', array('value' => ''), pht('Create a new Revision...')); $select[] = hsprintf(''); $selected_id = $request->getInt('revisionID'); $revisions = $this->loadSelectableRevisions($viewer, $selected_id); if ($revisions) { $select[] = hsprintf( '', pht('Update Existing Revision')); foreach ($revisions as $revision) { if ($selected_id == $revision->getID()) { $selected = 'selected'; } else { $selected = null; } $select[] = phutil_tag( 'option', array( 'value' => $revision->getID(), 'selected' => $selected, ), id(new PhutilUTF8StringTruncator()) ->setMaximumGlyphs(128) ->truncateString( 'D'.$revision->getID().' '.$revision->getTitle())); } $select[] = hsprintf(''); } $select = phutil_tag( 'select', array('name' => 'revisionID'), $select); $form = id(new AphrontFormView()) ->setUser($request->getUser()) ->setAction('/differential/revision/edit/') ->addHiddenInput('diffID', $diff->getID()) ->addHiddenInput('viaDiffView', 1) ->addHiddenInput( id(new DifferentialRepositoryField())->getFieldKey(), $diff->getRepositoryPHID()) ->appendRemarkupInstructions( pht( 'Review the diff for correctness. When you are satisfied, either '. '**create a new revision** or **update an existing revision**.')) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Attach To')) ->setValue($select)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Continue'))); $props = id(new DifferentialDiffProperty())->loadAllWhere( 'diffID = %d', $diff->getID()); $props = mpull($props, 'getData', 'getName'); $property_head = id(new PHUIHeaderView()) ->setHeader(pht('Properties')); $property_view = new PHUIPropertyListView(); $changesets = $diff->loadChangesets(); $changesets = msort($changesets, 'getSortKey'); $table_of_contents = $this->buildTableOfContents( $changesets, $changesets, $diff->loadCoverageMap($viewer)); $refs = array(); foreach ($changesets as $changeset) { $refs[$changeset->getID()] = $changeset->getID(); } $details = id(new DifferentialChangesetListView()) ->setChangesets($changesets) ->setVisibleChangesets($changesets) ->setRenderingReferences($refs) ->setStandaloneURI('/differential/changeset/') ->setDiff($diff) ->setTitle(pht('Diff %d', $diff->getID())) ->setUser($request->getUser()); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Diff %d', $diff->getID())); $prop_box = id(new PHUIObjectBoxView()) ->setHeader($property_head) ->addPropertyList($property_view) ->setForm($form); return $this->buildApplicationPage( array( $crumbs, $prop_box, $table_of_contents, $details, ), array( 'title' => pht('Diff View'), )); } private function loadSelectableRevisions( PhabricatorUser $viewer, $selected_id) { $revisions = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withAuthors(array($viewer->getPHID())) ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->execute(); $revisions = mpull($revisions, null, 'getID'); // If a specific revision is selected (for example, because the user is // following the "Update Diff" workflow), but not present in the dropdown, // try to add it to the dropdown even if it is closed. This allows the // workflow to be used to update abandoned revisions. if ($selected_id) { if (empty($revisions[$selected_id])) { $selected = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withAuthors(array($viewer->getPHID())) ->withIDs(array($selected_id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->execute(); $revisions = mpull($selected, null, 'getID') + $revisions; } } return $revisions; } } diff --git a/src/applications/differential/controller/DifferentialRevisionCloseDetailsController.php b/src/applications/differential/controller/DifferentialRevisionCloseDetailsController.php index 25051bcbb5..d5a7d897a8 100644 --- a/src/applications/differential/controller/DifferentialRevisionCloseDetailsController.php +++ b/src/applications/differential/controller/DifferentialRevisionCloseDetailsController.php @@ -1,117 +1,108 @@ phid = idx($data, 'phid'); - } - - public function processRequest() { - $request = $this->getRequest(); - - $viewer = $request->getUser(); - $xaction_phid = $this->phid; + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $xaction = id(new PhabricatorObjectQuery()) - ->withPHIDs(array($xaction_phid)) + ->withPHIDs(array($request->getURIData('phid'))) ->setViewer($viewer) ->executeOne(); if (!$xaction) { return new Aphront404Response(); } $obj_phid = $xaction->getObjectPHID(); $obj_handle = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs(array($obj_phid)) ->executeOne(); $body = $this->getRevisionMatchExplanation( $xaction->getMetadataValue('revisionMatchData'), $obj_handle); $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setTitle(pht('Commit Close Explanation')) ->appendParagraph($body) ->addCancelButton($obj_handle->getURI()); return id(new AphrontDialogResponse())->setDialog($dialog); } private function getRevisionMatchExplanation( $revision_match_data, PhabricatorObjectHandle $obj_handle) { if (!$revision_match_data) { return pht( 'This commit was made before this feature was built and thus this '. 'information is unavailable.'); } $body_why = array(); if ($revision_match_data['usedURI']) { return pht( 'We found a "%s" field with value "%s" in the commit message, '. 'and the domain on the URI matches this install, so '. 'we linked this commit to %s.', 'Differential Revision', $revision_match_data['foundURI'], phutil_tag( 'a', array( 'href' => $obj_handle->getURI(), ), $obj_handle->getName())); } else if ($revision_match_data['foundURI']) { $body_why[] = pht( 'We found a "%s" field with value "%s" in the commit message, '. 'but the domain on this URI did not match the configured '. 'domain for this install, "%s", so we ignored it under '. 'the assumption that it refers to some third-party revision.', 'Differential Revision', $revision_match_data['foundURI'], $revision_match_data['validDomain']); } else { $body_why[] = pht( 'We didn\'t find a "%s" field in the commit message.', 'Differential Revision'); } switch ($revision_match_data['matchHashType']) { case ArcanistDifferentialRevisionHash::HASH_GIT_TREE: $hash_info = true; $hash_type = 'tree'; break; case ArcanistDifferentialRevisionHash::HASH_GIT_COMMIT: case ArcanistDifferentialRevisionHash::HASH_MERCURIAL_COMMIT: $hash_info = true; $hash_type = 'commit'; break; default: $hash_info = false; break; } if ($hash_info) { $diff_link = phutil_tag( 'a', array( 'href' => $obj_handle->getURI(), ), $obj_handle->getName()); $body_why = pht( 'This commit and the active diff of %s had the same %s hash '. '(%s) so we linked this commit to %s.', $diff_link, $hash_type, $revision_match_data['matchHashValue'], $diff_link); } return phutil_implode_html("\n", $body_why); } } diff --git a/src/applications/differential/controller/DifferentialRevisionEditController.php b/src/applications/differential/controller/DifferentialRevisionEditController.php index 1c664f07d2..a52538ca55 100644 --- a/src/applications/differential/controller/DifferentialRevisionEditController.php +++ b/src/applications/differential/controller/DifferentialRevisionEditController.php @@ -1,211 +1,205 @@ getViewer(); + $id = $request->getURIData('id'); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - if (!$this->id) { - $this->id = $request->getInt('revisionID'); + if (!$id) { + $id = $request->getInt('revisionID'); } - if ($this->id) { + if ($id) { $revision = id(new DifferentialRevisionQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->needRelationships(true) ->needReviewerStatus(true) ->needActiveDiffs(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$revision) { return new Aphront404Response(); } } else { $revision = DifferentialRevision::initializeNewRevision($viewer); $revision->attachReviewerStatus(array()); } $diff_id = $request->getInt('diffID'); if ($diff_id) { $diff = id(new DifferentialDiffQuery()) ->setViewer($viewer) ->withIDs(array($diff_id)) ->executeOne(); if (!$diff) { return new Aphront404Response(); } if ($diff->getRevisionID()) { // TODO: Redirect? throw new Exception( pht('This diff is already attached to a revision!')); } } else { $diff = null; } if (!$diff) { if (!$revision->getID()) { throw new Exception( pht('You can not create a new revision without a diff!')); } } else { // TODO: It would be nice to show the diff being attached in the UI. } $field_list = PhabricatorCustomField::getObjectFields( $revision, PhabricatorCustomField::ROLE_EDIT); $field_list ->setViewer($viewer) ->readFieldsFromStorage($revision); if ($request->getStr('viaDiffView') && $diff) { $repo_key = id(new DifferentialRepositoryField())->getFieldKey(); $repository_field = idx( $field_list->getFields(), $repo_key); if ($repository_field) { $repository_field->setValue($request->getStr($repo_key)); } $view_policy_key = id(new DifferentialViewPolicyField())->getFieldKey(); $view_policy_field = idx( $field_list->getFields(), $view_policy_key); if ($view_policy_field) { $view_policy_field->setValue($diff->getViewPolicy()); } } $validation_exception = null; if ($request->isFormPost() && !$request->getStr('viaDiffView')) { $editor = id(new DifferentialTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); $xactions = $field_list->buildFieldTransactionsFromRequest( new DifferentialTransaction(), $request); if ($diff) { $repository_phid = null; $repository_tokenizer = $request->getArr( id(new DifferentialRepositoryField())->getFieldKey()); if ($repository_tokenizer) { $repository_phid = reset($repository_tokenizer); } $xactions[] = id(new DifferentialTransaction()) ->setTransactionType(DifferentialTransaction::TYPE_UPDATE) ->setNewValue($diff->getPHID()); $editor->setRepositoryPHIDOverride($repository_phid); } $comments = $request->getStr('comments'); if (strlen($comments)) { $xactions[] = id(new DifferentialTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( id(new DifferentialTransactionComment()) ->setContent($comments)); } try { $editor->applyTransactions($revision, $xactions); $revision_uri = '/D'.$revision->getID(); return id(new AphrontRedirectResponse())->setURI($revision_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; } } $form = new AphrontFormView(); $form->setUser($request->getUser()); if ($diff) { $form->addHiddenInput('diffID', $diff->getID()); } if ($revision->getID()) { $form->setAction('/differential/revision/edit/'.$revision->getID().'/'); } else { $form->setAction('/differential/revision/edit/'); } if ($diff && $revision->getID()) { $form ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel(pht('Comments')) ->setName('comments') ->setCaption(pht("Explain what's new in this diff.")) ->setValue($request->getStr('comments'))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save'))) ->appendChild( id(new AphrontFormDividerControl())); } $field_list->appendFieldsToForm($form); $submit = id(new AphrontFormSubmitControl()) ->setValue('Save'); if ($diff) { $submit->addCancelButton('/differential/diff/'.$diff->getID().'/'); } else { $submit->addCancelButton('/D'.$revision->getID()); } $form->appendChild($submit); $crumbs = $this->buildApplicationCrumbs(); if ($revision->getID()) { if ($diff) { $title = pht('Update Differential Revision'); $crumbs->addTextCrumb( 'D'.$revision->getID(), '/differential/diff/'.$diff->getID().'/'); } else { $title = pht('Edit Differential Revision'); $crumbs->addTextCrumb( 'D'.$revision->getID(), '/D'.$revision->getID()); } } else { $title = pht('Create New Differential Revision'); } $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setValidationException($validation_exception) ->setForm($form); $crumbs->addTextCrumb($title); return $this->buildApplicationPage( array( $crumbs, $form_box, ), array( 'title' => $title, )); } } diff --git a/src/applications/differential/controller/DifferentialRevisionLandController.php b/src/applications/differential/controller/DifferentialRevisionLandController.php index e19762b44d..4f8787956f 100644 --- a/src/applications/differential/controller/DifferentialRevisionLandController.php +++ b/src/applications/differential/controller/DifferentialRevisionLandController.php @@ -1,165 +1,164 @@ revisionID = $data['id']; $this->strategyClass = $data['strategy']; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $revision_id = $this->revisionID; $revision = id(new DifferentialRevisionQuery()) ->withIDs(array($revision_id)) ->setViewer($viewer) ->executeOne(); if (!$revision) { return new Aphront404Response(); } if (is_subclass_of($this->strategyClass, 'DifferentialLandingStrategy')) { $this->pushStrategy = newv($this->strategyClass, array()); } else { throw new Exception( pht( "Strategy type must be a valid class name and must subclass ". "%s. '%s' is not a subclass of %s", 'DifferentialLandingStrategy', $this->strategyClass, 'DifferentialLandingStrategy')); } if ($request->isDialogFormPost()) { $response = null; $text = ''; try { $response = $this->attemptLand($revision, $request); $title = pht('Success!'); $text = pht('Revision was successfully landed.'); } catch (Exception $ex) { $title = pht('Failed to land revision'); if ($ex instanceof PhutilProxyException) { $text = hsprintf( '%s:
%s
', $ex->getMessage(), $ex->getPreviousException()->getMessage()); } else { $text = phutil_tag('pre', array(), $ex->getMessage()); } $text = id(new PHUIInfoView()) ->appendChild($text); } if ($response instanceof AphrontDialogView) { $dialog = $response; } else { $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setTitle($title) ->appendChild(phutil_tag('p', array(), $text)) ->addCancelButton('/D'.$revision_id, pht('Done')); } return id(new AphrontDialogResponse())->setDialog($dialog); } $is_disabled = $this->pushStrategy->isActionDisabled( $viewer, $revision, $revision->getRepository()); if ($is_disabled) { if (is_string($is_disabled)) { $explain = $is_disabled; } else { $explain = pht('This action is not currently enabled.'); } $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setTitle(pht("Can't land revision")) ->appendChild($explain) ->addCancelButton('/D'.$revision_id); return id(new AphrontDialogResponse())->setDialog($dialog); } $prompt = hsprintf('%s

%s', pht( 'This will squash and rebase revision %s, and push it to '. 'the default / master branch.', $revision_id), pht('It is an experimental feature and may not work.')); $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setTitle(pht('Land Revision %s?', $revision_id)) ->appendChild($prompt) ->setSubmitURI($request->getRequestURI()) ->addSubmitButton(pht('Land it!')) ->addCancelButton('/D'.$revision_id); return id(new AphrontDialogResponse())->setDialog($dialog); } private function attemptLand($revision, $request) { $status = $revision->getStatus(); if ($status != ArcanistDifferentialRevisionStatus::ACCEPTED) { throw new Exception(pht('Only Accepted revisions can be landed.')); } $repository = $revision->getRepository(); if ($repository === null) { throw new Exception(pht('Revision is not attached to a repository.')); } $can_push = PhabricatorPolicyFilter::hasCapability( $request->getUser(), $repository, DiffusionPushCapability::CAPABILITY); if (!$can_push) { throw new Exception( pht('You do not have permission to push to this repository.')); } $lock = $this->lockRepository($repository); try { $response = $this->pushStrategy->processLandRequest( $request, $revision, $repository); } catch (Exception $e) { $lock->unlock(); throw $e; } $lock->unlock(); $looksoon = new ConduitCall( 'diffusion.looksoon', array( 'callsigns' => array($repository->getCallsign()), )); $looksoon->setUser($request->getUser()); $looksoon->execute(); return $response; } private function lockRepository($repository) { $lock_name = __CLASS__.':'.($repository->getCallsign()); $lock = PhabricatorGlobalLock::newLock($lock_name); $lock->lock(); return $lock; } } diff --git a/src/applications/differential/controller/DifferentialRevisionListController.php b/src/applications/differential/controller/DifferentialRevisionListController.php index e48b088e6b..29567a53bf 100644 --- a/src/applications/differential/controller/DifferentialRevisionListController.php +++ b/src/applications/differential/controller/DifferentialRevisionListController.php @@ -1,36 +1,30 @@ queryKey = idx($data, 'queryKey'); - } - - public function processRequest() { + public function handleRequest(AphrontRequest $request) { $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) + ->setQueryKey($request->getURIData('queryKey')) ->setSearchEngine(new DifferentialRevisionSearchEngine()) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $crumbs->addAction( id(new PHUIListItemView()) ->setHref($this->getApplicationURI('/diff/create/')) ->setName(pht('Create Diff')) ->setIcon('fa-plus-square')); return $crumbs; } } diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 4af487252c..f748b8d9d5 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -1,1039 +1,1035 @@ revisionID = $data['id']; - } - - public function processRequest() { + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $this->revisionID = $request->getURIData('id'); - $request = $this->getRequest(); - $user = $request->getUser(); - $viewer_is_anonymous = !$user->isLoggedIn(); + $viewer_is_anonymous = !$viewer->isLoggedIn(); $revision = id(new DifferentialRevisionQuery()) ->withIDs(array($this->revisionID)) ->setViewer($request->getUser()) ->needRelationships(true) ->needReviewerStatus(true) ->needReviewerAuthority(true) ->executeOne(); if (!$revision) { return new Aphront404Response(); } $diffs = id(new DifferentialDiffQuery()) ->setViewer($request->getUser()) ->withRevisionIDs(array($this->revisionID)) ->execute(); $diffs = array_reverse($diffs, $preserve_keys = true); if (!$diffs) { throw new Exception( pht('This revision has no diffs. Something has gone quite wrong.')); } $revision->attachActiveDiff(last($diffs)); $diff_vs = $request->getInt('vs'); $target_id = $request->getInt('id'); $target = idx($diffs, $target_id, end($diffs)); $target_manual = $target; if (!$target_id) { foreach ($diffs as $diff) { if ($diff->getCreationMethod() != 'commit') { $target_manual = $diff; } } } if (empty($diffs[$diff_vs])) { $diff_vs = null; } $repository = null; $repository_phid = $target->getRepositoryPHID(); if ($repository_phid) { if ($repository_phid == $revision->getRepositoryPHID()) { $repository = $revision->getRepository(); } else { $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($repository_phid)) ->executeOne(); } } list($changesets, $vs_map, $vs_changesets, $rendering_references) = $this->loadChangesetsAndVsMap( $target, idx($diffs, $diff_vs), $repository); if ($request->getExists('download')) { return $this->buildRawDiffResponse( $revision, $changesets, $vs_changesets, $vs_map, $repository); } $map = $vs_map; if (!$map) { $map = array_fill_keys(array_keys($changesets), 0); } $old_ids = array(); $new_ids = array(); foreach ($map as $id => $vs) { if ($vs <= 0) { $old_ids[] = $id; $new_ids[] = $id; } else { $new_ids[] = $id; $new_ids[] = $vs; } } $props = id(new DifferentialDiffProperty())->loadAllWhere( 'diffID = %d', $target_manual->getID()); $props = mpull($props, 'getData', 'getName'); $object_phids = array_merge( $revision->getReviewers(), $revision->getCCPHIDs(), $revision->loadCommitPHIDs(), array( $revision->getAuthorPHID(), - $user->getPHID(), + $viewer->getPHID(), )); foreach ($revision->getAttached() as $type => $phids) { foreach ($phids as $phid => $info) { $object_phids[] = $phid; } } $field_list = PhabricatorCustomField::getObjectFields( $revision, PhabricatorCustomField::ROLE_VIEW); - $field_list->setViewer($user); + $field_list->setViewer($viewer); $field_list->readFieldsFromStorage($revision); $warning_handle_map = array(); foreach ($field_list->getFields() as $key => $field) { $req = $field->getRequiredHandlePHIDsForRevisionHeaderWarnings(); foreach ($req as $phid) { $warning_handle_map[$key][] = $phid; $object_phids[] = $phid; } } $handles = $this->loadViewerHandles($object_phids); $request_uri = $request->getRequestURI(); $limit = 100; $large = $request->getStr('large'); if (count($changesets) > $limit && !$large) { $count = count($changesets); $warning = new PHUIInfoView(); $warning->setTitle(pht('Very Large Diff')); $warning->setSeverity(PHUIInfoView::SEVERITY_WARNING); $warning->appendChild(hsprintf( '%s %s', pht( 'This diff is very large and affects %s files. '. 'You may load each file individually or ', new PhutilNumber($count)), phutil_tag( 'a', array( 'class' => 'button grey', 'href' => $request_uri ->alter('large', 'true') ->setFragment('toc'), ), pht('Show All Files Inline')))); $warning = $warning->render(); $old = array_select_keys($changesets, $old_ids); $new = array_select_keys($changesets, $new_ids); $query = id(new DifferentialInlineCommentQuery()) - ->setViewer($user) + ->setViewer($viewer) ->needHidden(true) ->withRevisionPHIDs(array($revision->getPHID())); $inlines = $query->execute(); $inlines = $query->adjustInlinesForChangesets( $inlines, $old, $new, $revision); $visible_changesets = array(); foreach ($inlines as $inline) { $changeset_id = $inline->getChangesetID(); if (isset($changesets[$changeset_id])) { $visible_changesets[$changeset_id] = $changesets[$changeset_id]; } } } else { $warning = null; $visible_changesets = $changesets; } $commit_hashes = mpull($diffs, 'getSourceControlBaseRevision'); $local_commits = idx($props, 'local:commits', array()); foreach ($local_commits as $local_commit) { $commit_hashes[] = idx($local_commit, 'tree'); $commit_hashes[] = idx($local_commit, 'local'); } $commit_hashes = array_unique(array_filter($commit_hashes)); if ($commit_hashes) { $commits_for_links = id(new DiffusionCommitQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIdentifiers($commit_hashes) ->execute(); $commits_for_links = mpull( $commits_for_links, null, 'getCommitIdentifier'); } else { $commits_for_links = array(); } $revision_detail = id(new DifferentialRevisionDetailView()) - ->setUser($user) + ->setUser($viewer) ->setRevision($revision) ->setDiff(end($diffs)) ->setCustomFields($field_list) ->setURI($request->getRequestURI()); $actions = $this->getRevisionActions($revision); $whitespace = $request->getStr( 'whitespace', DifferentialChangesetParser::WHITESPACE_IGNORE_MOST); $repository = $revision->getRepository(); if ($repository) { $symbol_indexes = $this->buildSymbolIndexes( $repository, $visible_changesets); } else { $symbol_indexes = array(); } $revision_detail->setActions($actions); - $revision_detail->setUser($user); + $revision_detail->setUser($viewer); $revision_detail_box = $revision_detail->render(); $revision_warnings = $this->buildRevisionWarnings( $revision, $field_list, $warning_handle_map, $handles); if ($revision_warnings) { $revision_warnings = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors($revision_warnings); $revision_detail_box->setInfoView($revision_warnings); } $detail_diffs = array_select_keys( $diffs, array($diff_vs, $target->getID())); $detail_diffs = mpull($detail_diffs, null, 'getPHID'); $buildables = id(new HarbormasterBuildableQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withBuildablePHIDs(array_keys($detail_diffs)) ->withManualBuildables(false) ->needBuilds(true) ->needTargets(true) ->execute(); $buildables = mpull($buildables, null, 'getBuildablePHID'); foreach ($detail_diffs as $diff_phid => $detail_diff) { $detail_diff->attachBuildable(idx($buildables, $diff_phid)); } $diff_detail_box = $this->buildDiffDetailView( $detail_diffs, $revision, $field_list); $comment_view = $this->buildTransactions( $revision, $diff_vs ? $diffs[$diff_vs] : $target, $target, $old_ids, $new_ids); if (!$viewer_is_anonymous) { $comment_view->setQuoteRef('D'.$revision->getID()); $comment_view->setQuoteTargetID('comment-content'); } $wrap_id = celerity_generate_unique_node_id(); $comment_view = phutil_tag( 'div', array( 'id' => $wrap_id, ), $comment_view); $changeset_view = new DifferentialChangesetListView(); $changeset_view->setChangesets($changesets); $changeset_view->setVisibleChangesets($visible_changesets); if (!$viewer_is_anonymous) { $changeset_view->setInlineCommentControllerURI( '/differential/comment/inline/edit/'.$revision->getID().'/'); } $changeset_view->setStandaloneURI('/differential/changeset/'); $changeset_view->setRawFileURIs( '/differential/changeset/?view=old', '/differential/changeset/?view=new'); - $changeset_view->setUser($user); + $changeset_view->setUser($viewer); $changeset_view->setDiff($target); $changeset_view->setRenderingReferences($rendering_references); $changeset_view->setVsMap($vs_map); $changeset_view->setWhitespace($whitespace); if ($repository) { $changeset_view->setRepository($repository); } $changeset_view->setSymbolIndexes($symbol_indexes); $changeset_view->setTitle(pht('Diff %s', $target->getID())); $diff_history = id(new DifferentialRevisionUpdateHistoryView()) - ->setUser($user) + ->setUser($viewer) ->setDiffs($diffs) ->setSelectedVersusDiffID($diff_vs) ->setSelectedDiffID($target->getID()) ->setSelectedWhitespace($whitespace) ->setCommitsForLinks($commits_for_links); $local_view = id(new DifferentialLocalCommitsView()) - ->setUser($user) + ->setUser($viewer) ->setLocalCommits(idx($props, 'local:commits')) ->setCommitsForLinks($commits_for_links); if ($repository) { $other_revisions = $this->loadOtherRevisions( $changesets, $target, $repository); } else { $other_revisions = array(); } $other_view = null; if ($other_revisions) { $other_view = $this->renderOtherRevisions($other_revisions); } $toc_view = $this->buildTableOfContents( $changesets, $visible_changesets, - $target->loadCoverageMap($user)); + $target->loadCoverageMap($viewer)); $comment_form = null; if (!$viewer_is_anonymous) { $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', - $user->getPHID(), + $viewer->getPHID(), 'differential-comment-'.$revision->getID()); $reviewers = array(); $ccs = array(); if ($draft) { $reviewers = idx($draft->getMetadata(), 'reviewers', array()); $ccs = idx($draft->getMetadata(), 'ccs', array()); if ($reviewers || $ccs) { $handles = $this->loadViewerHandles(array_merge($reviewers, $ccs)); $reviewers = array_select_keys($handles, $reviewers); $ccs = array_select_keys($handles, $ccs); } } $comment_form = new DifferentialAddCommentView(); $comment_form->setRevision($revision); $review_warnings = array(); foreach ($field_list->getFields() as $field) { $review_warnings[] = $field->getWarningsForDetailView(); } $review_warnings = array_mergev($review_warnings); if ($review_warnings) { $review_warnings_panel = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors($review_warnings); $comment_form->setInfoView($review_warnings_panel); } $comment_form->setActions($this->getRevisionCommentActions($revision)); $action_uri = $this->getApplicationURI( 'comment/save/'.$revision->getID().'/'); $comment_form->setActionURI($action_uri); - $comment_form->setUser($user); + $comment_form->setUser($viewer); $comment_form->setDraft($draft); $comment_form->setReviewers(mpull($reviewers, 'getFullName', 'getPHID')); $comment_form->setCCs(mpull($ccs, 'getFullName', 'getPHID')); // TODO: This just makes the "Z" key work. Generalize this and remove // it at some point. $comment_form = phutil_tag( 'div', array( 'class' => 'differential-add-comment-panel', ), $comment_form); } $pane_id = celerity_generate_unique_node_id(); Javelin::initBehavior( 'differential-keyboard-navigation', array( 'haunt' => $pane_id, )); Javelin::initBehavior('differential-user-select'); $page_pane = id(new DifferentialPrimaryPaneView()) ->setID($pane_id) ->appendChild($comment_view); $signatures = DifferentialRequiredSignaturesField::loadForRevision( $revision); $missing_signatures = false; foreach ($signatures as $phid => $signed) { if (!$signed) { $missing_signatures = true; } } if ($missing_signatures) { $signature_message = id(new PHUIInfoView()) ->setErrors( array( array( phutil_tag('strong', array(), pht('Content Hidden:')), ' ', pht( 'The content of this revision is hidden until the author has '. 'signed all of the required legal agreements.'), ), )); $page_pane->appendChild($signature_message); } else { $page_pane->appendChild( array( $diff_history, $warning, $local_view, $toc_view, $other_view, $changeset_view, )); } if ($comment_form) { $page_pane->appendChild($comment_form); } else { // TODO: For now, just use this to get "Login to Comment". $page_pane->appendChild( id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($user) + ->setUser($viewer) ->setRequestURI($request->getRequestURI())); } $object_id = 'D'.$revision->getID(); $content = array( $revision_detail_box, $diff_detail_box, $page_pane, ); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($object_id, '/'.$object_id); - $prefs = $user->loadPreferences(); + $prefs = $viewer->loadPreferences(); $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE; if ($prefs->getPreference($pref_filetree)) { $collapsed = $prefs->getPreference( PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED, false); $nav = id(new DifferentialChangesetFileTreeSideNavBuilder()) ->setTitle('D'.$revision->getID()) ->setBaseURI(new PhutilURI('/D'.$revision->getID())) ->setCollapsed((bool)$collapsed) ->build($changesets); $nav->appendChild($content); $nav->setCrumbs($crumbs); $content = $nav; } else { array_unshift($content, $crumbs); } return $this->buildApplicationPage( $content, array( 'title' => $object_id.' '.$revision->getTitle(), 'pageObjects' => array($revision->getPHID()), )); } private function getRevisionActions(DifferentialRevision $revision) { $viewer = $this->getRequest()->getUser(); $revision_id = $revision->getID(); $revision_phid = $revision->getPHID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $revision, PhabricatorPolicyCapability::CAN_EDIT); $actions = array(); $actions[] = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setHref("/differential/revision/edit/{$revision_id}/") ->setName(pht('Edit Revision')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit); $actions[] = id(new PhabricatorActionView()) ->setIcon('fa-upload') ->setHref("/differential/revision/update/{$revision_id}/") ->setName(pht('Update Diff')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit); $this->requireResource('phabricator-object-selector-css'); $this->requireResource('javelin-behavior-phabricator-object-selector'); $actions[] = id(new PhabricatorActionView()) ->setIcon('fa-link') ->setName(pht('Edit Dependencies')) ->setHref("/search/attach/{$revision_phid}/DREV/dependencies/") ->setWorkflow(true) ->setDisabled(!$can_edit); $maniphest = 'PhabricatorManiphestApplication'; if (PhabricatorApplication::isClassInstalled($maniphest)) { $actions[] = id(new PhabricatorActionView()) ->setIcon('fa-anchor') ->setName(pht('Edit Maniphest Tasks')) ->setHref("/search/attach/{$revision_phid}/TASK/") ->setWorkflow(true) ->setDisabled(!$can_edit); } $request_uri = $this->getRequest()->getRequestURI(); $actions[] = id(new PhabricatorActionView()) ->setIcon('fa-download') ->setName(pht('Download Raw Diff')) ->setHref($request_uri->alter('download', 'true')); return $actions; } private function getRevisionCommentActions(DifferentialRevision $revision) { $actions = array( DifferentialAction::ACTION_COMMENT => true, ); $viewer = $this->getRequest()->getUser(); $viewer_phid = $viewer->getPHID(); $viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID()); $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers()); $status = $revision->getStatus(); $viewer_has_accepted = false; $viewer_has_rejected = false; $status_accepted = DifferentialReviewerStatus::STATUS_ACCEPTED; $status_rejected = DifferentialReviewerStatus::STATUS_REJECTED; foreach ($revision->getReviewerStatus() as $reviewer) { if ($reviewer->getReviewerPHID() == $viewer_phid) { if ($reviewer->getStatus() == $status_accepted) { $viewer_has_accepted = true; } if ($reviewer->getStatus() == $status_rejected) { $viewer_has_rejected = true; } break; } } $allow_self_accept = PhabricatorEnv::getEnvConfig( 'differential.allow-self-accept'); $always_allow_abandon = PhabricatorEnv::getEnvConfig( 'differential.always-allow-abandon'); $always_allow_close = PhabricatorEnv::getEnvConfig( 'differential.always-allow-close'); $allow_reopen = PhabricatorEnv::getEnvConfig( 'differential.allow-reopen'); if ($viewer_is_owner) { switch ($status) { case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: $actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept; $actions[DifferentialAction::ACTION_ABANDON] = true; $actions[DifferentialAction::ACTION_RETHINK] = true; break; case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED: $actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept; $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_CLOSE] = true; break; case ArcanistDifferentialRevisionStatus::CLOSED: break; case ArcanistDifferentialRevisionStatus::ABANDONED: $actions[DifferentialAction::ACTION_RECLAIM] = true; break; } } else { switch ($status) { case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: $actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon; $actions[DifferentialAction::ACTION_ACCEPT] = true; $actions[DifferentialAction::ACTION_REJECT] = true; $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer; break; case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED: $actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon; $actions[DifferentialAction::ACTION_ACCEPT] = true; $actions[DifferentialAction::ACTION_REJECT] = !$viewer_has_rejected; $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer; break; case ArcanistDifferentialRevisionStatus::ACCEPTED: $actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon; $actions[DifferentialAction::ACTION_ACCEPT] = !$viewer_has_accepted; $actions[DifferentialAction::ACTION_REJECT] = true; $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer; break; case ArcanistDifferentialRevisionStatus::CLOSED: case ArcanistDifferentialRevisionStatus::ABANDONED: break; } if ($status != ArcanistDifferentialRevisionStatus::CLOSED) { $actions[DifferentialAction::ACTION_CLAIM] = true; $actions[DifferentialAction::ACTION_CLOSE] = $always_allow_close; } } $actions[DifferentialAction::ACTION_ADDREVIEWERS] = true; $actions[DifferentialAction::ACTION_ADDCCS] = true; $actions[DifferentialAction::ACTION_REOPEN] = $allow_reopen && ($status == ArcanistDifferentialRevisionStatus::CLOSED); $actions = array_keys(array_filter($actions)); $actions_dict = array(); foreach ($actions as $action) { $actions_dict[$action] = DifferentialAction::getActionVerb($action); } return $actions_dict; } private function loadChangesetsAndVsMap( DifferentialDiff $target, DifferentialDiff $diff_vs = null, PhabricatorRepository $repository = null) { $load_diffs = array($target); if ($diff_vs) { $load_diffs[] = $diff_vs; } $raw_changesets = id(new DifferentialChangesetQuery()) ->setViewer($this->getRequest()->getUser()) ->withDiffs($load_diffs) ->execute(); $changeset_groups = mgroup($raw_changesets, 'getDiffID'); $changesets = idx($changeset_groups, $target->getID(), array()); $changesets = mpull($changesets, null, 'getID'); $refs = array(); $vs_map = array(); $vs_changesets = array(); if ($diff_vs) { $vs_id = $diff_vs->getID(); $vs_changesets_path_map = array(); foreach (idx($changeset_groups, $vs_id, array()) as $changeset) { $path = $changeset->getAbsoluteRepositoryPath($repository, $diff_vs); $vs_changesets_path_map[$path] = $changeset; $vs_changesets[$changeset->getID()] = $changeset; } foreach ($changesets as $key => $changeset) { $path = $changeset->getAbsoluteRepositoryPath($repository, $target); if (isset($vs_changesets_path_map[$path])) { $vs_map[$changeset->getID()] = $vs_changesets_path_map[$path]->getID(); $refs[$changeset->getID()] = $changeset->getID().'/'.$vs_changesets_path_map[$path]->getID(); unset($vs_changesets_path_map[$path]); } else { $refs[$changeset->getID()] = $changeset->getID(); } } foreach ($vs_changesets_path_map as $path => $changeset) { $changesets[$changeset->getID()] = $changeset; $vs_map[$changeset->getID()] = -1; $refs[$changeset->getID()] = $changeset->getID().'/-1'; } } else { foreach ($changesets as $changeset) { $refs[$changeset->getID()] = $changeset->getID(); } } $changesets = msort($changesets, 'getSortKey'); return array($changesets, $vs_map, $vs_changesets, $refs); } private function buildSymbolIndexes( PhabricatorRepository $repository, array $visible_changesets) { assert_instances_of($visible_changesets, 'DifferentialChangeset'); $engine = PhabricatorSyntaxHighlighter::newEngine(); $langs = $repository->getSymbolLanguages(); $langs = nonempty($langs, array()); $sources = $repository->getSymbolSources(); $sources = nonempty($sources, array()); $symbol_indexes = array(); if ($langs && $sources) { $have_symbols = id(new DiffusionSymbolQuery()) ->existsSymbolsInRepository($repository->getPHID()); if (!$have_symbols) { return $symbol_indexes; } } $repository_phids = array_merge( array($repository->getPHID()), $sources); $indexed_langs = array_fill_keys($langs, true); foreach ($visible_changesets as $key => $changeset) { $lang = $engine->getLanguageFromFilename($changeset->getFilename()); if (empty($indexed_langs) || isset($indexed_langs[$lang])) { $symbol_indexes[$key] = array( 'lang' => $lang, 'repositories' => $repository_phids, ); } } return $symbol_indexes; } private function loadOtherRevisions( array $changesets, DifferentialDiff $target, PhabricatorRepository $repository) { assert_instances_of($changesets, 'DifferentialChangeset'); $paths = array(); foreach ($changesets as $changeset) { $paths[] = $changeset->getAbsoluteRepositoryPath( $repository, $target); } if (!$paths) { return array(); } $path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs(); if (!$path_map) { return array(); } $recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds')); $query = id(new DifferentialRevisionQuery()) ->setViewer($this->getRequest()->getUser()) ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) ->withUpdatedEpochBetween($recent, null) ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED) ->setLimit(10) ->needFlags(true) ->needDrafts(true) ->needRelationships(true); foreach ($path_map as $path => $path_id) { $query->withPath($repository->getID(), $path_id); } $results = $query->execute(); // Strip out *this* revision. foreach ($results as $key => $result) { if ($result->getID() == $this->revisionID) { unset($results[$key]); } } return $results; } private function renderOtherRevisions(array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) ->setHeader(pht('Recent Similar Open Revisions')); $view = id(new DifferentialRevisionListView()) ->setHeader($header) ->setRevisions($revisions) ->setUser($viewer); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); return $view; } /** * Note this code is somewhat similar to the buildPatch method in * @{class:DifferentialReviewRequestMail}. * * @return @{class:AphrontRedirectResponse} */ private function buildRawDiffResponse( DifferentialRevision $revision, array $changesets, array $vs_changesets, array $vs_map, PhabricatorRepository $repository = null) { assert_instances_of($changesets, 'DifferentialChangeset'); assert_instances_of($vs_changesets, 'DifferentialChangeset'); $viewer = $this->getRequest()->getUser(); id(new DifferentialHunkQuery()) ->setViewer($viewer) ->withChangesets($changesets) ->needAttachToChangesets(true) ->execute(); $diff = new DifferentialDiff(); $diff->attachChangesets($changesets); $raw_changes = $diff->buildChangesList(); $changes = array(); foreach ($raw_changes as $changedict) { $changes[] = ArcanistDiffChange::newFromDictionary($changedict); } $loader = id(new PhabricatorFileBundleLoader()) ->setViewer($viewer); $bundle = ArcanistBundle::newFromChanges($changes); $bundle->setLoadFileDataCallback(array($loader, 'loadFileData')); $vcs = $repository ? $repository->getVersionControlSystem() : null; switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $raw_diff = $bundle->toGitPatch(); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: default: $raw_diff = $bundle->toUnifiedDiff(); break; } $request_uri = $this->getRequest()->getRequestURI(); // this ends up being something like // D123.diff // or the verbose // D123.vs123.id123.whitespaceignore-all.diff // lame but nice to include these options $file_name = ltrim($request_uri->getPath(), '/').'.'; foreach ($request_uri->getQueryParams() as $key => $value) { if ($key == 'download') { continue; } $file_name .= $key.$value.'.'; } $file_name .= 'diff'; $file = PhabricatorFile::buildFromFileDataOrHash( $raw_diff, array( 'name' => $file_name, 'ttl' => (60 * 60 * 24), 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, )); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $file->attachToObject($revision->getPHID()); unset($unguarded); return $file->getRedirectResponse(); } private function buildTransactions( DifferentialRevision $revision, DifferentialDiff $left_diff, DifferentialDiff $right_diff, array $old_ids, array $new_ids) { $timeline = $this->buildTransactionTimeline( $revision, new DifferentialTransactionQuery(), $engine = null, array( 'left' => $left_diff->getID(), 'right' => $right_diff->getID(), 'old' => implode(',', $old_ids), 'new' => implode(',', $new_ids), )); return $timeline; } private function buildRevisionWarnings( DifferentialRevision $revision, PhabricatorCustomFieldList $field_list, array $warning_handle_map, array $handles) { $warnings = array(); foreach ($field_list->getFields() as $key => $field) { $phids = idx($warning_handle_map, $key, array()); $field_handles = array_select_keys($handles, $phids); $field_warnings = $field->getWarningsForRevisionHeader($field_handles); foreach ($field_warnings as $warning) { $warnings[] = $warning; } } return $warnings; } private function buildDiffDetailView( array $diffs, DifferentialRevision $revision, PhabricatorCustomFieldList $field_list) { $viewer = $this->getViewer(); $fields = array(); foreach ($field_list->getFields() as $field) { if ($field->shouldAppearInDiffPropertyView()) { $fields[] = $field; } } if (!$fields) { return null; } // Make sure we're only going to render unique diffs. $diffs = mpull($diffs, null, 'getID'); $labels = array(pht('Left'), pht('Right')); $property_lists = array(); foreach ($diffs as $diff) { if (count($diffs) == 2) { $label = array_shift($labels); $label = pht('%s (Diff %d)', $label, $diff->getID()); } else { $label = pht('Diff %d', $diff->getID()); } $property_lists[] = array( $label, $this->buildDiffPropertyList($diff, $revision, $fields), ); } $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Diff Detail')) ->setUser($viewer); $last_tab = null; foreach ($property_lists as $key => $property_list) { list($tab_name, $list_view) = $property_list; $tab = id(new PHUIListItemView()) ->setKey($key) ->setName($tab_name); $box->addPropertyList($list_view, $tab); $last_tab = $tab; } if ($last_tab) { $last_tab->setSelected(true); } return $box; } private function buildDiffPropertyList( DifferentialDiff $diff, DifferentialRevision $revision, array $fields) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($diff); foreach ($fields as $field) { $label = $field->renderDiffPropertyViewLabel($diff); $value = $field->renderDiffPropertyViewValue($diff); if ($value !== null) { $view->addProperty($label, $value); } } return $view; } }