diff --git a/src/applications/differential/controller/DifferentialChangesetViewController.php b/src/applications/differential/controller/DifferentialChangesetViewController.php index a73875c8f8..3f08ce4883 100644 --- a/src/applications/differential/controller/DifferentialChangesetViewController.php +++ b/src/applications/differential/controller/DifferentialChangesetViewController.php @@ -1,424 +1,423 @@ getViewer(); $rendering_reference = $request->getStr('ref'); $parts = explode('/', $rendering_reference); if (count($parts) == 2) { list($id, $vs) = $parts; } else { $id = $parts[0]; $vs = 0; } $id = (int)$id; $vs = (int)$vs; $load_ids = array($id); if ($vs && ($vs != -1)) { $load_ids[] = $vs; } $changesets = id(new DifferentialChangesetQuery()) ->setViewer($viewer) ->withIDs($load_ids) ->needHunks(true) ->execute(); $changesets = mpull($changesets, null, 'getID'); $changeset = idx($changesets, $id); if (!$changeset) { return new Aphront404Response(); } $vs_changeset = null; if ($vs && ($vs != -1)) { $vs_changeset = idx($changesets, $vs); if (!$vs_changeset) { return new Aphront404Response(); } } $view = $request->getStr('view'); if ($view) { $phid = idx($changeset->getMetadata(), "$view:binary-phid"); if ($phid) { return id(new AphrontRedirectResponse())->setURI("/file/info/$phid/"); } switch ($view) { case 'new': return $this->buildRawFileResponse($changeset, $is_new = true); case 'old': if ($vs_changeset) { return $this->buildRawFileResponse($vs_changeset, $is_new = true); } return $this->buildRawFileResponse($changeset, $is_new = false); default: return new Aphront400Response(); } } $old = array(); $new = array(); if (!$vs) { $right = $changeset; $left = null; $right_source = $right->getID(); $right_new = true; $left_source = $right->getID(); $left_new = false; $render_cache_key = $right->getID(); $old[] = $changeset; $new[] = $changeset; } else if ($vs == -1) { $right = null; $left = $changeset; $right_source = $left->getID(); $right_new = false; $left_source = $left->getID(); $left_new = true; $render_cache_key = null; $old[] = $changeset; $new[] = $changeset; } else { $right = $changeset; $left = $vs_changeset; $right_source = $right->getID(); $right_new = true; $left_source = $left->getID(); $left_new = true; $render_cache_key = null; $new[] = $left; $new[] = $right; } if ($left) { $left_data = $left->makeNewFile(); if ($right) { $right_data = $right->makeNewFile(); } else { $right_data = $left->makeOldFile(); } $engine = new PhabricatorDifferenceEngine(); $synthetic = $engine->generateChangesetFromFileContent( $left_data, $right_data); $choice = clone nonempty($left, $right); $choice->attachHunks($synthetic->getHunks()); $changeset = $choice; } if ($left_new || $right_new) { $diff_map = array(); if ($left) { $diff_map[] = $left->getDiff(); } if ($right) { $diff_map[] = $right->getDiff(); } $diff_map = mpull($diff_map, null, 'getPHID'); $buildables = id(new HarbormasterBuildableQuery()) ->setViewer($viewer) ->withBuildablePHIDs(array_keys($diff_map)) ->withManualBuildables(false) ->needBuilds(true) ->needTargets(true) ->execute(); $buildables = mpull($buildables, null, 'getBuildablePHID'); foreach ($diff_map as $diff_phid => $changeset_diff) { $changeset_diff->attachBuildable(idx($buildables, $diff_phid)); } } $coverage = null; if ($right_new) { $coverage = $this->loadCoverage($right); } $spec = $request->getStr('range'); list($range_s, $range_e, $mask) = DifferentialChangesetParser::parseRangeSpecification($spec); $parser = id(new DifferentialChangesetParser()) ->setCoverage($coverage) ->setChangeset($changeset) ->setRenderingReference($rendering_reference) ->setRenderCacheKey($render_cache_key) ->setRightSideCommentMapping($right_source, $right_new) ->setLeftSideCommentMapping($left_source, $left_new); $parser->readParametersFromRequest($request); if ($left && $right) { $parser->setOriginals($left, $right); } $diff = $changeset->getDiff(); $revision_id = $diff->getRevisionID(); $can_mark = false; $object_owner_phid = null; $revision = null; if ($revision_id) { $revision = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withIDs(array($revision_id)) ->executeOne(); if ($revision) { $can_mark = ($revision->getAuthorPHID() == $viewer->getPHID()); $object_owner_phid = $revision->getAuthorPHID(); } } // Load both left-side and right-side inline comments. if ($revision) { $query = id(new DifferentialInlineCommentQuery()) ->setViewer($viewer) ->needHidden(true) ->withRevisionPHIDs(array($revision->getPHID())); $inlines = $query->execute(); $inlines = $query->adjustInlinesForChangesets( $inlines, $old, $new, $revision); } else { $inlines = array(); } if ($left_new) { $inlines = array_merge( $inlines, $this->buildLintInlineComments($left)); } if ($right_new) { $inlines = array_merge( $inlines, $this->buildLintInlineComments($right)); } $phids = array(); foreach ($inlines as $inline) { $parser->parseInlineComment($inline); if ($inline->getAuthorPHID()) { $phids[$inline->getAuthorPHID()] = true; } } $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); $parser->setHandles($handles); $engine = new PhabricatorMarkupEngine(); $engine->setViewer($viewer); foreach ($inlines as $inline) { $engine->addObject( $inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); } $engine->process(); $parser ->setUser($viewer) ->setMarkupEngine($engine) ->setShowEditAndReplyLinks(true) ->setCanMarkDone($can_mark) ->setObjectOwnerPHID($object_owner_phid) ->setRange($range_s, $range_e) ->setMask($mask); if ($request->isAjax()) { // NOTE: We must render the changeset before we render coverage // information, since it builds some caches. $rendered_changeset = $parser->renderChangeset(); $mcov = $parser->renderModifiedCoverage(); $coverage_data = array( 'differential-mcoverage-'.md5($changeset->getFilename()) => $mcov, ); return id(new PhabricatorChangesetResponse()) ->setRenderedChangeset($rendered_changeset) ->setCoverage($coverage_data) ->setUndoTemplates($parser->getRenderer()->renderUndoTemplates()); } $detail = id(new DifferentialChangesetListView()) ->setUser($this->getViewer()) ->setChangesets(array($changeset)) ->setVisibleChangesets(array($changeset)) ->setRenderingReferences(array($rendering_reference)) ->setRenderURI('/differential/changeset/') ->setDiff($diff) ->setTitle(pht('Standalone View')) ->setParser($parser); if ($revision_id) { $detail->setInlineCommentControllerURI( '/differential/comment/inline/edit/'.$revision_id.'/'); } $crumbs = $this->buildApplicationCrumbs(); if ($revision_id) { $crumbs->addTextCrumb('D'.$revision_id, '/D'.$revision_id); } $diff_id = $diff->getID(); if ($diff_id) { $crumbs->addTextCrumb( pht('Diff %d', $diff_id), $this->getApplicationURI('diff/'.$diff_id)); } $crumbs->addTextCrumb($changeset->getDisplayFilename()); return $this->buildApplicationPage( array( $crumbs, $detail, ), array( 'title' => pht('Changeset View'), 'device' => false, )); } private function buildRawFileResponse( DifferentialChangeset $changeset, $is_new) { $viewer = $this->getViewer(); if ($is_new) { $key = 'raw:new:phid'; } else { $key = 'raw:old:phid'; } $metadata = $changeset->getMetadata(); $file = null; $phid = idx($metadata, $key); if ($phid) { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($phid)) ->execute(); if ($file) { $file = head($file); } } if (!$file) { // This is just building a cache of the changeset content in the file // tool, and is safe to run on a read pathway. $unguard = AphrontWriteGuard::beginScopedUnguardedWrites(); if ($is_new) { $data = $changeset->makeNewFile(); } else { $data = $changeset->makeOldFile(); } $file = PhabricatorFile::newFromFileData( $data, array( 'name' => $changeset->getFilename(), 'mime-type' => 'text/plain', )); $metadata[$key] = $file->getPHID(); $changeset->setMetadata($metadata); $changeset->save(); unset($unguard); } return $file->getRedirectResponse(); } private function buildLintInlineComments($changeset) { $diff = $changeset->getDiff(); $target_phids = $diff->getBuildTargetPHIDs(); if (!$target_phids) { return array(); } $messages = id(new HarbormasterBuildLintMessage())->loadAllWhere( 'buildTargetPHID IN (%Ls) AND path = %s', $target_phids, $changeset->getFilename()); if (!$messages) { return array(); } $template = id(new DifferentialInlineComment()) ->setChangesetID($changeset->getID()) ->setIsNewFile(1) ->setLineLength(0); $inlines = array(); foreach ($messages as $message) { $description = $message->getProperty('description'); - $description = '%%%'.$description.'%%%'; $inlines[] = id(clone $template) ->setSyntheticAuthor(pht('Lint: %s', $message->getName())) ->setLineNumber($message->getLine()) ->setContent($description); } return $inlines; } private function loadCoverage(DifferentialChangeset $changeset) { $target_phids = $changeset->getDiff()->getBuildTargetPHIDs(); if (!$target_phids) { return array(); } $unit = id(new HarbormasterBuildUnitMessage())->loadAllWhere( 'buildTargetPHID IN (%Ls)', $target_phids); $coverage = array(); foreach ($unit as $message) { $test_coverage = $message->getProperty('coverage', array()); $coverage_data = idx($test_coverage, $changeset->getFileName()); if (!strlen($coverage_data)) { continue; } $coverage[] = $coverage_data; } return ArcanistUnitTestResult::mergeCoverage($coverage); } } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index 99ac7e1194..2c47c744ac 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -1,482 +1,479 @@ inlineComment = $comment; return $this; } public function isHidden() { return $this->inlineComment->isHidden(); } public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; return $this; } public function setMarkupEngine(PhabricatorMarkupEngine $engine) { $this->markupEngine = $engine; return $this; } public function setEditable($editable) { $this->editable = $editable; return $this; } public function setPreview($preview) { $this->preview = $preview; return $this; } public function setAllowReply($allow_reply) { $this->allowReply = $allow_reply; return $this; } public function setRenderer($renderer) { $this->renderer = $renderer; return $this; } public function getRenderer() { return $this->renderer; } public function setCanMarkDone($can_mark_done) { $this->canMarkDone = $can_mark_done; return $this; } public function getCanMarkDone() { return $this->canMarkDone; } public function setObjectOwnerPHID($phid) { $this->objectOwnerPHID = $phid; return $this; } public function getObjectOwnerPHID() { return $this->objectOwnerPHID; } public function getAnchorName() { $inline = $this->inlineComment; if ($inline->getID()) { return 'inline-'.$inline->getID(); } return null; } public function getScaffoldCellID() { $anchor = $this->getAnchorName(); if ($anchor) { return 'anchor-'.$anchor; } return null; } public function render() { require_celerity_resource('phui-inline-comment-view-css'); $inline = $this->inlineComment; $classes = array( 'differential-inline-comment', ); $metadata = array( 'id' => $inline->getID(), 'phid' => $inline->getPHID(), 'changesetID' => $inline->getChangesetID(), 'number' => $inline->getLineNumber(), 'length' => $inline->getLineLength(), 'isNewFile' => (bool)$inline->getIsNewFile(), 'on_right' => $this->getIsOnRight(), 'original' => $inline->getContent(), 'replyToCommentPHID' => $inline->getReplyToCommentPHID(), ); $sigil = 'differential-inline-comment'; if ($this->preview) { $sigil = $sigil.' differential-inline-comment-preview'; } $classes = array( 'differential-inline-comment', ); $content = $inline->getContent(); $handles = $this->handles; $links = array(); $is_synthetic = false; if ($inline->getSyntheticAuthor()) { $is_synthetic = true; } $draft_text = null; if (!$is_synthetic) { // This display is controlled by CSS $draft_text = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) ->setName(pht('Unsubmitted')) ->setSlimShady(true) ->setShade(PHUITagView::COLOR_RED) ->addClass('mml inline-draft-text'); } $ghost_tag = null; $ghost = $inline->getIsGhost(); $ghost_id = null; if ($ghost) { if ($ghost['new']) { $ghosticon = 'fa-fast-forward'; $reason = pht('View on forward revision'); } else { $ghosticon = 'fa-fast-backward'; $reason = pht('View on previous revision'); } $ghost_icon = id(new PHUIIconView()) ->setIconFont($ghosticon) ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => $reason, 'size' => 300, )); $ghost_tag = phutil_tag( 'a', array( 'class' => 'ghost-icon', 'href' => $ghost['href'], 'target' => '_blank', ), $ghost_icon); $classes[] = 'inline-comment-ghost'; } // I think this is unused if ($inline->getHasReplies()) { $classes[] = 'inline-comment-has-reply'; } // I think this is unused if ($inline->getReplyToCommentPHID()) { $classes[] = 'inline-comment-is-reply'; } $viewer_phid = $this->getUser()->getPHID(); $owner_phid = $this->getObjectOwnerPHID(); if ($viewer_phid) { if ($viewer_phid == $owner_phid) { $classes[] = 'viewer-is-object-owner'; } } $nextprev = null; if (!$this->preview) { $nextprev = new PHUIButtonBarView(); $nextprev->setBorderless(true); $nextprev->addClass('inline-button-divider'); $up = id(new PHUIButtonView()) ->setTag('a') ->setTooltip(pht('Previous')) ->setIconFont('fa-chevron-up') ->addSigil('differential-inline-prev') ->setMustCapture(true); $down = id(new PHUIButtonView()) ->setTag('a') ->setTooltip(pht('Next')) ->setIconFont('fa-chevron-down') ->addSigil('differential-inline-next') ->setMustCapture(true); if ($this->canHide()) { $hide = id(new PHUIButtonView()) ->setTag('a') ->setTooltip(pht('Hide Comment')) ->setIconFont('fa-times') ->addSigil('hide-inline') ->setMustCapture(true); $nextprev->addButton($hide); } $nextprev->addButton($up); $nextprev->addButton($down); $action_buttons = array(); if ($this->allowReply) { - if (!$is_synthetic) { - // NOTE: No product reason why you can't reply to these, but the reply // mechanism currently sends the inline comment ID to the server, not // file/line information, and synthetic comments don't have an inline // comment ID. $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') ->setIconFont('fa-reply') ->setTooltip(pht('Reply')) ->addSigil('differential-inline-reply') ->setMustCapture(true); } - } } $anchor_name = $this->getAnchorName(); if ($this->editable && !$this->preview) { $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') ->setIconFont('fa-pencil') ->setTooltip(pht('Edit')) ->addSigil('differential-inline-edit') ->setMustCapture(true); $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') ->setIconFont('fa-trash-o') ->setTooltip(pht('Delete')) ->addSigil('differential-inline-delete') ->setMustCapture(true); } else if ($this->preview) { $links[] = javelin_tag( 'a', array( - 'class' => 'inline-button-divider pml msl', - 'meta' => array( + 'class' => 'inline-button-divider pml msl', + 'meta' => array( 'anchor' => $anchor_name, ), - 'sigil' => 'differential-inline-preview-jump', + 'sigil' => 'differential-inline-preview-jump', ), pht('Not Visible')); $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') ->setTooltip(pht('Delete')) ->setIconFont('fa-trash-o') ->addSigil('differential-inline-delete') ->setMustCapture(true); } $done_button = null; if (!$is_synthetic) { $draft_state = false; switch ($inline->getFixedState()) { case PhabricatorInlineCommentInterface::STATE_DRAFT: $is_done = ($this->getCanMarkDone()); $draft_state = true; break; case PhabricatorInlineCommentInterface::STATE_UNDRAFT: $is_done = !($this->getCanMarkDone()); $draft_state = true; break; case PhabricatorInlineCommentInterface::STATE_DONE: $is_done = true; break; default: case PhabricatorInlineCommentInterface::STATE_UNDONE: $is_done = false; break; } // If you don't have permission to mark the comment as "Done", you also // can not see the draft state. if (!$this->getCanMarkDone()) { $draft_state = false; } if ($is_done) { $classes[] = 'inline-is-done'; } if ($draft_state) { $classes[] = 'inline-state-is-draft'; } if ($this->getCanMarkDone()) { $done_input = javelin_tag( 'input', array( 'type' => 'checkbox', 'checked' => ($is_done ? 'checked' : null), 'disabled' => ($this->getCanMarkDone() ? null : 'disabled'), 'class' => 'differential-inline-done', 'sigil' => 'differential-inline-done', )); $done_button = phutil_tag( 'label', array( 'class' => 'differential-inline-done-label '. ($this->getCanMarkDone() ? null : 'done-is-disabled'), ), array( $done_input, pht('Done'), )); } else { if ($is_done) { $icon = id(new PHUIIconView())->setIconFont('fa-check sky msr'); $label = pht('Done'); $class = 'button-done'; } else { $icon = null; $label = pht('Not Done'); $class = 'button-not-done'; } $done_button = phutil_tag( 'div', array( 'class' => 'done-label '.$class, ), array( $icon, $label, )); } } $content = $this->markupEngine->getOutput( $inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); if ($this->preview) { $anchor = null; } else { $anchor = phutil_tag( 'a', array( 'name' => $anchor_name, 'id' => $anchor_name, 'class' => 'differential-inline-comment-anchor', ), ''); } if ($inline->isDraft() && !$is_synthetic) { $classes[] = 'inline-state-is-draft'; } if ($is_synthetic) { $classes[] = 'differential-inline-comment-synthetic'; } $classes = implode(' ', $classes); $author_owner = null; if ($is_synthetic) { $author = $inline->getSyntheticAuthor(); } else { $author = $handles[$inline->getAuthorPHID()]->getName(); if ($inline->getAuthorPHID() == $this->objectOwnerPHID) { $author_owner = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) ->setName(pht('Author')) ->setSlimShady(true) ->setShade(PHUITagView::COLOR_YELLOW) ->addClass('mml'); } } $actions = null; if ($action_buttons) { $actions = new PHUIButtonBarView(); $actions->setBorderless(true); $actions->addClass('inline-button-divider'); foreach ($action_buttons as $button) { $actions->addButton($button); } } $group_left = phutil_tag( 'div', array( 'class' => 'inline-head-left', ), array( $author, $author_owner, $draft_text, $ghost_tag, )); $group_right = phutil_tag( 'div', array( 'class' => 'inline-head-right', ), array( $anchor, $done_button, $links, $actions, $nextprev, )); $markup = javelin_tag( 'div', array( 'class' => $classes, 'sigil' => $sigil, 'meta' => $metadata, ), array( phutil_tag_div('differential-inline-comment-head grouped', array( $group_left, $group_right, )), phutil_tag_div( 'differential-inline-comment-content', phutil_tag_div('phabricator-remarkup', $content)), )); return $markup; } private function canHide() { $inline = $this->inlineComment; if ($inline->isDraft()) { return false; } if (!$inline->getID()) { return false; } $viewer = $this->getUser(); if (!$viewer->isLoggedIn()) { return false; } if (!$inline->supportsHiding()) { return false; } return true; } }