mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-29 02:02:41 +01:00
1710 lines
50 KiB
PHP
1710 lines
50 KiB
PHP
|
<?php
|
||
|
|
||
|
/*
|
||
|
* Copyright 2011 Facebook, Inc.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
class DifferentialRevisionViewController extends DifferentialController {
|
||
|
|
||
|
private $revisionID;
|
||
|
|
||
|
public function willProcessRequest(array $data) {
|
||
|
$this->revisionID = $data['id'];
|
||
|
}
|
||
|
|
||
|
public function processRequest() {
|
||
|
|
||
|
$request = $this->getRequest();
|
||
|
|
||
|
$revision = id(new DifferentialRevision())->load($this->revisionID);
|
||
|
if (!$revision) {
|
||
|
return new Aphront404Response();
|
||
|
}
|
||
|
|
||
|
$revision->loadRelationships();
|
||
|
|
||
|
$diffs = $revision->loadDiffs();
|
||
|
|
||
|
$target = end($diffs);
|
||
|
|
||
|
$changesets = $target->loadChangesets();
|
||
|
|
||
|
$object_phids = array_merge(
|
||
|
$revision->getReviewers(),
|
||
|
$revision->getCCPHIDs(),
|
||
|
array(
|
||
|
$revision->getOwnerPHID(),
|
||
|
$request->getUser()->getPHID(),
|
||
|
));
|
||
|
|
||
|
$handles = id(new PhabricatorObjectHandleData($object_phids))
|
||
|
->loadHandles();
|
||
|
|
||
|
$diff_history = new DifferentialRevisionUpdateHistoryView();
|
||
|
$diff_history->setDiffs($diffs);
|
||
|
|
||
|
$toc_view = new DifferentialDiffTableOfContentsView();
|
||
|
$toc_view->setChangesets($changesets);
|
||
|
|
||
|
$changeset_view = new DifferentialChangesetListView();
|
||
|
$changeset_view->setChangesets($changesets);
|
||
|
|
||
|
$revision_detail = new DifferentialRevisionDetailView();
|
||
|
$revision_detail->setRevision($revision);
|
||
|
|
||
|
$properties = $this->getRevisionProperties($revision, $target, $handles);
|
||
|
$revision_detail->setProperties($properties);
|
||
|
|
||
|
$actions = $this->getRevisionActions($revision);
|
||
|
$revision_detail->setActions($actions);
|
||
|
|
||
|
return $this->buildStandardPageResponse(
|
||
|
'<div class="differential-primary-pane">'.
|
||
|
$revision_detail->render().
|
||
|
$diff_history->render().
|
||
|
$toc_view->render().
|
||
|
$changeset_view->render().
|
||
|
'</div>',
|
||
|
array(
|
||
|
'title' => $revision->getTitle(),
|
||
|
));
|
||
|
}
|
||
|
|
||
|
private function getRevisionProperties(
|
||
|
DifferentialRevision $revision,
|
||
|
DifferentialDiff $diff,
|
||
|
array $handles) {
|
||
|
|
||
|
$properties = array();
|
||
|
|
||
|
$status = $revision->getStatus();
|
||
|
$status = DifferentialRevisionStatus::getNameForRevisionStatus($status);
|
||
|
$properties['Revision Status'] = '<strong>'.$status.'</strong>';
|
||
|
|
||
|
$author = $handles[$revision->getOwnerPHID()];
|
||
|
$properties['Author'] = $author->renderLink();
|
||
|
|
||
|
$properties['Reviewers'] = $this->renderHandleLinkList(
|
||
|
array_select_keys(
|
||
|
$handles,
|
||
|
$revision->getReviewers()));
|
||
|
|
||
|
$properties['CCs'] = $this->renderHandleLinkList(
|
||
|
array_select_keys(
|
||
|
$handles,
|
||
|
$revision->getCCPHIDs()));
|
||
|
|
||
|
$path = $diff->getSourcePath();
|
||
|
if ($path) {
|
||
|
$branch = $diff->getBranch() ? ' (' . $diff->getBranch() . ')' : '';
|
||
|
$host = $diff->getSourceMachine();
|
||
|
if ($host) {
|
||
|
$host .= ':';
|
||
|
}
|
||
|
$properties['Path'] = phutil_escape_html("{$host}{$path} {$branch}");
|
||
|
}
|
||
|
|
||
|
|
||
|
$properties['Lint'] = 'TODO';
|
||
|
$properties['Unit'] = 'TODO';
|
||
|
|
||
|
return $properties;
|
||
|
}
|
||
|
|
||
|
private function getRevisionActions(DifferentialRevision $revision) {
|
||
|
$viewer_phid = $this->getRequest()->getUser()->getPHID();
|
||
|
$viewer_is_owner = ($revision->getOwnerPHID() == $viewer_phid);
|
||
|
$viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
|
||
|
$viewer_is_cc = in_array($viewer_phid, $revision->getCCPHIDs());
|
||
|
$status = $revision->getStatus();
|
||
|
$revision_id = $revision->getID();
|
||
|
$revision_phid = $revision->getPHID();
|
||
|
|
||
|
$links = array();
|
||
|
|
||
|
if ($viewer_is_owner) {
|
||
|
$links[] = array(
|
||
|
'class' => 'revision-edit',
|
||
|
'href' => "/differential/revision/edit/{$revision_id}/",
|
||
|
'name' => 'Edit Revision',
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (!$viewer_is_owner && !$viewer_is_reviewer) {
|
||
|
$action = $viewer_is_cc ? 'rem' : 'add';
|
||
|
$links[] = array(
|
||
|
'class' => $viewer_is_cc ? 'subscribe-rem' : 'subscribe-add',
|
||
|
'href' => "/differential/subscribe/{$action}/{$revision_id}/",
|
||
|
'name' => $viewer_is_cc ? 'Unsubscribe' : 'Subscribe',
|
||
|
);
|
||
|
} else {
|
||
|
$links[] = array(
|
||
|
'class' => 'subscribe-rem unavailable',
|
||
|
'name' => 'Automatically Subscribed',
|
||
|
);
|
||
|
}
|
||
|
|
||
|
$links[] = array(
|
||
|
'class' => 'transcripts-metamta',
|
||
|
'name' => 'MetaMTA Transcripts',
|
||
|
'href' => "/mail/?phid={$revision_phid}",
|
||
|
);
|
||
|
|
||
|
return $links;
|
||
|
}
|
||
|
|
||
|
|
||
|
private function renderHandleLinkList(array $list) {
|
||
|
if (empty($list)) {
|
||
|
return '<em>None</em>';
|
||
|
}
|
||
|
return implode(', ', mpull($list, 'renderLink'));
|
||
|
}
|
||
|
|
||
|
}
|
||
|
/*
|
||
|
|
||
|
|
||
|
$viewer_id = $this->getRequest()->getViewerContext()->getUserID();
|
||
|
$viewer_is_owner = ($viewer_id == $revision->getOwnerID());
|
||
|
$viewer_is_reviewer =
|
||
|
((array_search($viewer_id, $revision->getReviewers())) !== false);
|
||
|
$viewer_is_cc =
|
||
|
((array_search($viewer_id, $revision->getCCFBIDs())) !== false);
|
||
|
$status = $revision->getStatus();
|
||
|
|
||
|
$links = array();
|
||
|
|
||
|
if (!$viewer_is_owner && !$viewer_is_reviewer) {
|
||
|
$action = $viewer_is_cc
|
||
|
? 'rem'
|
||
|
: 'add';
|
||
|
$revision_id = $revision->getID();
|
||
|
$href = "/differential/subscribe/{$action}/{$revision_id}";
|
||
|
$links[] = array(
|
||
|
$viewer_is_cc ? 'subscribe-disabled' : 'subscribe-enabled',
|
||
|
<a href={$href}>{$viewer_is_cc ? 'Unsubscribe' : 'Subscribe'}</a>,
|
||
|
);
|
||
|
} else {
|
||
|
$links[] = array(
|
||
|
'subscribe-disabled unavailable',
|
||
|
<a>Automatically Subscribed</a>,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
$blast_uri = RedirectURI(
|
||
|
'/intern/differential/?action=tasks&fbid='.$revision->getFBID())
|
||
|
->setTier('intern');
|
||
|
$links[] = array(
|
||
|
'tasks',
|
||
|
<a href={$blast_uri}>Edit Tasks</a>,
|
||
|
);
|
||
|
|
||
|
$engineering_repository_id = RepositoryRef::getByCallsign('E')->getID();
|
||
|
$svn_revision = $revision->getSVNRevision();
|
||
|
if ($status == DifferentialConstants::COMMITTED &&
|
||
|
$svn_revision &&
|
||
|
$revision->getRepositoryID() == $engineering_repository_id) {
|
||
|
$href = '/intern/push/request.php?rev='.$svn_revision;
|
||
|
$href = RedirectURI($href)->setTier('intern');
|
||
|
$links[] = array(
|
||
|
'merge',
|
||
|
<a href={$href} id="ask_for_merge_link">Ask for Merge</a>,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
$links[] = array(
|
||
|
'herald-transcript',
|
||
|
<a href={"/herald/transcript/?fbid=".$revision->getFBID()}
|
||
|
>Herald Transcripts</a>,
|
||
|
);
|
||
|
$links[] = array(
|
||
|
'metamta-transcript',
|
||
|
<a href={"/mail/?view=all&fbid=".$revision->getFBID()}
|
||
|
>MetaMTA Transcripts</a>,
|
||
|
);
|
||
|
|
||
|
|
||
|
$list = <ul class="differential-actions" />;
|
||
|
foreach ($links as $link) {
|
||
|
list($class, $tag) = $link;
|
||
|
$list->appendChild(<li class={$class}>{$tag}</li>);
|
||
|
}
|
||
|
|
||
|
return $list;
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
// TODO
|
||
|
// $sandcastle = $this->getSandcastleURI($diff);
|
||
|
// if ($sandcastle) {
|
||
|
// $fields['Sandcastle'] = <a href={$sandcastle}>{$sandcastle}</a>;
|
||
|
// }
|
||
|
|
||
|
$path = $diff->getSourcePath();
|
||
|
if ($path) {
|
||
|
$host = $diff->getSourceMachine();
|
||
|
$branch = $diff->getGitBranch() ? ' (' . $diff->getGitBranch() . ')' : '';
|
||
|
|
||
|
if ($host) {
|
||
|
// TODO
|
||
|
// $user = $handles[$this->getRequest()->getViewerContext()->getUserID()]
|
||
|
// ->getName();
|
||
|
$user = 'TODO';
|
||
|
$fields['Path'] =
|
||
|
<x:frag>
|
||
|
<a href={"ssh://{$user}@{$host}"}>{$host}</a>:{$path}{$branch}
|
||
|
</x:frag>;
|
||
|
} else {
|
||
|
$fields['Path'] = $path;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$reviewer_links = array();
|
||
|
foreach ($revision->getReviewers() as $reviewer) {
|
||
|
$reviewer_links[] = <tools:handle handle={$handles[$reviewer]}
|
||
|
link={true} />;
|
||
|
}
|
||
|
if ($reviewer_links) {
|
||
|
$fields['Reviewers'] = array_implode(', ', $reviewer_links);
|
||
|
} else {
|
||
|
$fields['Reviewers'] = <em>None</em>;
|
||
|
}
|
||
|
|
||
|
$ccs = $revision->getCCFBIDs();
|
||
|
if ($ccs) {
|
||
|
$links = array();
|
||
|
foreach ($ccs as $cc) {
|
||
|
$links[] = <tools:handle handle={$handles[$cc]}
|
||
|
link={true} />;
|
||
|
}
|
||
|
$fields['CCs'] = array_implode(', ', $links);
|
||
|
}
|
||
|
|
||
|
$blame_rev = $revision->getSvnBlameRevision();
|
||
|
if ($blame_rev) {
|
||
|
if ($revision->getRepositoryRef() && is_numeric($blame_rev)) {
|
||
|
$ref = new RevisionRef($revision->getRepositoryRef(), $blame_rev);
|
||
|
$fields['Blame Revision'] =
|
||
|
<a href={URI($ref->getDetailURL())}>
|
||
|
{$ref->getName()}
|
||
|
</a>;
|
||
|
} else {
|
||
|
$fields['Blame Revision'] = $blame_rev;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$tasks = $revision->getTaskHandles();
|
||
|
|
||
|
if ($tasks) {
|
||
|
$links = array();
|
||
|
foreach ($tasks as $task) {
|
||
|
$links[] = <tools:handle handle={$task} link={true} />;
|
||
|
}
|
||
|
$fields['Tasks'] = array_implode(<br />, $links);
|
||
|
}
|
||
|
|
||
|
$bugzilla_id = $revision->getBugzillaID();
|
||
|
if ($bugzilla_id) {
|
||
|
$href = 'http://bugs.developers.facebook.com/show_bug.cgi?id='.
|
||
|
$bugzilla_id;
|
||
|
$fields['Bugzilla'] = <a href={$href}>{'#'.$bugzilla_id}</a>;
|
||
|
}
|
||
|
|
||
|
$fields['Apply Patch'] = <tt>arc patch --revision {$revision->getID()}</tt>;
|
||
|
|
||
|
if ($diff->getParentRevisionID()) {
|
||
|
$parent = id(new DifferentialRevision())->load(
|
||
|
$diff->getParentRevisionID());
|
||
|
if ($parent) {
|
||
|
$fields['Depends On'] =
|
||
|
<a href={$parent->getURI()}>
|
||
|
D{$parent->getID()}: {$parent->getName()}
|
||
|
</a>;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$star = <span class="star">{"\xE2\x98\x85"}</span>;
|
||
|
|
||
|
Javelin::initBehavior('differential-star-more');
|
||
|
|
||
|
switch ($diff->getLinted()) {
|
||
|
case Diff::LINT_FAIL:
|
||
|
$more = $this->renderDiffPropertyMoreLink($diff, 'lint');
|
||
|
$fields['Lint'] =
|
||
|
<x:frag>
|
||
|
<span class="star-warn">{$star} Lint Failures</span>
|
||
|
{$more}
|
||
|
</x:frag>;
|
||
|
break;
|
||
|
case Diff::LINT_WARNINGS:
|
||
|
$more = $this->renderDiffPropertyMoreLink($diff, 'lint');
|
||
|
$fields['Lint'] =
|
||
|
<x:frag>
|
||
|
<span class="star-warn">{$star} Lint Warnings</span>
|
||
|
{$more}
|
||
|
</x:frag>;
|
||
|
break;
|
||
|
case Diff::LINT_OKAY:
|
||
|
$fields['Lint'] =
|
||
|
<span class="star-okay">{$star} Lint Free</span>;
|
||
|
break;
|
||
|
default:
|
||
|
case Diff::LINT_NO:
|
||
|
$fields['Lint'] =
|
||
|
<span class="star-none">{$star} Not Linted</span>;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$unit_details = false;
|
||
|
switch ($diff->getUnitTested()) {
|
||
|
case Diff::UNIT_FAIL:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-warn">{$star} Unit Test Failures</span>;
|
||
|
$unit_details = true;
|
||
|
break;
|
||
|
case Diff::UNIT_WARN:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-warn">{$star} Unit Test Warnings</span>;
|
||
|
$unit_details = true;
|
||
|
break;
|
||
|
case Diff::UNIT_OKAY:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-okay">{$star} Unit Tests Passed</span>;
|
||
|
$unit_details = true;
|
||
|
break;
|
||
|
case Diff::UNIT_NO_TESTS:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-none">{$star} No Test Coverage</span>;
|
||
|
break;
|
||
|
case Diff::UNIT_NO:
|
||
|
default:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-none">{$star} Not Unit Tested</span>;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ($unit_details) {
|
||
|
$fields['Unit Tests'] =
|
||
|
<x:frag>
|
||
|
{$fields['Unit Tests']}
|
||
|
{$this->renderDiffPropertyMoreLink($diff, 'unit')}
|
||
|
</x:frag>;
|
||
|
}
|
||
|
|
||
|
$platform_impact = $revision->getPlatformImpact();
|
||
|
if ($platform_impact) {
|
||
|
$fields['Platform Impact'] =
|
||
|
<text linebreaks="true">{$platform_impact}</text>;
|
||
|
}
|
||
|
|
||
|
return $fields;
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
|
||
|
|
||
|
|
||
|
protected function getSandcastleURI(Diff $diff) {
|
||
|
$uri = $this->getDiffProperty($diff, 'facebook:sandcastle_uri');
|
||
|
if (!$uri) {
|
||
|
$uri = $diff->getSandboxURL();
|
||
|
}
|
||
|
return $uri;
|
||
|
}
|
||
|
|
||
|
protected function getDiffProperty(Diff $diff, $property, $default = null) {
|
||
|
$diff_id = $diff->getID();
|
||
|
if (empty($this->diffProperties[$diff_id])) {
|
||
|
$props = id(new DifferentialDiffProperty())
|
||
|
->loadAllWhere('diffID = %s', $diff_id);
|
||
|
$dict = array_pull($props, 'getData', 'getName');
|
||
|
$this->diffProperties[$diff_id] = $dict;
|
||
|
}
|
||
|
return idx($this->diffProperties[$diff_id], $property, $default);
|
||
|
}
|
||
|
|
||
|
public function process() {
|
||
|
$uri = $this->getRequest()->getPath();
|
||
|
if (starts_with($uri, '/d')) {
|
||
|
return <alite:redirect uri={strtoupper($uri)}/>;
|
||
|
}
|
||
|
|
||
|
$revision = id(new DifferentialRevision())->load($this->revisionID);
|
||
|
if (!$revision) {
|
||
|
throw new Exception("Bad revision ID.");
|
||
|
}
|
||
|
|
||
|
$diffs = id(new Diff())->loadAllWhere(
|
||
|
'revisionID = %d',
|
||
|
$revision->getID());
|
||
|
$diffs = array_psort($diffs, 'getID');
|
||
|
|
||
|
$request = $this->getRequest();
|
||
|
$new = $request->getInt('new');
|
||
|
$old = $request->getInt('old');
|
||
|
|
||
|
if (($new || $old) && $new <= $old) {
|
||
|
throw new Exception(
|
||
|
"You can only view the diff of an older update relative to a newer ".
|
||
|
"update.");
|
||
|
}
|
||
|
|
||
|
if ($new && empty($diffs[$new])) {
|
||
|
throw new Exception(
|
||
|
"The 'new' diff does not exist.");
|
||
|
} else if ($new) {
|
||
|
$diff = $diffs[$new];
|
||
|
} else {
|
||
|
$diff = end($diffs);
|
||
|
if (!$diff) {
|
||
|
throw new Exception("No diff attached to this revision?");
|
||
|
}
|
||
|
$new = $diff->getID();
|
||
|
}
|
||
|
|
||
|
$target_diff = $diff;
|
||
|
|
||
|
if ($old && empty($diffs[$old])) {
|
||
|
throw new Exception(
|
||
|
"The 'old' diff does not exist.");
|
||
|
}
|
||
|
|
||
|
$rows = array(array('Base', '', true, false, null,
|
||
|
$diff->getSourceControlBaseRevision()
|
||
|
? $diff->getSourceControlBaseRevision()
|
||
|
: <em>Master</em>));
|
||
|
$idx = 0;
|
||
|
foreach ($diffs as $cdiff) {
|
||
|
$rows[] = array(
|
||
|
'Diff '.(++$idx),
|
||
|
$cdiff->getID(),
|
||
|
$cdiff->getID() != max(array_pull($diffs, 'getID')),
|
||
|
true,
|
||
|
$cdiff->getDateCreated(),
|
||
|
$cdiff->getDescription()
|
||
|
? $cdiff->getDescription()
|
||
|
: <em>No description available.</em>,
|
||
|
$cdiff->getUnitTested(),
|
||
|
$cdiff->getLinted());
|
||
|
}
|
||
|
|
||
|
$diff_table =
|
||
|
<table class="differential-diff-differ">
|
||
|
<tr>
|
||
|
<th>Diff</th>
|
||
|
<th>Diff ID</th>
|
||
|
<th>Description</th>
|
||
|
<th>Age</th>
|
||
|
<th>Lint</th>
|
||
|
<th>Unit</th>
|
||
|
</tr>
|
||
|
</table>;
|
||
|
$ii = 0;
|
||
|
|
||
|
$old_ids = array();
|
||
|
foreach ($rows as $row) {
|
||
|
$xold = null;
|
||
|
if ($row[2]) {
|
||
|
$lradio = <input name="old" value={$row[1]} type="radio"
|
||
|
disabled={$row[1] >= $new}
|
||
|
checked={$old == $row[1]} />;
|
||
|
if ($old == $row[1]) {
|
||
|
$xold = 'old-now';
|
||
|
}
|
||
|
$old_ids[] = $lradio->requireUniqueID();
|
||
|
} else {
|
||
|
$lradio = null;
|
||
|
}
|
||
|
$xnew = null;
|
||
|
if ($row[3]) {
|
||
|
$rradio = <input name="new" value={$row[1]} type="radio"
|
||
|
sigil="new-radio"
|
||
|
checked={$new == $row[1]} />;
|
||
|
if ($new == $row[1]) {
|
||
|
$xnew = 'new-now';
|
||
|
}
|
||
|
} else {
|
||
|
$rradio = null;
|
||
|
}
|
||
|
|
||
|
if ($row[3]) {
|
||
|
$unit_star = 'star-none';
|
||
|
switch ($row[6]) {
|
||
|
case Diff::UNIT_FAIL:
|
||
|
case Diff::UNIT_WARN: $unit_star = 'star-warn'; break;
|
||
|
case Diff::UNIT_OKAY: $unit_star = 'star-okay'; break;
|
||
|
}
|
||
|
|
||
|
$lint_star = 'star-none';
|
||
|
switch ($row[7]) {
|
||
|
case Diff::LINT_FAIL:
|
||
|
case Diff::LINT_WARNINGS: $lint_star = 'star-warn'; break;
|
||
|
case Diff::LINT_OKAY: $lint_star = 'star-okay'; break;
|
||
|
}
|
||
|
|
||
|
$star = "\xE2\x98\x85";
|
||
|
|
||
|
$unit_star =
|
||
|
<span class={$unit_star}>
|
||
|
<span class="star">{$star}</span>
|
||
|
</span>;
|
||
|
|
||
|
$lint_star =
|
||
|
<span class={$lint_star}>
|
||
|
<span class="star">{$star}</span>
|
||
|
</span>;
|
||
|
} else {
|
||
|
$unit_star = null;
|
||
|
$lint_star = null;
|
||
|
}
|
||
|
|
||
|
$diff_table->appendChild(
|
||
|
<tr class={++$ii % 2 ? 'alt' : null}>
|
||
|
<td class="name">{$row[0]}</td>
|
||
|
<td class="diffid">{$row[1]}</td>
|
||
|
<td class="desc">{$row[5]}</td>
|
||
|
<td class="age">{$row[4] ? ago(time() - $row[4]) : null}</td>
|
||
|
<td class="star">{$lint_star}</td>
|
||
|
<td class="star">{$unit_star}</td>
|
||
|
<td class={"old {$xold}"}>{$lradio}</td>
|
||
|
<td class={"new {$xnew}"}>{$rradio}</td>
|
||
|
</tr>);
|
||
|
}
|
||
|
|
||
|
Javelin::initBehavior('differential-diff-radios', array(
|
||
|
'radios' => $old_ids,
|
||
|
));
|
||
|
|
||
|
$diff_table->appendChild(
|
||
|
<tr>
|
||
|
<td colspan="8" class="diff-differ-submit">
|
||
|
<label>Whitespace Changes:</label>
|
||
|
{id(<select name="whitespace" />)->setOptions(
|
||
|
array(
|
||
|
'ignore-all' => 'Ignore All',
|
||
|
'ignore-trailing' => 'Ignore Trailing',
|
||
|
'show-all' => 'Show All',
|
||
|
), $request->getStr('whitespace'))}{' '}
|
||
|
<button type="submit">Show Diff</button>
|
||
|
</td>
|
||
|
</tr>);
|
||
|
|
||
|
$diff_table =
|
||
|
<div class="differential-table-of-contents">
|
||
|
<h1>Revision Update History</h1>
|
||
|
<form action={URI::getRequestURI()} method="get">
|
||
|
{$diff_table}
|
||
|
</form>
|
||
|
</div>;
|
||
|
|
||
|
|
||
|
$load_ids = array_filter(array($old, $diff->getID()));
|
||
|
|
||
|
$viewer_id = $this->getRequest()->getViewerContext()->getUserID();
|
||
|
|
||
|
$raw_objects = queryfx_all(
|
||
|
smc_get_db('cdb.differential', 'r'),
|
||
|
'SELECT * FROM changeset WHERE changeset.diffID IN (%Ld)',
|
||
|
$load_ids);
|
||
|
|
||
|
$raw_objects = array_group($raw_objects, 'diffID');
|
||
|
$objects = $raw_objects[$diff->getID()];
|
||
|
|
||
|
if (!$objects) {
|
||
|
$changesets = array();
|
||
|
} else {
|
||
|
$changesets = id(new DifferentialChangeset())->loadAllFromArray($objects);
|
||
|
}
|
||
|
|
||
|
$against_warn = null;
|
||
|
$against_map = array();
|
||
|
$visible_changesets = array();
|
||
|
if ($old) {
|
||
|
$old_diff = $diffs[$old];
|
||
|
$new_diff = $diff;
|
||
|
$old_path = $old_diff->getSourcePath();
|
||
|
$new_path = $new_diff->getSourcePath();
|
||
|
|
||
|
$old_prefix = null;
|
||
|
$new_prefix = null;
|
||
|
if ((strlen($old_path) < strlen($new_path)) &&
|
||
|
(!strncmp($old_path, $new_path, strlen($old_path)))) {
|
||
|
$old_prefix = substr($new_path, strlen($old_path));
|
||
|
}
|
||
|
if ((strlen($new_path) < strlen($old_path)) &&
|
||
|
(!strncmp($old_path, $new_path, strlen($new_path)))) {
|
||
|
$new_prefix = substr($old_path, strlen($new_path));
|
||
|
}
|
||
|
|
||
|
$old_changesets = id(new DifferentialChangeset())
|
||
|
->loadAllFromArray($raw_objects[$old]);
|
||
|
$old_changesets = array_pull($old_changesets, null, 'getFilename');
|
||
|
if ($new_prefix) {
|
||
|
$rekeyed_map = array();
|
||
|
foreach ($old_changesets as $key => $value) {
|
||
|
$rekeyed_map[$new_prefix.$key] = $value;
|
||
|
}
|
||
|
$old_changesets = $rekeyed_map;
|
||
|
}
|
||
|
|
||
|
foreach ($changesets as $key => $changeset) {
|
||
|
$file = $old_prefix.$changeset->getFilename();
|
||
|
if (isset($old_changesets[$file])) {
|
||
|
$checksum = $changeset->getChecksum();
|
||
|
if ($checksum !== null &&
|
||
|
$checksum == $old_changesets[$file]->getChecksum()) {
|
||
|
unset($changesets[$key]);
|
||
|
unset($old_changesets[$file]);
|
||
|
} else {
|
||
|
$against_map[$changeset->getID()] = $old_changesets[$file]->getID();
|
||
|
unset($old_changesets[$file]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach ($old_changesets as $changeset) {
|
||
|
$changesets[$changeset->getID()] = $changeset;
|
||
|
$against_map[$changeset->getID()] = -1;
|
||
|
}
|
||
|
|
||
|
$against_warn =
|
||
|
<tools:notice title="NOTE - Diff of Diffs">
|
||
|
You are viewing a synthetic diff between two previous diffs in this
|
||
|
revision. You can not add new inline comments (for now).
|
||
|
</tools:notice>;
|
||
|
} else {
|
||
|
$visible_changesets = array_pull($changesets, 'getID');
|
||
|
}
|
||
|
|
||
|
$changesets = array_psort($changesets, 'getSortKey');
|
||
|
$all_changesets = $changesets;
|
||
|
|
||
|
$warning = null;
|
||
|
$limit = 100;
|
||
|
if (count($changesets) > $limit && !$this->getRequest()->getStr('large')) {
|
||
|
$count = number_format(count($changesets));
|
||
|
$warning =
|
||
|
<tools:notice title="Very Large Diff">
|
||
|
This diff is extremely large and affects {$count} files. Only the
|
||
|
first {number_format($limit)} files are shown.
|
||
|
<strong>
|
||
|
<a href={$revision->getURI().'?large=true'}>Show All Files</a>
|
||
|
</strong>
|
||
|
</tools:notice>;
|
||
|
$changesets = array_slice($changesets, 0, $limit);
|
||
|
if (!$old) {
|
||
|
$visible_changesets = array_pull($changesets, 'getID');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$detail_view =
|
||
|
<differential:changeset-detail-view
|
||
|
changesets={$changesets}
|
||
|
revision={$revision}
|
||
|
against={$against_map}
|
||
|
edit={empty($against_map)}
|
||
|
whitespace={$request->getStr('whitespace')} />;
|
||
|
|
||
|
$table_of_contents =
|
||
|
<differential:changeset-table-of-contents
|
||
|
changesets={$all_changesets} />;
|
||
|
|
||
|
$implied_feedback = array();
|
||
|
foreach (array(
|
||
|
'summarize' => $revision->getSummary(),
|
||
|
'testplan' => $revision->getTestPlan(),
|
||
|
'annotate' => $revision->getNotes(),
|
||
|
) as $type => $text) {
|
||
|
if (!strlen($text)) {
|
||
|
continue;
|
||
|
}
|
||
|
$implied_feedback[] = id(new DifferentialFeedback())
|
||
|
->setUserID($revision->getOwnerID())
|
||
|
->setAction($type)
|
||
|
->setDateCreated($revision->getDateCreated())
|
||
|
->setContent($text);
|
||
|
}
|
||
|
|
||
|
$feedback = id(new DifferentialFeedback())->loadAllWithRevision($revision);
|
||
|
$feedback = array_merge($implied_feedback, $feedback);
|
||
|
|
||
|
$inline_comments = $this->loadInlineComments($feedback, $changesets);
|
||
|
|
||
|
$diff_map = array();
|
||
|
$diffs = array_psort($diffs, 'getID');
|
||
|
foreach ($diffs as $diff) {
|
||
|
$diff_map[$diff->getID()] = count($diff_map) + 1;
|
||
|
}
|
||
|
$visible_changesets = array_fill_keys($visible_changesets, true);
|
||
|
$hidden_changesets = array();
|
||
|
foreach ($changesets as $changeset) {
|
||
|
$id = $changeset->getID();
|
||
|
if (isset($visible_changesets[$id])) {
|
||
|
continue;
|
||
|
}
|
||
|
$hidden_changesets[$id] = $diff_map[$changeset->getDiffID()];
|
||
|
}
|
||
|
|
||
|
$revision->loadRelationships();
|
||
|
$ccs = $revision->getCCFBIDs();
|
||
|
$reviewers = $revision->getReviewers();
|
||
|
|
||
|
$actors = array_pull($feedback, 'getUserID');
|
||
|
$actors[] = $revision->getOwnerID();
|
||
|
|
||
|
$tasks = array();
|
||
|
assoc_get_by_type(
|
||
|
$revision->getFBID(),
|
||
|
22284182462, // TODO: include issue, DIFFCAMP_TASK_ASSOC
|
||
|
$start = null,
|
||
|
$limit = null,
|
||
|
$pending = true,
|
||
|
$tasks);
|
||
|
memcache_dispatch();
|
||
|
$tasks = array_keys($tasks);
|
||
|
|
||
|
$preparer = new Preparer();
|
||
|
$fbids = array_merge_fast(
|
||
|
array($actors, array($viewer_id), $reviewers, $ccs, $tasks),
|
||
|
true);
|
||
|
$handles = array();
|
||
|
$handle_data = id(new ToolsHandleData($fbids, $handles))
|
||
|
->needNames()
|
||
|
->needAlternateNames()
|
||
|
->needAlternateIDs()
|
||
|
->needThumbnails();
|
||
|
$preparer->waitFor($handle_data);
|
||
|
$preparer->go();
|
||
|
|
||
|
$revision->attachTaskHandles(array_select_keys($handles, $tasks));
|
||
|
|
||
|
$inline_comments = array_group($inline_comments, 'getFeedbackID');
|
||
|
|
||
|
$engine = new RemarkupEngine();
|
||
|
$engine->enableFeature(RemarkupEngine::FEATURE_GUESS_IMAGES);
|
||
|
$engine->enableFeature(RemarkupEngine::FEATURE_YOUTUBE);
|
||
|
$engine->setCurrentSandcastle($this->getSandcastleURI($target_diff));
|
||
|
$feed = array();
|
||
|
foreach ($feedback as $comment) {
|
||
|
$inlines = null;
|
||
|
if (isset($inline_comments[$comment->getID()])) {
|
||
|
$inlines = $inline_comments[$comment->getID()];
|
||
|
}
|
||
|
$feed[] =
|
||
|
<differential:feedback
|
||
|
feedback={$comment}
|
||
|
handle={$handles[$comment->getUserID()]}
|
||
|
engine={$engine}
|
||
|
inline={$inlines}
|
||
|
changesets={$changesets}
|
||
|
hidden={$hidden_changesets} />;
|
||
|
}
|
||
|
|
||
|
$feed = $this->renderFeedbackList($feed, $feedback, $viewer_id);
|
||
|
|
||
|
$fields = $this->getDetailFields($revision, $diff, $handles);
|
||
|
$table = <table class="differential-revision-properties" />;
|
||
|
foreach ($fields as $key => $value) {
|
||
|
$table->appendChild(
|
||
|
<tr>
|
||
|
<th>{$key}:</th><td>{$value}</td>
|
||
|
</tr>);
|
||
|
}
|
||
|
|
||
|
$quick_links = $this->getQuickLinks($revision);
|
||
|
|
||
|
$edit_link = null;
|
||
|
if ($revision->getOwnerID() == $viewer_id) {
|
||
|
$edit_link = '/differential/revision/edit/'.$revision->getID().'/';
|
||
|
$edit_link =
|
||
|
<x:frag>
|
||
|
{' '}(<a href={$edit_link}>Edit Revision</a>)
|
||
|
</x:frag>;
|
||
|
}
|
||
|
|
||
|
$info =
|
||
|
<div class="differential-revision-information">
|
||
|
<div class="differential-revision-actions">
|
||
|
{$quick_links}
|
||
|
</div>
|
||
|
<div class="differential-revision-detail">
|
||
|
<h1>{$revision->getName()}{$edit_link}</h1>
|
||
|
{$table}
|
||
|
</div>
|
||
|
</div>;
|
||
|
|
||
|
$actions = $this->getRevisionActions($revision);
|
||
|
$revision_id = $revision->getID();
|
||
|
|
||
|
Javelin::initBehavior(
|
||
|
'differential-feedback-preview',
|
||
|
array(
|
||
|
'uri' => '/differential/preview/'.$revision->getFBID().'/',
|
||
|
'preview' => 'overall-feedback-preview',
|
||
|
'action' => 'feedback-action',
|
||
|
'content' => 'feedback-content',
|
||
|
));
|
||
|
|
||
|
Javelin::initBehavior(
|
||
|
'differential-inline-comment-preview',
|
||
|
array(
|
||
|
'uri' => '/differential/inline-preview/'.$revision_id.'/'.$new.'/',
|
||
|
'preview' => 'inline-comment-preview',
|
||
|
));
|
||
|
|
||
|
$content = SavedCopy::loadData(
|
||
|
$viewer_id,
|
||
|
SavedCopy::Type_DifferentialRevisionFeedback,
|
||
|
$revision->getFBID());
|
||
|
|
||
|
|
||
|
$inline_comment_container =
|
||
|
<div id="inline-comment-preview"><p>Loading...</p></div>;
|
||
|
|
||
|
$feedback = id(new DifferentialFeedback())
|
||
|
->setAction('none')
|
||
|
->setUserID($viewer_id)
|
||
|
->setContent($content);
|
||
|
|
||
|
$preview =
|
||
|
<div class="differential-feedback differential-feedback-preview">
|
||
|
<div id="overall-feedback-preview">
|
||
|
<differential:feedback
|
||
|
feedback={$feedback}
|
||
|
engine={$engine}
|
||
|
preview={true}
|
||
|
handle={$handles[$viewer_id]} />
|
||
|
</div>
|
||
|
{$inline_comment_container}
|
||
|
</div>;
|
||
|
|
||
|
$syntax_link =
|
||
|
<a href={'http://www.intern.facebook.com/intern/wiki/index.php' .
|
||
|
'/Articles/Remarkup_Syntax_Reference'}
|
||
|
target="_blank"
|
||
|
tabindex="4">Remarkup Reference</a>;
|
||
|
|
||
|
Javelin::initBehavior(
|
||
|
'differential-add-reviewers',
|
||
|
array(
|
||
|
'src' => redirect_str('/datasource/employee/', 'tools'),
|
||
|
'tokenizer' => 'reviewer-tokenizer',
|
||
|
'select' => 'feedback-action',
|
||
|
'row' => 'reviewer-tokenizer-row',
|
||
|
));
|
||
|
|
||
|
$feedback_form =
|
||
|
<x:frag>
|
||
|
<div class="differential-feedback-form">
|
||
|
<tools:form
|
||
|
method="post"
|
||
|
action={"/differential/revision/feedback/{$revision_id}/"}>
|
||
|
<h1>Provide Feedback</h1>
|
||
|
<tools:fieldset>
|
||
|
<tools:control type="select" label="Action">
|
||
|
{id(<select name="action" id="feedback-action"
|
||
|
tabindex="1" />)
|
||
|
->setOptions($actions)}
|
||
|
</tools:control>
|
||
|
<tools:control type="text" label="Reviewers"
|
||
|
style="display: none;"
|
||
|
id="reviewer-tokenizer-row">
|
||
|
<javelin:tokenizer-template
|
||
|
id="reviewer-tokenizer"
|
||
|
name="reviewers" />
|
||
|
</tools:control>
|
||
|
<tools:control type="textarea" label="Feedback"
|
||
|
caption={$syntax_link}>
|
||
|
<tools:droppable-textarea id="feedback-content" name="feedback"
|
||
|
tabindex="2">
|
||
|
{$content}
|
||
|
</tools:droppable-textarea>
|
||
|
</tools:control>
|
||
|
<tools:control type="submit">
|
||
|
<button type="submit"
|
||
|
tabindex="3">Clowncopterize</button>
|
||
|
</tools:control>
|
||
|
</tools:fieldset>
|
||
|
</tools:form>
|
||
|
</div>
|
||
|
{$preview}
|
||
|
</x:frag>;
|
||
|
|
||
|
$notice = null;
|
||
|
if ($this->getRequest()->getBool('diff_changed')) {
|
||
|
$notice =
|
||
|
<tools:notice title="Revision Updated Recently">
|
||
|
This revision was updated with a <strong>new diff</strong> while you
|
||
|
were providing feedback. Your inline comments appear on the
|
||
|
<strong>old diff</strong>.
|
||
|
</tools:notice>;
|
||
|
}
|
||
|
|
||
|
return
|
||
|
<differential:standard-page title={$revision->getName()}>
|
||
|
<div class="differential-primary-pane">
|
||
|
{$warning}
|
||
|
{$notice}
|
||
|
{$info}
|
||
|
<div class="differential-feedback">
|
||
|
{$feed}
|
||
|
</div>
|
||
|
{$diff_table}
|
||
|
{$table_of_contents}
|
||
|
{$against_warn}
|
||
|
{$detail_view}
|
||
|
{$feedback_form}
|
||
|
</div>
|
||
|
</differential:standard-page>;
|
||
|
}
|
||
|
|
||
|
protected function getQuickLinks(DifferentialRevision $revision) {
|
||
|
|
||
|
$viewer_id = $this->getRequest()->getViewerContext()->getUserID();
|
||
|
$viewer_is_owner = ($viewer_id == $revision->getOwnerID());
|
||
|
$viewer_is_reviewer =
|
||
|
((array_search($viewer_id, $revision->getReviewers())) !== false);
|
||
|
$viewer_is_cc =
|
||
|
((array_search($viewer_id, $revision->getCCFBIDs())) !== false);
|
||
|
$status = $revision->getStatus();
|
||
|
|
||
|
$links = array();
|
||
|
|
||
|
if (!$viewer_is_owner && !$viewer_is_reviewer) {
|
||
|
$action = $viewer_is_cc
|
||
|
? 'rem'
|
||
|
: 'add';
|
||
|
$revision_id = $revision->getID();
|
||
|
$href = "/differential/subscribe/{$action}/{$revision_id}";
|
||
|
$links[] = array(
|
||
|
$viewer_is_cc ? 'subscribe-disabled' : 'subscribe-enabled',
|
||
|
<a href={$href}>{$viewer_is_cc ? 'Unsubscribe' : 'Subscribe'}</a>,
|
||
|
);
|
||
|
} else {
|
||
|
$links[] = array(
|
||
|
'subscribe-disabled unavailable',
|
||
|
<a>Automatically Subscribed</a>,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
$blast_uri = RedirectURI(
|
||
|
'/intern/differential/?action=blast&fbid='.$revision->getFBID())
|
||
|
->setTier('intern');
|
||
|
$links[] = array(
|
||
|
'blast',
|
||
|
<a href={$blast_uri}>Blast Revision</a>,
|
||
|
);
|
||
|
|
||
|
$blast_uri = RedirectURI(
|
||
|
'/intern/differential/?action=tasks&fbid='.$revision->getFBID())
|
||
|
->setTier('intern');
|
||
|
$links[] = array(
|
||
|
'tasks',
|
||
|
<a href={$blast_uri}>Edit Tasks</a>,
|
||
|
);
|
||
|
|
||
|
if ($viewer_is_owner && false) {
|
||
|
$perflab_uri = RedirectURI(
|
||
|
'/intern/differential/?action=perflab&fbid='.$revision->getFBID())
|
||
|
->setTier('intern');
|
||
|
$links[] = array(
|
||
|
'perflab',
|
||
|
<a href={$perflab_uri}>Run in Perflab</a>,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
$engineering_repository_id = RepositoryRef::getByCallsign('E')->getID();
|
||
|
$svn_revision = $revision->getSVNRevision();
|
||
|
if ($status == DifferentialConstants::COMMITTED &&
|
||
|
$svn_revision &&
|
||
|
$revision->getRepositoryID() == $engineering_repository_id) {
|
||
|
$href = '/intern/push/request.php?rev='.$svn_revision;
|
||
|
$href = RedirectURI($href)->setTier('intern');
|
||
|
$links[] = array(
|
||
|
'merge',
|
||
|
<a href={$href} id="ask_for_merge_link">Ask for Merge</a>,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
$links[] = array(
|
||
|
'herald-transcript',
|
||
|
<a href={"/herald/transcript/?fbid=".$revision->getFBID()}
|
||
|
>Herald Transcripts</a>,
|
||
|
);
|
||
|
$links[] = array(
|
||
|
'metamta-transcript',
|
||
|
<a href={"/mail/?view=all&fbid=".$revision->getFBID()}
|
||
|
>MetaMTA Transcripts</a>,
|
||
|
);
|
||
|
|
||
|
|
||
|
$list = <ul class="differential-actions" />;
|
||
|
foreach ($links as $link) {
|
||
|
list($class, $tag) = $link;
|
||
|
$list->appendChild(<li class={$class}>{$tag}</li>);
|
||
|
}
|
||
|
|
||
|
return $list;
|
||
|
}
|
||
|
|
||
|
protected function getDetailFields(
|
||
|
DifferentialRevision $revision,
|
||
|
Diff $diff,
|
||
|
array $handles) {
|
||
|
|
||
|
$fields = array();
|
||
|
$fields['Revision Status'] = $this->getRevisionStatusDisplay($revision);
|
||
|
|
||
|
$author = $revision->getOwnerID();
|
||
|
$fields['Author'] = <tools:handle handle={$handles[$author]}
|
||
|
link={true} />;
|
||
|
|
||
|
$sandcastle = $this->getSandcastleURI($diff);
|
||
|
if ($sandcastle) {
|
||
|
$fields['Sandcastle'] = <a href={$sandcastle}>{$sandcastle}</a>;
|
||
|
}
|
||
|
|
||
|
$path = $diff->getSourcePath();
|
||
|
if ($path) {
|
||
|
$host = $diff->getSourceMachine();
|
||
|
$branch = $diff->getGitBranch() ? ' (' . $diff->getGitBranch() . ')' : '';
|
||
|
|
||
|
if ($host) {
|
||
|
$user = $handles[$this->getRequest()->getViewerContext()->getUserID()]
|
||
|
->getName();
|
||
|
$fields['Path'] =
|
||
|
<x:frag>
|
||
|
<a href={"ssh://{$user}@{$host}"}>{$host}</a>:{$path}{$branch}
|
||
|
</x:frag>;
|
||
|
} else {
|
||
|
$fields['Path'] = $path;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$reviewer_links = array();
|
||
|
foreach ($revision->getReviewers() as $reviewer) {
|
||
|
$reviewer_links[] = <tools:handle handle={$handles[$reviewer]}
|
||
|
link={true} />;
|
||
|
}
|
||
|
if ($reviewer_links) {
|
||
|
$fields['Reviewers'] = array_implode(', ', $reviewer_links);
|
||
|
} else {
|
||
|
$fields['Reviewers'] = <em>None</em>;
|
||
|
}
|
||
|
|
||
|
$ccs = $revision->getCCFBIDs();
|
||
|
if ($ccs) {
|
||
|
$links = array();
|
||
|
foreach ($ccs as $cc) {
|
||
|
$links[] = <tools:handle handle={$handles[$cc]}
|
||
|
link={true} />;
|
||
|
}
|
||
|
$fields['CCs'] = array_implode(', ', $links);
|
||
|
}
|
||
|
|
||
|
$blame_rev = $revision->getSvnBlameRevision();
|
||
|
if ($blame_rev) {
|
||
|
if ($revision->getRepositoryRef() && is_numeric($blame_rev)) {
|
||
|
$ref = new RevisionRef($revision->getRepositoryRef(), $blame_rev);
|
||
|
$fields['Blame Revision'] =
|
||
|
<a href={URI($ref->getDetailURL())}>
|
||
|
{$ref->getName()}
|
||
|
</a>;
|
||
|
} else {
|
||
|
$fields['Blame Revision'] = $blame_rev;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$tasks = $revision->getTaskHandles();
|
||
|
|
||
|
if ($tasks) {
|
||
|
$links = array();
|
||
|
foreach ($tasks as $task) {
|
||
|
$links[] = <tools:handle handle={$task} link={true} />;
|
||
|
}
|
||
|
$fields['Tasks'] = array_implode(<br />, $links);
|
||
|
}
|
||
|
|
||
|
$bugzilla_id = $revision->getBugzillaID();
|
||
|
if ($bugzilla_id) {
|
||
|
$href = 'http://bugs.developers.facebook.com/show_bug.cgi?id='.
|
||
|
$bugzilla_id;
|
||
|
$fields['Bugzilla'] = <a href={$href}>{'#'.$bugzilla_id}</a>;
|
||
|
}
|
||
|
|
||
|
$fields['Apply Patch'] = <tt>arc patch --revision {$revision->getID()}</tt>;
|
||
|
|
||
|
if ($diff->getParentRevisionID()) {
|
||
|
$parent = id(new DifferentialRevision())->load(
|
||
|
$diff->getParentRevisionID());
|
||
|
if ($parent) {
|
||
|
$fields['Depends On'] =
|
||
|
<a href={$parent->getURI()}>
|
||
|
D{$parent->getID()}: {$parent->getName()}
|
||
|
</a>;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$star = <span class="star">{"\xE2\x98\x85"}</span>;
|
||
|
|
||
|
Javelin::initBehavior('differential-star-more');
|
||
|
|
||
|
switch ($diff->getLinted()) {
|
||
|
case Diff::LINT_FAIL:
|
||
|
$more = $this->renderDiffPropertyMoreLink($diff, 'lint');
|
||
|
$fields['Lint'] =
|
||
|
<x:frag>
|
||
|
<span class="star-warn">{$star} Lint Failures</span>
|
||
|
{$more}
|
||
|
</x:frag>;
|
||
|
break;
|
||
|
case Diff::LINT_WARNINGS:
|
||
|
$more = $this->renderDiffPropertyMoreLink($diff, 'lint');
|
||
|
$fields['Lint'] =
|
||
|
<x:frag>
|
||
|
<span class="star-warn">{$star} Lint Warnings</span>
|
||
|
{$more}
|
||
|
</x:frag>;
|
||
|
break;
|
||
|
case Diff::LINT_OKAY:
|
||
|
$fields['Lint'] =
|
||
|
<span class="star-okay">{$star} Lint Free</span>;
|
||
|
break;
|
||
|
default:
|
||
|
case Diff::LINT_NO:
|
||
|
$fields['Lint'] =
|
||
|
<span class="star-none">{$star} Not Linted</span>;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$unit_details = false;
|
||
|
switch ($diff->getUnitTested()) {
|
||
|
case Diff::UNIT_FAIL:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-warn">{$star} Unit Test Failures</span>;
|
||
|
$unit_details = true;
|
||
|
break;
|
||
|
case Diff::UNIT_WARN:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-warn">{$star} Unit Test Warnings</span>;
|
||
|
$unit_details = true;
|
||
|
break;
|
||
|
case Diff::UNIT_OKAY:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-okay">{$star} Unit Tests Passed</span>;
|
||
|
$unit_details = true;
|
||
|
break;
|
||
|
case Diff::UNIT_NO_TESTS:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-none">{$star} No Test Coverage</span>;
|
||
|
break;
|
||
|
case Diff::UNIT_NO:
|
||
|
default:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-none">{$star} Not Unit Tested</span>;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ($unit_details) {
|
||
|
$fields['Unit Tests'] =
|
||
|
<x:frag>
|
||
|
{$fields['Unit Tests']}
|
||
|
{$this->renderDiffPropertyMoreLink($diff, 'unit')}
|
||
|
</x:frag>;
|
||
|
}
|
||
|
|
||
|
$platform_impact = $revision->getPlatformImpact();
|
||
|
if ($platform_impact) {
|
||
|
$fields['Platform Impact'] =
|
||
|
<text linebreaks="true">{$platform_impact}</text>;
|
||
|
}
|
||
|
|
||
|
return $fields;
|
||
|
}
|
||
|
|
||
|
protected function renderDiffPropertyMoreLink(Diff $diff, $name) {
|
||
|
$target = <div class="star-more"
|
||
|
style="display: none;">
|
||
|
<div class="star-loading">Loading...</div>
|
||
|
</div>;
|
||
|
$meta = array(
|
||
|
'target' => $target->requireUniqueID(),
|
||
|
'uri' => '/differential/diffprop/'.$diff->getID().'/'.$name.'/',
|
||
|
);
|
||
|
$more =
|
||
|
<span sigil="star-link-container">
|
||
|
·
|
||
|
<a mustcapture="true"
|
||
|
sigil="star-more"
|
||
|
href="#"
|
||
|
meta={$meta}>Show Details</a>
|
||
|
</span>;
|
||
|
return <x:frag>{$more}{$target}</x:frag>;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
protected function loadInlineComments(array $feedback, array &$changesets) {
|
||
|
|
||
|
$inline_comments = array();
|
||
|
$feedback_ids = array_filter(array_pull($feedback, 'getID'));
|
||
|
if (!$feedback_ids) {
|
||
|
return $inline_comments;
|
||
|
}
|
||
|
|
||
|
$inline_comments = id(new DifferentialInlineComment())
|
||
|
->loadAllWhere('feedbackID in (%Ld)', $feedback_ids);
|
||
|
|
||
|
$load_changesets = array();
|
||
|
$load_hunks = array();
|
||
|
foreach ($inline_comments as $inline) {
|
||
|
$changeset_id = $inline->getChangesetID();
|
||
|
if (isset($changesets[$changeset_id])) {
|
||
|
continue;
|
||
|
}
|
||
|
$load_changesets[$changeset_id] = true;
|
||
|
}
|
||
|
|
||
|
$more_changesets = array();
|
||
|
if ($load_changesets) {
|
||
|
$changeset_ids = array_keys($load_changesets);
|
||
|
$more_changesets += id(new DifferentialChangeset())
|
||
|
->loadAllWithIDs($changeset_ids);
|
||
|
}
|
||
|
|
||
|
if ($more_changesets) {
|
||
|
$changesets += $more_changesets;
|
||
|
$changesets = array_psort($changesets, 'getSortKey');
|
||
|
}
|
||
|
|
||
|
return $inline_comments;
|
||
|
}
|
||
|
|
||
|
protected function getRevisionActions(DifferentialRevision $revision) {
|
||
|
$actions = array(
|
||
|
'none' => true,
|
||
|
);
|
||
|
|
||
|
$viewer = $this->getRequest()->getViewerContext();
|
||
|
|
||
|
$viewer_is_owner = ($viewer->getUserID() == $revision->getOwnerID());
|
||
|
if ($viewer_is_owner) {
|
||
|
switch ($revision->getStatus()) {
|
||
|
case DifferentialConstants::NEEDS_REVIEW:
|
||
|
$actions['abandon'] = true;
|
||
|
break;
|
||
|
case DifferentialConstants::NEEDS_REVISION:
|
||
|
$actions['abandon'] = true;
|
||
|
$actions['request_review'] = true;
|
||
|
break;
|
||
|
case DifferentialConstants::ACCEPTED:
|
||
|
$actions['abandon'] = true;
|
||
|
$actions['request_review'] = true;
|
||
|
break;
|
||
|
case DifferentialConstants::COMMITTED:
|
||
|
break;
|
||
|
case DifferentialConstants::ABANDONED:
|
||
|
$actions['reclaim'] = true;
|
||
|
break;
|
||
|
default:
|
||
|
throw new Exception('Unknown DifferentialRevision status.');
|
||
|
}
|
||
|
} else {
|
||
|
switch ($revision->getStatus()) {
|
||
|
case DifferentialConstants::NEEDS_REVIEW:
|
||
|
$actions['accept'] = true;
|
||
|
$actions['reject'] = true;
|
||
|
break;
|
||
|
case DifferentialConstants::NEEDS_REVISION:
|
||
|
$actions['accept'] = true;
|
||
|
break;
|
||
|
case DifferentialConstants::ACCEPTED:
|
||
|
$actions['reject'] = true;
|
||
|
break;
|
||
|
case DifferentialConstants::COMMITTED:
|
||
|
break;
|
||
|
case DifferentialConstants::ABANDONED:
|
||
|
break;
|
||
|
default:
|
||
|
throw new Exception('Unknown DifferentialRevision status.');
|
||
|
}
|
||
|
|
||
|
if (in_array($viewer->getUserID(), $revision->getReviewers())) {
|
||
|
$actions['resign'] = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Put add reviewers at the bottom since it's rare relative to other
|
||
|
// actions, notably accept and reject
|
||
|
$actions['add_reviewers'] = true;
|
||
|
|
||
|
static $action_names = array(
|
||
|
'none' => 'Comment',
|
||
|
'abandon' => 'Abandon Revision',
|
||
|
'request_review' => 'Request Review',
|
||
|
'reclaim' => 'Reclaim Revision',
|
||
|
'accept' => "Accept Revision \xE2\x9C\x94",
|
||
|
'reject' => "Request Changes \xE2\x9C\x98",
|
||
|
'resign' => "Resign as Reviewer",
|
||
|
'add_reviewers' => "Add Reviewers",
|
||
|
);
|
||
|
|
||
|
foreach ($actions as $key => $value) {
|
||
|
$actions[$key] = $action_names[$key];
|
||
|
}
|
||
|
|
||
|
return $actions;
|
||
|
}
|
||
|
|
||
|
protected function getRevisionStatusDisplay(DifferentialRevision $revision) {
|
||
|
$viewer_id = $this->getRequest()->getViewerContext()->getUserID();
|
||
|
$viewer_is_owner = ($viewer_id == $revision->getOwnerID());
|
||
|
$status = $revision->getStatus();
|
||
|
|
||
|
$more = null;
|
||
|
switch ($status) {
|
||
|
case DifferentialConstants::NEEDS_REVIEW:
|
||
|
$message = 'Pending Review';
|
||
|
break;
|
||
|
case DifferentialConstants::NEEDS_REVISION:
|
||
|
$message = 'Awaiting Revision';
|
||
|
if ($viewer_is_owner) {
|
||
|
$more = 'Make the requested changes and update the revision.';
|
||
|
}
|
||
|
break;
|
||
|
case DifferentialConstants::ACCEPTED:
|
||
|
$message = 'Ready for Commit';
|
||
|
if ($viewer_is_owner) {
|
||
|
$more =
|
||
|
<x:frag>
|
||
|
Run <tt>arc commit</tt> (svn) or <tt>arc amend</tt> (git) to
|
||
|
proceed.
|
||
|
</x:frag>;
|
||
|
}
|
||
|
break;
|
||
|
case DifferentialConstants::COMMITTED:
|
||
|
$message = 'Committed';
|
||
|
$ref = $revision->getRevisionRef();
|
||
|
$more = $ref
|
||
|
? (<a href={URI($ref->getDetailURL())}>
|
||
|
{$ref->getName()}
|
||
|
</a>)
|
||
|
: null;
|
||
|
|
||
|
$engineering_repository_id = RepositoryRef::getByCallsign('E')->getID();
|
||
|
if ($revision->getSVNRevision() &&
|
||
|
$revision->getRepositoryID() == $engineering_repository_id) {
|
||
|
Javelin::initBehavior(
|
||
|
'differential-revtracker-status',
|
||
|
array(
|
||
|
'uri' => '/differential/revtracker/'.$revision->getID().'/',
|
||
|
'statusId' => 'revtracker_status',
|
||
|
'mergeLinkId' => 'ask_for_merge_link',
|
||
|
));
|
||
|
}
|
||
|
break;
|
||
|
case DifferentialConstants::ABANDONED:
|
||
|
$message = 'Abandoned';
|
||
|
break;
|
||
|
default:
|
||
|
throw new Exception("Unknown revision status.");
|
||
|
}
|
||
|
|
||
|
if ($more) {
|
||
|
$message =
|
||
|
<x:frag>
|
||
|
<strong id="revtracker_status">{$message}</strong>
|
||
|
· {$more}
|
||
|
</x:frag>;
|
||
|
} else {
|
||
|
$message = <strong id="revtracker_status">{$message}</strong>;
|
||
|
}
|
||
|
|
||
|
return $message;
|
||
|
}
|
||
|
|
||
|
protected function renderFeedbackList(array $xhp, array $obj, $viewer_id) {
|
||
|
|
||
|
// Use magical heuristics to try to hide older comments.
|
||
|
|
||
|
$obj = array_reverse($obj);
|
||
|
$obj = array_values($obj);
|
||
|
$xhp = array_reverse($xhp);
|
||
|
$xhp = array_values($xhp);
|
||
|
|
||
|
$last_comment = null;
|
||
|
foreach ($obj as $position => $feedback) {
|
||
|
if ($feedback->getUserID() == $viewer_id) {
|
||
|
if ($last_comment === null) {
|
||
|
$last_comment = $position;
|
||
|
} else if ($last_comment == $position - 1) {
|
||
|
// If you made consecuitive comments, show them all. This is a spaz
|
||
|
// rule for epriestley comments.
|
||
|
$last_comment = $position;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$header = array();
|
||
|
|
||
|
$hide = array();
|
||
|
if ($last_comment !== null) {
|
||
|
foreach ($obj as $position => $feedback) {
|
||
|
$action = $feedback->getAction();
|
||
|
if ($action == 'testplan' || $action == 'summarize') {
|
||
|
// Always show summary and test plan.
|
||
|
$header[] = $xhp[$position];
|
||
|
unset($xhp[$position]);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ($position <= $last_comment) {
|
||
|
// Always show comments after your last comment.
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ($position < 3) {
|
||
|
// Always show the most recent 3 comments.
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Hide everything else.
|
||
|
$hide[] = $position;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (count($hide) <= 3) {
|
||
|
// Don't hide if there's not much to hide.
|
||
|
$hide = array();
|
||
|
}
|
||
|
|
||
|
$header = array_reverse($header);
|
||
|
|
||
|
$hidden = array_select_keys($xhp, $hide);
|
||
|
$visible = array_diff_key($xhp, $hidden);
|
||
|
|
||
|
$visible = array_reverse($visible);
|
||
|
$hidden = array_reverse($hidden);
|
||
|
|
||
|
if ($hidden) {
|
||
|
Javelin::initBehavior(
|
||
|
'differential-show-all-feedback',
|
||
|
array(
|
||
|
'markup' => id(<x:frag>{$hidden}</x:frag>)->toString(),
|
||
|
));
|
||
|
$hidden =
|
||
|
<div sigil="all-feedback-container">
|
||
|
<div class="older-replies-are-hidden">
|
||
|
{number_format(count($hidden))} older replies are hidden.
|
||
|
<a href="#" sigil="show-all-feedback"
|
||
|
mustcapture="true">Show all feedback.</a>
|
||
|
</div>
|
||
|
</div>;
|
||
|
} else {
|
||
|
$hidden = null;
|
||
|
}
|
||
|
|
||
|
return
|
||
|
<x:frag>
|
||
|
{$header}
|
||
|
{$hidden}
|
||
|
{$visible}
|
||
|
</x:frag>;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
protected function getDetailFields(
|
||
|
DifferentialRevision $revision,
|
||
|
Diff $diff,
|
||
|
array $handles) {
|
||
|
|
||
|
$fields = array();
|
||
|
$fields['Revision Status'] = $this->getRevisionStatusDisplay($revision);
|
||
|
|
||
|
$author = $revision->getOwnerID();
|
||
|
$fields['Author'] = <tools:handle handle={$handles[$author]}
|
||
|
link={true} />;
|
||
|
|
||
|
$sandcastle = $this->getSandcastleURI($diff);
|
||
|
if ($sandcastle) {
|
||
|
$fields['Sandcastle'] = <a href={$sandcastle}>{$sandcastle}</a>;
|
||
|
}
|
||
|
|
||
|
$path = $diff->getSourcePath();
|
||
|
if ($path) {
|
||
|
$host = $diff->getSourceMachine();
|
||
|
$branch = $diff->getGitBranch() ? ' (' . $diff->getGitBranch() . ')' : '';
|
||
|
|
||
|
if ($host) {
|
||
|
$user = $handles[$this->getRequest()->getViewerContext()->getUserID()]
|
||
|
->getName();
|
||
|
$fields['Path'] =
|
||
|
<x:frag>
|
||
|
<a href={"ssh://{$user}@{$host}"}>{$host}</a>:{$path}{$branch}
|
||
|
</x:frag>;
|
||
|
} else {
|
||
|
$fields['Path'] = $path;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$reviewer_links = array();
|
||
|
foreach ($revision->getReviewers() as $reviewer) {
|
||
|
$reviewer_links[] = <tools:handle handle={$handles[$reviewer]}
|
||
|
link={true} />;
|
||
|
}
|
||
|
if ($reviewer_links) {
|
||
|
$fields['Reviewers'] = array_implode(', ', $reviewer_links);
|
||
|
} else {
|
||
|
$fields['Reviewers'] = <em>None</em>;
|
||
|
}
|
||
|
|
||
|
$ccs = $revision->getCCFBIDs();
|
||
|
if ($ccs) {
|
||
|
$links = array();
|
||
|
foreach ($ccs as $cc) {
|
||
|
$links[] = <tools:handle handle={$handles[$cc]}
|
||
|
link={true} />;
|
||
|
}
|
||
|
$fields['CCs'] = array_implode(', ', $links);
|
||
|
}
|
||
|
|
||
|
$blame_rev = $revision->getSvnBlameRevision();
|
||
|
if ($blame_rev) {
|
||
|
if ($revision->getRepositoryRef() && is_numeric($blame_rev)) {
|
||
|
$ref = new RevisionRef($revision->getRepositoryRef(), $blame_rev);
|
||
|
$fields['Blame Revision'] =
|
||
|
<a href={URI($ref->getDetailURL())}>
|
||
|
{$ref->getName()}
|
||
|
</a>;
|
||
|
} else {
|
||
|
$fields['Blame Revision'] = $blame_rev;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$tasks = $revision->getTaskHandles();
|
||
|
|
||
|
if ($tasks) {
|
||
|
$links = array();
|
||
|
foreach ($tasks as $task) {
|
||
|
$links[] = <tools:handle handle={$task} link={true} />;
|
||
|
}
|
||
|
$fields['Tasks'] = array_implode(<br />, $links);
|
||
|
}
|
||
|
|
||
|
$bugzilla_id = $revision->getBugzillaID();
|
||
|
if ($bugzilla_id) {
|
||
|
$href = 'http://bugs.developers.facebook.com/show_bug.cgi?id='.
|
||
|
$bugzilla_id;
|
||
|
$fields['Bugzilla'] = <a href={$href}>{'#'.$bugzilla_id}</a>;
|
||
|
}
|
||
|
|
||
|
$fields['Apply Patch'] = <tt>arc patch --revision {$revision->getID()}</tt>;
|
||
|
|
||
|
if ($diff->getParentRevisionID()) {
|
||
|
$parent = id(new DifferentialRevision())->load(
|
||
|
$diff->getParentRevisionID());
|
||
|
if ($parent) {
|
||
|
$fields['Depends On'] =
|
||
|
<a href={$parent->getURI()}>
|
||
|
D{$parent->getID()}: {$parent->getName()}
|
||
|
</a>;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$star = <span class="star">{"\xE2\x98\x85"}</span>;
|
||
|
|
||
|
Javelin::initBehavior('differential-star-more');
|
||
|
|
||
|
switch ($diff->getLinted()) {
|
||
|
case Diff::LINT_FAIL:
|
||
|
$more = $this->renderDiffPropertyMoreLink($diff, 'lint');
|
||
|
$fields['Lint'] =
|
||
|
<x:frag>
|
||
|
<span class="star-warn">{$star} Lint Failures</span>
|
||
|
{$more}
|
||
|
</x:frag>;
|
||
|
break;
|
||
|
case Diff::LINT_WARNINGS:
|
||
|
$more = $this->renderDiffPropertyMoreLink($diff, 'lint');
|
||
|
$fields['Lint'] =
|
||
|
<x:frag>
|
||
|
<span class="star-warn">{$star} Lint Warnings</span>
|
||
|
{$more}
|
||
|
</x:frag>;
|
||
|
break;
|
||
|
case Diff::LINT_OKAY:
|
||
|
$fields['Lint'] =
|
||
|
<span class="star-okay">{$star} Lint Free</span>;
|
||
|
break;
|
||
|
default:
|
||
|
case Diff::LINT_NO:
|
||
|
$fields['Lint'] =
|
||
|
<span class="star-none">{$star} Not Linted</span>;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$unit_details = false;
|
||
|
switch ($diff->getUnitTested()) {
|
||
|
case Diff::UNIT_FAIL:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-warn">{$star} Unit Test Failures</span>;
|
||
|
$unit_details = true;
|
||
|
break;
|
||
|
case Diff::UNIT_WARN:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-warn">{$star} Unit Test Warnings</span>;
|
||
|
$unit_details = true;
|
||
|
break;
|
||
|
case Diff::UNIT_OKAY:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-okay">{$star} Unit Tests Passed</span>;
|
||
|
$unit_details = true;
|
||
|
break;
|
||
|
case Diff::UNIT_NO_TESTS:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-none">{$star} No Test Coverage</span>;
|
||
|
break;
|
||
|
case Diff::UNIT_NO:
|
||
|
default:
|
||
|
$fields['Unit Tests'] =
|
||
|
<span class="star-none">{$star} Not Unit Tested</span>;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ($unit_details) {
|
||
|
$fields['Unit Tests'] =
|
||
|
<x:frag>
|
||
|
{$fields['Unit Tests']}
|
||
|
{$this->renderDiffPropertyMoreLink($diff, 'unit')}
|
||
|
</x:frag>;
|
||
|
}
|
||
|
|
||
|
$platform_impact = $revision->getPlatformImpact();
|
||
|
if ($platform_impact) {
|
||
|
$fields['Platform Impact'] =
|
||
|
<text linebreaks="true">{$platform_impact}</text>;
|
||
|
}
|
||
|
|
||
|
return $fields;
|
||
|
}
|
||
|
|
||
|
|
||
|
*/
|