mirror of
https://we.phorge.it/source/phorge.git
synced 2025-03-12 20:34:54 +01:00
Summary: Ref T13189. See PHI710. Ref T13088. Fixes T9951. Allow callers to `harbormaster.sendmessage` to specify that the test details are remarkup so they can use rich formatting and include links, files, etc. Test Plan: {F5840098} Reviewers: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13189, T13088, T9951 Differential Revision: https://secure.phabricator.com/D19615
1455 lines
42 KiB
PHP
1455 lines
42 KiB
PHP
<?php
|
|
|
|
final class DifferentialRevisionViewController
|
|
extends DifferentialController {
|
|
|
|
private $revisionID;
|
|
private $changesetCount;
|
|
private $hiddenChangesets;
|
|
private $warnings = array();
|
|
|
|
public function shouldAllowPublic() {
|
|
return true;
|
|
}
|
|
|
|
public function isLargeDiff() {
|
|
return ($this->getChangesetCount() > $this->getLargeDiffLimit());
|
|
}
|
|
|
|
public function isVeryLargeDiff() {
|
|
return ($this->getChangesetCount() > $this->getVeryLargeDiffLimit());
|
|
}
|
|
|
|
public function getLargeDiffLimit() {
|
|
return 100;
|
|
}
|
|
|
|
public function getVeryLargeDiffLimit() {
|
|
return 1000;
|
|
}
|
|
|
|
public function getChangesetCount() {
|
|
if ($this->changesetCount === null) {
|
|
throw new PhutilInvalidStateException('setChangesetCount');
|
|
}
|
|
return $this->changesetCount;
|
|
}
|
|
|
|
public function setChangesetCount($count) {
|
|
$this->changesetCount = $count;
|
|
return $this;
|
|
}
|
|
|
|
public function handleRequest(AphrontRequest $request) {
|
|
$viewer = $this->getViewer();
|
|
$this->revisionID = $request->getURIData('id');
|
|
|
|
$viewer_is_anonymous = !$viewer->isLoggedIn();
|
|
|
|
$revision = id(new DifferentialRevisionQuery())
|
|
->withIDs(array($this->revisionID))
|
|
->setViewer($viewer)
|
|
->needReviewers(true)
|
|
->needReviewerAuthority(true)
|
|
->executeOne();
|
|
if (!$revision) {
|
|
return new Aphront404Response();
|
|
}
|
|
|
|
$diffs = id(new DifferentialDiffQuery())
|
|
->setViewer($viewer)
|
|
->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 = $this->getOldDiffID($revision, $diffs);
|
|
if ($diff_vs instanceof AphrontResponse) {
|
|
return $diff_vs;
|
|
}
|
|
|
|
$target_id = $this->getNewDiffID($revision, $diffs);
|
|
if ($target_id instanceof AphrontResponse) {
|
|
return $target_id;
|
|
}
|
|
|
|
$target = $diffs[$target_id];
|
|
|
|
$target_manual = $target;
|
|
if (!$target_id) {
|
|
foreach ($diffs as $diff) {
|
|
if ($diff->getCreationMethod() != 'commit') {
|
|
$target_manual = $diff;
|
|
}
|
|
}
|
|
}
|
|
|
|
$repository = null;
|
|
$repository_phid = $target->getRepositoryPHID();
|
|
if ($repository_phid) {
|
|
if ($repository_phid == $revision->getRepositoryPHID()) {
|
|
$repository = $revision->getRepository();
|
|
} else {
|
|
$repository = id(new PhabricatorRepositoryQuery())
|
|
->setViewer($viewer)
|
|
->withPHIDs(array($repository_phid))
|
|
->executeOne();
|
|
}
|
|
}
|
|
|
|
list($changesets, $vs_map, $vs_changesets, $rendering_references) =
|
|
$this->loadChangesetsAndVsMap(
|
|
$target,
|
|
idx($diffs, $diff_vs),
|
|
$repository);
|
|
|
|
$this->setChangesetCount(count($rendering_references));
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
$this->loadDiffProperties($diffs);
|
|
$props = $target_manual->getDiffProperties();
|
|
|
|
$subscriber_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID(
|
|
$revision->getPHID());
|
|
|
|
$object_phids = array_merge(
|
|
$revision->getReviewerPHIDs(),
|
|
$subscriber_phids,
|
|
$revision->loadCommitPHIDs(),
|
|
array(
|
|
$revision->getAuthorPHID(),
|
|
$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($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);
|
|
$warnings = $this->warnings;
|
|
|
|
$request_uri = $request->getRequestURI();
|
|
|
|
$large = $request->getStr('large');
|
|
|
|
$large_warning =
|
|
($this->isLargeDiff()) &&
|
|
(!$this->isVeryLargeDiff()) &&
|
|
(!$large);
|
|
|
|
if ($large_warning) {
|
|
$count = $this->getChangesetCount();
|
|
|
|
$expand_uri = $request_uri
|
|
->alter('large', 'true')
|
|
->setFragment('toc');
|
|
|
|
$message = array(
|
|
pht(
|
|
'This large diff affects %s files. Files without inline '.
|
|
'comments have been collapsed.',
|
|
new PhutilNumber($count)),
|
|
' ',
|
|
phutil_tag(
|
|
'strong',
|
|
array(),
|
|
phutil_tag(
|
|
'a',
|
|
array(
|
|
'href' => $expand_uri,
|
|
),
|
|
pht('Expand All Files'))),
|
|
);
|
|
|
|
$warnings[] = id(new PHUIInfoView())
|
|
->setTitle(pht('Large Diff'))
|
|
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
|
|
->appendChild($message);
|
|
|
|
$folded_changesets = $changesets;
|
|
} else {
|
|
$folded_changesets = array();
|
|
}
|
|
|
|
// Don't hide or fold changesets which have inline comments.
|
|
$hidden_changesets = $this->hiddenChangesets;
|
|
if ($hidden_changesets || $folded_changesets) {
|
|
$old = array_select_keys($changesets, $old_ids);
|
|
$new = array_select_keys($changesets, $new_ids);
|
|
|
|
$query = id(new DifferentialInlineCommentQuery())
|
|
->setViewer($viewer)
|
|
->needHidden(true)
|
|
->withRevisionPHIDs(array($revision->getPHID()));
|
|
$inlines = $query->execute();
|
|
$inlines = $query->adjustInlinesForChangesets(
|
|
$inlines,
|
|
$old,
|
|
$new,
|
|
$revision);
|
|
|
|
foreach ($inlines as $inline) {
|
|
$changeset_id = $inline->getChangesetID();
|
|
if (!isset($changesets[$changeset_id])) {
|
|
continue;
|
|
}
|
|
|
|
unset($hidden_changesets[$changeset_id]);
|
|
unset($folded_changesets[$changeset_id]);
|
|
}
|
|
}
|
|
|
|
// If we would hide only one changeset, don't hide anything. The notice
|
|
// we'd render about it is about the same size as the changeset.
|
|
if (count($hidden_changesets) < 2) {
|
|
$hidden_changesets = array();
|
|
}
|
|
|
|
// Update the set of hidden changesets, since we may have just un-hidden
|
|
// some of them.
|
|
if ($hidden_changesets) {
|
|
$warnings[] = id(new PHUIInfoView())
|
|
->setTitle(pht('Showing Only Differences'))
|
|
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
|
|
->appendChild(
|
|
pht(
|
|
'This revision modifies %s more files that are hidden because '.
|
|
'they were not modified between selected diffs and they have no '.
|
|
'inline comments.',
|
|
phutil_count($hidden_changesets)));
|
|
}
|
|
|
|
// Compute the unfolded changesets. By default, everything is unfolded.
|
|
$unfolded_changesets = $changesets;
|
|
foreach ($folded_changesets as $changeset_id => $changeset) {
|
|
unset($unfolded_changesets[$changeset_id]);
|
|
}
|
|
|
|
// Throw away any hidden changesets.
|
|
foreach ($hidden_changesets as $changeset_id => $changeset) {
|
|
unset($changesets[$changeset_id]);
|
|
unset($unfolded_changesets[$changeset_id]);
|
|
}
|
|
|
|
$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($viewer)
|
|
->withIdentifiers($commit_hashes)
|
|
->execute();
|
|
$commits_for_links = mpull(
|
|
$commits_for_links,
|
|
null,
|
|
'getCommitIdentifier');
|
|
} else {
|
|
$commits_for_links = array();
|
|
}
|
|
|
|
$header = $this->buildHeader($revision);
|
|
$subheader = $this->buildSubheaderView($revision);
|
|
$details = $this->buildDetails($revision, $field_list);
|
|
$curtain = $this->buildCurtain($revision);
|
|
|
|
$whitespace = $request->getStr(
|
|
'whitespace',
|
|
DifferentialChangesetParser::WHITESPACE_IGNORE_MOST);
|
|
|
|
$repository = $revision->getRepository();
|
|
if ($repository) {
|
|
$symbol_indexes = $this->buildSymbolIndexes(
|
|
$repository,
|
|
$unfolded_changesets);
|
|
} else {
|
|
$symbol_indexes = array();
|
|
}
|
|
|
|
$revision_warnings = $this->buildRevisionWarnings(
|
|
$revision,
|
|
$field_list,
|
|
$warning_handle_map,
|
|
$handles);
|
|
$info_view = null;
|
|
if ($revision_warnings) {
|
|
$info_view = id(new PHUIInfoView())
|
|
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
|
|
->setErrors($revision_warnings);
|
|
}
|
|
|
|
$detail_diffs = array_select_keys(
|
|
$diffs,
|
|
array($diff_vs, $target->getID()));
|
|
$detail_diffs = mpull($detail_diffs, null, 'getPHID');
|
|
|
|
$this->loadHarbormasterData($detail_diffs);
|
|
|
|
$diff_detail_box = $this->buildDiffDetailView(
|
|
$detail_diffs,
|
|
$revision,
|
|
$field_list);
|
|
|
|
$unit_box = $this->buildUnitMessagesView(
|
|
$target,
|
|
$revision);
|
|
|
|
$timeline = $this->buildTransactions(
|
|
$revision,
|
|
$diff_vs ? $diffs[$diff_vs] : $target,
|
|
$target,
|
|
$old_ids,
|
|
$new_ids);
|
|
|
|
$timeline->setQuoteRef($revision->getMonogram());
|
|
|
|
if ($this->isVeryLargeDiff()) {
|
|
$messages = array();
|
|
|
|
$messages[] = pht(
|
|
'This very large diff affects more than %s files. Use the %s to '.
|
|
'browse changes.',
|
|
new PhutilNumber($this->getVeryLargeDiffLimit()),
|
|
phutil_tag(
|
|
'a',
|
|
array(
|
|
'href' => '/differential/diff/'.$target->getID().'/changesets/',
|
|
),
|
|
phutil_tag('strong', array(), pht('Changeset List'))));
|
|
|
|
$changeset_view = id(new PHUIInfoView())
|
|
->setErrors($messages);
|
|
} else {
|
|
$changeset_view = id(new DifferentialChangesetListView())
|
|
->setChangesets($changesets)
|
|
->setVisibleChangesets($unfolded_changesets)
|
|
->setStandaloneURI('/differential/changeset/')
|
|
->setRawFileURIs(
|
|
'/differential/changeset/?view=old',
|
|
'/differential/changeset/?view=new')
|
|
->setUser($viewer)
|
|
->setDiff($target)
|
|
->setRenderingReferences($rendering_references)
|
|
->setVsMap($vs_map)
|
|
->setWhitespace($whitespace)
|
|
->setSymbolIndexes($symbol_indexes)
|
|
->setTitle(pht('Diff %s', $target->getID()))
|
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
|
|
|
|
|
|
$revision_id = $revision->getID();
|
|
$inline_list_uri = "/revision/inlines/{$revision_id}/";
|
|
$inline_list_uri = $this->getApplicationURI($inline_list_uri);
|
|
$changeset_view->setInlineListURI($inline_list_uri);
|
|
|
|
if ($repository) {
|
|
$changeset_view->setRepository($repository);
|
|
}
|
|
|
|
if (!$viewer_is_anonymous) {
|
|
$changeset_view->setInlineCommentControllerURI(
|
|
'/differential/comment/inline/edit/'.$revision->getID().'/');
|
|
}
|
|
}
|
|
|
|
$broken_diffs = $this->loadHistoryDiffStatus($diffs);
|
|
|
|
$history = id(new DifferentialRevisionUpdateHistoryView())
|
|
->setUser($viewer)
|
|
->setDiffs($diffs)
|
|
->setDiffUnitStatuses($broken_diffs)
|
|
->setSelectedVersusDiffID($diff_vs)
|
|
->setSelectedDiffID($target->getID())
|
|
->setSelectedWhitespace($whitespace)
|
|
->setCommitsForLinks($commits_for_links);
|
|
|
|
$local_table = id(new DifferentialLocalCommitsView())
|
|
->setUser($viewer)
|
|
->setLocalCommits(idx($props, 'local:commits'))
|
|
->setCommitsForLinks($commits_for_links);
|
|
|
|
if ($repository && !$this->isVeryLargeDiff()) {
|
|
$other_revisions = $this->loadOtherRevisions(
|
|
$changesets,
|
|
$target,
|
|
$repository);
|
|
} else {
|
|
$other_revisions = array();
|
|
}
|
|
|
|
$other_view = null;
|
|
if ($other_revisions) {
|
|
$other_view = $this->renderOtherRevisions($other_revisions);
|
|
}
|
|
|
|
if ($this->isVeryLargeDiff()) {
|
|
$toc_view = null;
|
|
|
|
// When rendering a "very large" diff, we skip computation of owners
|
|
// that own no files because it is significantly expensive and not very
|
|
// valuable.
|
|
foreach ($revision->getReviewers() as $reviewer) {
|
|
// Give each reviewer a dummy nonempty value so the UI does not render
|
|
// the "(Owns No Changed Paths)" note. If that behavior becomes more
|
|
// sophisticated in the future, this behavior might also need to.
|
|
$reviewer->attachChangesets($changesets);
|
|
}
|
|
} else {
|
|
$this->buildPackageMaps($changesets);
|
|
|
|
$toc_view = $this->buildTableOfContents(
|
|
$changesets,
|
|
$unfolded_changesets,
|
|
$target->loadCoverageMap($viewer));
|
|
|
|
// Attach changesets to each reviewer so we can show which Owners package
|
|
// reviewers own no files.
|
|
foreach ($revision->getReviewers() as $reviewer) {
|
|
$reviewer_phid = $reviewer->getReviewerPHID();
|
|
$reviewer_changesets = $this->getPackageChangesets($reviewer_phid);
|
|
$reviewer->attachChangesets($reviewer_changesets);
|
|
}
|
|
}
|
|
|
|
$tab_group = new PHUITabGroupView();
|
|
|
|
if ($toc_view) {
|
|
$tab_group->addTab(
|
|
id(new PHUITabView())
|
|
->setName(pht('Files'))
|
|
->setKey('files')
|
|
->appendChild($toc_view));
|
|
}
|
|
|
|
$tab_group->addTab(
|
|
id(new PHUITabView())
|
|
->setName(pht('History'))
|
|
->setKey('history')
|
|
->appendChild($history));
|
|
|
|
$filetree_on = $viewer->compareUserSetting(
|
|
PhabricatorShowFiletreeSetting::SETTINGKEY,
|
|
PhabricatorShowFiletreeSetting::VALUE_ENABLE_FILETREE);
|
|
|
|
$collapsed_key = PhabricatorFiletreeVisibleSetting::SETTINGKEY;
|
|
$filetree_collapsed = (bool)$viewer->getUserSetting($collapsed_key);
|
|
|
|
// See PHI811. If the viewer has the file tree on, the files tab with the
|
|
// table of contents is redundant, so default to the "History" tab instead.
|
|
if ($filetree_on && !$filetree_collapsed) {
|
|
$tab_group->selectTab('history');
|
|
}
|
|
|
|
$tab_group->addTab(
|
|
id(new PHUITabView())
|
|
->setName(pht('Commits'))
|
|
->setKey('commits')
|
|
->appendChild($local_table));
|
|
|
|
$stack_graph = id(new DifferentialRevisionGraph())
|
|
->setViewer($viewer)
|
|
->setSeedPHID($revision->getPHID())
|
|
->setLoadEntireGraph(true)
|
|
->loadGraph();
|
|
if (!$stack_graph->isEmpty()) {
|
|
$stack_table = $stack_graph->newGraphTable();
|
|
|
|
$parent_type = DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST;
|
|
$reachable = $stack_graph->getReachableObjects($parent_type);
|
|
|
|
foreach ($reachable as $key => $reachable_revision) {
|
|
if ($reachable_revision->isClosed()) {
|
|
unset($reachable[$key]);
|
|
}
|
|
}
|
|
|
|
if ($reachable) {
|
|
$stack_name = pht('Stack (%s Open)', phutil_count($reachable));
|
|
$stack_color = PHUIListItemView::STATUS_FAIL;
|
|
} else {
|
|
$stack_name = pht('Stack');
|
|
$stack_color = null;
|
|
}
|
|
|
|
$tab_group->addTab(
|
|
id(new PHUITabView())
|
|
->setName($stack_name)
|
|
->setKey('stack')
|
|
->setColor($stack_color)
|
|
->appendChild($stack_table));
|
|
}
|
|
|
|
if ($other_view) {
|
|
$tab_group->addTab(
|
|
id(new PHUITabView())
|
|
->setName(pht('Similar'))
|
|
->setKey('similar')
|
|
->appendChild($other_view));
|
|
}
|
|
|
|
$view_button = id(new PHUIButtonView())
|
|
->setTag('a')
|
|
->setText(pht('Changeset List'))
|
|
->setHref('/differential/diff/'.$target->getID().'/changesets/')
|
|
->setIcon('fa-align-left');
|
|
|
|
$tab_header = id(new PHUIHeaderView())
|
|
->setHeader(pht('Revision Contents'))
|
|
->addActionLink($view_button);
|
|
|
|
$tab_view = id(new PHUIObjectBoxView())
|
|
->setHeader($tab_header)
|
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
|
->addTabGroup($tab_group);
|
|
|
|
$signatures = DifferentialRequiredSignaturesField::loadForRevision(
|
|
$revision);
|
|
$missing_signatures = false;
|
|
foreach ($signatures as $phid => $signed) {
|
|
if (!$signed) {
|
|
$missing_signatures = true;
|
|
}
|
|
}
|
|
|
|
$footer = array();
|
|
$signature_message = null;
|
|
if ($missing_signatures) {
|
|
$signature_message = id(new PHUIInfoView())
|
|
->setTitle(pht('Content Hidden'))
|
|
->appendChild(
|
|
pht(
|
|
'The content of this revision is hidden until the author has '.
|
|
'signed all of the required legal agreements.'));
|
|
} else {
|
|
$anchor = id(new PhabricatorAnchorView())
|
|
->setAnchorName('toc')
|
|
->setNavigationMarker(true);
|
|
|
|
$footer[] = array(
|
|
$anchor,
|
|
$warnings,
|
|
$tab_view,
|
|
$changeset_view,
|
|
);
|
|
}
|
|
|
|
$comment_view = id(new DifferentialRevisionEditEngine())
|
|
->setViewer($viewer)
|
|
->buildEditEngineCommentView($revision);
|
|
|
|
$comment_view->setTransactionTimeline($timeline);
|
|
|
|
$review_warnings = array();
|
|
foreach ($field_list->getFields() as $field) {
|
|
$review_warnings[] = $field->getWarningsForDetailView();
|
|
}
|
|
$review_warnings = array_mergev($review_warnings);
|
|
|
|
if ($review_warnings) {
|
|
$warnings_view = id(new PHUIInfoView())
|
|
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
|
|
->setErrors($review_warnings);
|
|
|
|
$comment_view->setInfoView($warnings_view);
|
|
}
|
|
|
|
$footer[] = $comment_view;
|
|
|
|
$monogram = $revision->getMonogram();
|
|
$operations_box = $this->buildOperationsBox($revision);
|
|
|
|
$crumbs = $this->buildApplicationCrumbs();
|
|
$crumbs->addTextCrumb($monogram);
|
|
$crumbs->setBorder(true);
|
|
|
|
$nav = null;
|
|
if ($filetree_on && !$this->isVeryLargeDiff()) {
|
|
$width_key = PhabricatorFiletreeWidthSetting::SETTINGKEY;
|
|
$width_value = $viewer->getUserSetting($width_key);
|
|
|
|
$nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
|
|
->setTitle($monogram)
|
|
->setBaseURI(new PhutilURI($revision->getURI()))
|
|
->setCollapsed($filetree_collapsed)
|
|
->setWidth((int)$width_value)
|
|
->build($changesets);
|
|
}
|
|
|
|
Javelin::initBehavior('differential-user-select');
|
|
|
|
$view = id(new PHUITwoColumnView())
|
|
->setHeader($header)
|
|
->setSubheader($subheader)
|
|
->setCurtain($curtain)
|
|
->setMainColumn(
|
|
array(
|
|
$operations_box,
|
|
$info_view,
|
|
$details,
|
|
$diff_detail_box,
|
|
$unit_box,
|
|
$timeline,
|
|
$signature_message,
|
|
))
|
|
->setFooter($footer);
|
|
|
|
$page = $this->newPage()
|
|
->setTitle($monogram.' '.$revision->getTitle())
|
|
->setCrumbs($crumbs)
|
|
->setPageObjectPHIDs(array($revision->getPHID()))
|
|
->appendChild($view);
|
|
|
|
if ($nav) {
|
|
$page->setNavigation($nav);
|
|
}
|
|
|
|
return $page;
|
|
}
|
|
|
|
private function buildHeader(DifferentialRevision $revision) {
|
|
$view = id(new PHUIHeaderView())
|
|
->setHeader($revision->getTitle($revision))
|
|
->setUser($this->getViewer())
|
|
->setPolicyObject($revision)
|
|
->setHeaderIcon('fa-cog');
|
|
|
|
$status_tag = id(new PHUITagView())
|
|
->setName($revision->getStatusDisplayName())
|
|
->setIcon($revision->getStatusIcon())
|
|
->setColor($revision->getStatusTagColor())
|
|
->setType(PHUITagView::TYPE_SHADE);
|
|
|
|
$view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_tag);
|
|
|
|
// If the revision is in a status other than "Draft", but not broadcasting,
|
|
// add an additional "Draft" tag to the header to make it clear that this
|
|
// revision hasn't promoted yet.
|
|
if (!$revision->getShouldBroadcast() && !$revision->isDraft()) {
|
|
$draft_status = DifferentialRevisionStatus::newForStatus(
|
|
DifferentialRevisionStatus::DRAFT);
|
|
|
|
$draft_tag = id(new PHUITagView())
|
|
->setName($draft_status->getDisplayName())
|
|
->setIcon($draft_status->getIcon())
|
|
->setColor($draft_status->getTagColor())
|
|
->setType(PHUITagView::TYPE_SHADE);
|
|
|
|
$view->addTag($draft_tag);
|
|
}
|
|
|
|
return $view;
|
|
}
|
|
|
|
private function buildSubheaderView(DifferentialRevision $revision) {
|
|
$viewer = $this->getViewer();
|
|
|
|
$author_phid = $revision->getAuthorPHID();
|
|
|
|
$author = $viewer->renderHandle($author_phid)->render();
|
|
$date = phabricator_datetime($revision->getDateCreated(), $viewer);
|
|
$author = phutil_tag('strong', array(), $author);
|
|
|
|
$handles = $viewer->loadHandles(array($author_phid));
|
|
$image_uri = $handles[$author_phid]->getImageURI();
|
|
$image_href = $handles[$author_phid]->getURI();
|
|
|
|
$content = pht('Authored by %s on %s.', $author, $date);
|
|
|
|
return id(new PHUIHeadThingView())
|
|
->setImage($image_uri)
|
|
->setImageHref($image_href)
|
|
->setContent($content);
|
|
}
|
|
|
|
private function buildDetails(
|
|
DifferentialRevision $revision,
|
|
$custom_fields) {
|
|
$viewer = $this->getViewer();
|
|
$properties = id(new PHUIPropertyListView())
|
|
->setUser($viewer);
|
|
|
|
if ($custom_fields) {
|
|
$custom_fields->appendFieldsToPropertyList(
|
|
$revision,
|
|
$viewer,
|
|
$properties);
|
|
}
|
|
|
|
$header = id(new PHUIHeaderView())
|
|
->setHeader(pht('Details'));
|
|
|
|
return id(new PHUIObjectBoxView())
|
|
->setHeader($header)
|
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
|
->appendChild($properties);
|
|
}
|
|
|
|
private function buildCurtain(DifferentialRevision $revision) {
|
|
$viewer = $this->getViewer();
|
|
$revision_id = $revision->getID();
|
|
$revision_phid = $revision->getPHID();
|
|
$curtain = $this->newCurtainView($revision);
|
|
|
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
|
$viewer,
|
|
$revision,
|
|
PhabricatorPolicyCapability::CAN_EDIT);
|
|
|
|
$curtain->addAction(
|
|
id(new PhabricatorActionView())
|
|
->setIcon('fa-pencil')
|
|
->setHref("/differential/revision/edit/{$revision_id}/")
|
|
->setName(pht('Edit Revision'))
|
|
->setDisabled(!$can_edit)
|
|
->setWorkflow(!$can_edit));
|
|
|
|
$curtain->addAction(
|
|
id(new PhabricatorActionView())
|
|
->setIcon('fa-upload')
|
|
->setHref("/differential/revision/update/{$revision_id}/")
|
|
->setName(pht('Update Diff'))
|
|
->setDisabled(!$can_edit)
|
|
->setWorkflow(!$can_edit));
|
|
|
|
$request_uri = $this->getRequest()->getRequestURI();
|
|
$curtain->addAction(
|
|
id(new PhabricatorActionView())
|
|
->setIcon('fa-download')
|
|
->setName(pht('Download Raw Diff'))
|
|
->setHref($request_uri->alter('download', 'true')));
|
|
|
|
$relationship_list = PhabricatorObjectRelationshipList::newForObject(
|
|
$viewer,
|
|
$revision);
|
|
|
|
$revision_actions = array(
|
|
DifferentialRevisionHasParentRelationship::RELATIONSHIPKEY,
|
|
DifferentialRevisionHasChildRelationship::RELATIONSHIPKEY,
|
|
);
|
|
|
|
$revision_submenu = $relationship_list->newActionSubmenu($revision_actions)
|
|
->setName(pht('Edit Related Revisions...'))
|
|
->setIcon('fa-cog');
|
|
|
|
$curtain->addAction($revision_submenu);
|
|
|
|
$relationship_submenu = $relationship_list->newActionMenu();
|
|
if ($relationship_submenu) {
|
|
$curtain->addAction($relationship_submenu);
|
|
}
|
|
|
|
$repository = $revision->getRepository();
|
|
if ($repository && $repository->canPerformAutomation()) {
|
|
$revision_id = $revision->getID();
|
|
|
|
$op = new DrydockLandRepositoryOperation();
|
|
$barrier = $op->getBarrierToLanding($viewer, $revision);
|
|
|
|
if ($barrier) {
|
|
$can_land = false;
|
|
} else {
|
|
$can_land = true;
|
|
}
|
|
|
|
$action = id(new PhabricatorActionView())
|
|
->setName(pht('Land Revision'))
|
|
->setIcon('fa-fighter-jet')
|
|
->setHref("/differential/revision/operation/{$revision_id}/")
|
|
->setWorkflow(true)
|
|
->setDisabled(!$can_land);
|
|
|
|
$curtain->addAction($action);
|
|
}
|
|
|
|
return $curtain;
|
|
}
|
|
|
|
private function loadHistoryDiffStatus(array $diffs) {
|
|
assert_instances_of($diffs, 'DifferentialDiff');
|
|
|
|
$diff_phids = mpull($diffs, 'getPHID');
|
|
$bad_unit_status = array(
|
|
ArcanistUnitTestResult::RESULT_FAIL,
|
|
ArcanistUnitTestResult::RESULT_BROKEN,
|
|
);
|
|
|
|
$message = new HarbormasterBuildUnitMessage();
|
|
$target = new HarbormasterBuildTarget();
|
|
$build = new HarbormasterBuild();
|
|
$buildable = new HarbormasterBuildable();
|
|
|
|
$broken_diffs = queryfx_all(
|
|
$message->establishConnection('r'),
|
|
'SELECT distinct a.buildablePHID
|
|
FROM %T m
|
|
JOIN %T t ON m.buildTargetPHID = t.phid
|
|
JOIN %T b ON t.buildPHID = b.phid
|
|
JOIN %T a ON b.buildablePHID = a.phid
|
|
WHERE a.buildablePHID IN (%Ls)
|
|
AND m.result in (%Ls)',
|
|
$message->getTableName(),
|
|
$target->getTableName(),
|
|
$build->getTableName(),
|
|
$buildable->getTableName(),
|
|
$diff_phids,
|
|
$bad_unit_status);
|
|
|
|
$unit_status = array();
|
|
foreach ($broken_diffs as $broken) {
|
|
$phid = $broken['buildablePHID'];
|
|
$unit_status[$phid] = DifferentialUnitStatus::UNIT_FAIL;
|
|
}
|
|
|
|
return $unit_status;
|
|
}
|
|
|
|
private function loadChangesetsAndVsMap(
|
|
DifferentialDiff $target,
|
|
DifferentialDiff $diff_vs = null,
|
|
PhabricatorRepository $repository = null) {
|
|
$viewer = $this->getViewer();
|
|
|
|
$load_diffs = array($target);
|
|
if ($diff_vs) {
|
|
$load_diffs[] = $diff_vs;
|
|
}
|
|
|
|
$raw_changesets = id(new DifferentialChangesetQuery())
|
|
->setViewer($viewer)
|
|
->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();
|
|
$must_compare = 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]);
|
|
|
|
$must_compare[] = $changeset->getID();
|
|
|
|
} 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');
|
|
|
|
// See T13137. When displaying the diff between two updates, hide any
|
|
// changesets which haven't actually changed.
|
|
$this->hiddenChangesets = array();
|
|
foreach ($must_compare as $changeset_id) {
|
|
$changeset = $changesets[$changeset_id];
|
|
$vs_changeset = $vs_changesets[$vs_map[$changeset_id]];
|
|
|
|
if ($changeset->hasSameEffectAs($vs_changeset)) {
|
|
$this->hiddenChangesets[$changeset_id] = $changesets[$changeset_id];
|
|
}
|
|
}
|
|
|
|
return array($changesets, $vs_map, $vs_changesets, $refs);
|
|
}
|
|
|
|
private function buildSymbolIndexes(
|
|
PhabricatorRepository $repository,
|
|
array $unfolded_changesets) {
|
|
assert_instances_of($unfolded_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 ($unfolded_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())
|
|
->withIsOpen(true)
|
|
->withUpdatedEpochBetween($recent, null)
|
|
->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED)
|
|
->setLimit(10)
|
|
->needFlags(true)
|
|
->needDrafts(true)
|
|
->needReviewers(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 Revisions'));
|
|
|
|
return id(new DifferentialRevisionListView())
|
|
->setViewer($viewer)
|
|
->setRevisions($revisions)
|
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
|
->setNoBox(true);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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->getViewer();
|
|
|
|
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';
|
|
|
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
|
$file = PhabricatorFile::newFromFileData(
|
|
$raw_diff,
|
|
array(
|
|
'name' => $file_name,
|
|
'ttl.relative' => phutil_units('24 hours in seconds'),
|
|
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
|
|
));
|
|
|
|
$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;
|
|
}
|
|
|
|
$property_lists = array();
|
|
foreach ($this->getDiffTabLabels($diffs) as $tab) {
|
|
list($label, $diff) = $tab;
|
|
|
|
$property_lists[] = array(
|
|
$label,
|
|
$this->buildDiffPropertyList($diff, $revision, $fields),
|
|
);
|
|
}
|
|
|
|
$tab_group = id(new PHUITabGroupView())
|
|
->setHideSingleTab(true);
|
|
|
|
foreach ($property_lists as $key => $property_list) {
|
|
list($tab_name, $list_view) = $property_list;
|
|
|
|
$tab = id(new PHUITabView())
|
|
->setKey($key)
|
|
->setName($tab_name)
|
|
->appendChild($list_view);
|
|
|
|
$tab_group->addTab($tab);
|
|
$tab_group->selectTab($key);
|
|
}
|
|
|
|
return id(new PHUIObjectBoxView())
|
|
->setHeaderText(pht('Diff Detail'))
|
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
|
->setUser($viewer)
|
|
->addTabGroup($tab_group);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
private function buildOperationsBox(DifferentialRevision $revision) {
|
|
$viewer = $this->getViewer();
|
|
|
|
// Save a query if we can't possibly have pending operations.
|
|
$repository = $revision->getRepository();
|
|
if (!$repository || !$repository->canPerformAutomation()) {
|
|
return null;
|
|
}
|
|
|
|
$operations = id(new DrydockRepositoryOperationQuery())
|
|
->setViewer($viewer)
|
|
->withObjectPHIDs(array($revision->getPHID()))
|
|
->withIsDismissed(false)
|
|
->withOperationTypes(
|
|
array(
|
|
DrydockLandRepositoryOperation::OPCONST,
|
|
))
|
|
->execute();
|
|
if (!$operations) {
|
|
return null;
|
|
}
|
|
|
|
$state_fail = DrydockRepositoryOperation::STATE_FAIL;
|
|
|
|
// We're going to show the oldest operation which hasn't failed, or the
|
|
// most recent failure if they're all failures.
|
|
$operations = msort($operations, 'getID');
|
|
foreach ($operations as $operation) {
|
|
if ($operation->getOperationState() != $state_fail) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we found a completed operation, don't render anything. We don't want
|
|
// to show an older error after the thing worked properly.
|
|
if ($operation->isDone()) {
|
|
return null;
|
|
}
|
|
|
|
$box_view = id(new PHUIObjectBoxView())
|
|
->setHeaderText(pht('Active Operations'))
|
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
|
|
|
|
return id(new DrydockRepositoryOperationStatusView())
|
|
->setUser($viewer)
|
|
->setBoxView($box_view)
|
|
->setOperation($operation);
|
|
}
|
|
|
|
private function buildUnitMessagesView(
|
|
$diff,
|
|
DifferentialRevision $revision) {
|
|
$viewer = $this->getViewer();
|
|
|
|
if (!$diff->getBuildable()) {
|
|
return null;
|
|
}
|
|
|
|
if (!$diff->getUnitMessages()) {
|
|
return null;
|
|
}
|
|
|
|
$interesting_messages = array();
|
|
foreach ($diff->getUnitMessages() as $message) {
|
|
switch ($message->getResult()) {
|
|
case ArcanistUnitTestResult::RESULT_PASS:
|
|
case ArcanistUnitTestResult::RESULT_SKIP:
|
|
break;
|
|
default:
|
|
$interesting_messages[] = $message;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$interesting_messages) {
|
|
return null;
|
|
}
|
|
|
|
$excuse = null;
|
|
if ($diff->hasDiffProperty('arc:unit-excuse')) {
|
|
$excuse = $diff->getProperty('arc:unit-excuse');
|
|
}
|
|
|
|
return id(new HarbormasterUnitSummaryView())
|
|
->setViewer($viewer)
|
|
->setExcuse($excuse)
|
|
->setBuildable($diff->getBuildable())
|
|
->setUnitMessages($diff->getUnitMessages())
|
|
->setLimit(5)
|
|
->setShowViewAll(true);
|
|
}
|
|
|
|
private function getOldDiffID(DifferentialRevision $revision, array $diffs) {
|
|
assert_instances_of($diffs, 'DifferentialDiff');
|
|
$request = $this->getRequest();
|
|
|
|
$diffs = mpull($diffs, null, 'getID');
|
|
|
|
$is_new = ($request->getURIData('filter') === 'new');
|
|
$old_id = $request->getInt('vs');
|
|
|
|
// This is ambiguous, so just 404 rather than trying to figure out what
|
|
// the user expects.
|
|
if ($is_new && $old_id) {
|
|
return new Aphront404Response();
|
|
}
|
|
|
|
if ($is_new) {
|
|
$viewer = $this->getViewer();
|
|
|
|
$xactions = id(new DifferentialTransactionQuery())
|
|
->setViewer($viewer)
|
|
->withObjectPHIDs(array($revision->getPHID()))
|
|
->withAuthorPHIDs(array($viewer->getPHID()))
|
|
->setOrder('newest')
|
|
->setLimit(1)
|
|
->execute();
|
|
|
|
if (!$xactions) {
|
|
$this->warnings[] = id(new PHUIInfoView())
|
|
->setTitle(pht('No Actions'))
|
|
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
|
|
->appendChild(
|
|
pht(
|
|
'Showing all changes because you have never taken an '.
|
|
'action on this revision.'));
|
|
} else {
|
|
$xaction = head($xactions);
|
|
|
|
// Find the transactions which updated this revision. We want to
|
|
// figure out which diff was active when you last took an action.
|
|
$updates = id(new DifferentialTransactionQuery())
|
|
->setViewer($viewer)
|
|
->withObjectPHIDs(array($revision->getPHID()))
|
|
->withTransactionTypes(
|
|
array(
|
|
DifferentialRevisionUpdateTransaction::TRANSACTIONTYPE,
|
|
))
|
|
->setOrder('oldest')
|
|
->execute();
|
|
|
|
// Sort the diffs into two buckets: those older than your last action
|
|
// and those newer than your last action.
|
|
$older = array();
|
|
$newer = array();
|
|
foreach ($updates as $update) {
|
|
// If you updated the revision with "arc diff", try to count that
|
|
// update as "before your last action".
|
|
if ($update->getDateCreated() <= $xaction->getDateCreated()) {
|
|
$older[] = $update->getNewValue();
|
|
} else {
|
|
$newer[] = $update->getNewValue();
|
|
}
|
|
}
|
|
|
|
if (!$newer) {
|
|
$this->warnings[] = id(new PHUIInfoView())
|
|
->setTitle(pht('No Recent Updates'))
|
|
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
|
|
->appendChild(
|
|
pht(
|
|
'Showing all changes because the diff for this revision '.
|
|
'has not been updated since your last action.'));
|
|
} else {
|
|
$older = array_fuse($older);
|
|
|
|
// Find the most recent diff from before the last action.
|
|
$old = null;
|
|
foreach ($diffs as $diff) {
|
|
if (!isset($older[$diff->getPHID()])) {
|
|
break;
|
|
}
|
|
|
|
$old = $diff;
|
|
}
|
|
|
|
// It's possible we may not find such a diff: transactions may have
|
|
// been removed from the database, for example. If we miss, just
|
|
// fail into some reasonable state since 404'ing would be perplexing.
|
|
if ($old) {
|
|
$this->warnings[] = id(new PHUIInfoView())
|
|
->setTitle(pht('New Changes Shown'))
|
|
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
|
|
->appendChild(
|
|
pht(
|
|
'Showing changes since the last action you took on this '.
|
|
'revision.'));
|
|
|
|
$old_id = $old->getID();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($diffs[$old_id])) {
|
|
return $old_id;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function getNewDiffID(DifferentialRevision $revision, array $diffs) {
|
|
assert_instances_of($diffs, 'DifferentialDiff');
|
|
$request = $this->getRequest();
|
|
|
|
$diffs = mpull($diffs, null, 'getID');
|
|
|
|
$is_new = ($request->getURIData('filter') === 'new');
|
|
$new_id = $request->getInt('id');
|
|
|
|
if ($is_new && $new_id) {
|
|
return new Aphront404Response();
|
|
}
|
|
|
|
if (isset($diffs[$new_id])) {
|
|
return $new_id;
|
|
}
|
|
|
|
return (int)last_key($diffs);
|
|
}
|
|
|
|
}
|