diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php
index 1dffc8ff1b..5593053e22 100644
--- a/src/applications/differential/controller/DifferentialRevisionViewController.php
+++ b/src/applications/differential/controller/DifferentialRevisionViewController.php
@@ -1,979 +1,978 @@
allowsAnonymousAccess();
}
public function willProcessRequest(array $data) {
$this->revisionID = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$viewer_is_anonymous = !$user->isLoggedIn();
$revision = id(new DifferentialRevision())->load($this->revisionID);
if (!$revision) {
return new Aphront404Response();
}
$revision->loadRelationships();
$diffs = $revision->loadDiffs();
if (!$diffs) {
throw new Exception(
"This revision has no diffs. Something has gone quite wrong.");
}
$diff_vs = $request->getInt('vs');
$target_id = $request->getInt('id');
$target = idx($diffs, $target_id, end($diffs));
$target_manual = $target;
if (!$target_id) {
foreach ($diffs as $diff) {
if ($diff->getCreationMethod() != 'commit') {
$target_manual = $diff;
}
}
}
if (empty($diffs[$diff_vs])) {
$diff_vs = null;
}
$arc_project = $target->loadArcanistProject();
$repository = ($arc_project ? $arc_project->loadRepository() : null);
list($changesets, $vs_map, $vs_changesets, $rendering_references) =
$this->loadChangesetsAndVsMap(
$target,
idx($diffs, $diff_vs),
$repository);
if ($request->getExists('download')) {
return $this->buildRawDiffResponse(
$changesets,
$vs_changesets,
$vs_map,
$repository);
}
$props = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID = %d',
$target_manual->getID());
$props = mpull($props, 'getData', 'getName');
$aux_fields = $this->loadAuxiliaryFields($revision);
$comments = $revision->loadComments();
$comments = array_merge(
$this->getImplicitComments($revision, reset($diffs)),
$comments);
$all_changesets = $changesets;
$inlines = $this->loadInlineComments($comments, $all_changesets);
$object_phids = array_merge(
$revision->getReviewers(),
$revision->getCCPHIDs(),
$revision->loadCommitPHIDs(),
array(
$revision->getAuthorPHID(),
$user->getPHID(),
),
mpull($comments, 'getAuthorPHID'));
foreach ($comments as $comment) {
$metadata = $comment->getMetadata();
$added_reviewers = idx(
$metadata,
DifferentialComment::METADATA_ADDED_REVIEWERS);
if ($added_reviewers) {
foreach ($added_reviewers as $phid) {
$object_phids[] = $phid;
}
}
$added_ccs = idx(
$metadata,
DifferentialComment::METADATA_ADDED_CCS);
if ($added_ccs) {
foreach ($added_ccs as $phid) {
$object_phids[] = $phid;
}
}
}
foreach ($revision->getAttached() as $type => $phids) {
foreach ($phids as $phid => $info) {
$object_phids[] = $phid;
}
}
$aux_phids = array();
foreach ($aux_fields as $key => $aux_field) {
$aux_field->setDiff($target);
$aux_field->setManualDiff($target_manual);
$aux_field->setDiffProperties($props);
$aux_phids[$key] = $aux_field->getRequiredHandlePHIDsForRevisionView();
}
$object_phids = array_merge($object_phids, array_mergev($aux_phids));
$object_phids = array_unique($object_phids);
$handles = $this->loadViewerHandles($object_phids);
foreach ($aux_fields as $key => $aux_field) {
// Make sure each field only has access to handles it specifically
// requested, not all handles. Otherwise you can get a field which works
// only in the presence of other fields.
$aux_field->setHandles(array_select_keys($handles, $aux_phids[$key]));
}
$reviewer_warning = null;
if ($revision->getStatus() ==
ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) {
$has_live_reviewer = false;
foreach ($revision->getReviewers() as $reviewer) {
if (!$handles[$reviewer]->isDisabled()) {
$has_live_reviewer = true;
break;
}
}
if (!$has_live_reviewer) {
$reviewer_warning = new AphrontErrorView();
$reviewer_warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$reviewer_warning->setTitle(pht('No Active Reviewers'));
if ($revision->getReviewers()) {
$reviewer_warning->appendChild(
phutil_render_tag(
'p',
array(),
pht('All specified reviewers are disabled and this revision '.
'needs review. You may want to add some new reviewers.')
));
} else {
$reviewer_warning->appendChild(
phutil_render_tag(
'p',
array(),
pht('This revision has no specified reviewers and needs '.
'review. You may want to add some reviewers.')
));
}
}
}
$request_uri = $request->getRequestURI();
$limit = 100;
$large = $request->getStr('large');
if (count($changesets) > $limit && !$large) {
$count = count($changesets);
$warning = new AphrontErrorView();
$warning->setTitle('Very Large Diff');
$warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$warning->appendChild(
pht(
- 'This diff is very large and affects %2$s files. Load each file '.
+ 'This diff is very large and affects %s files. Load each file '.
'individually.',
- $count,
- PhutilTranslator::getInstance()->formatNumber($count)).
+ new PhutilNumber($count)).
" ".
phutil_render_tag(
'a',
array(
'href' => $request_uri
->alter('large', 'true')
->setFragment('toc'),
),
pht('Show All Files Inline')).
"");
$warning = $warning->render();
$my_inlines = id(new DifferentialInlineComment())->loadAllWhere(
'revisionID = %d AND commentID IS NULL AND authorPHID = %s AND '.
'changesetID IN (%Ld)',
$this->revisionID,
$user->getPHID(),
mpull($changesets, 'getID'));
$visible_changesets = array();
foreach ($inlines + $my_inlines as $inline) {
$changeset_id = $inline->getChangesetID();
if (isset($changesets[$changeset_id])) {
$visible_changesets[$changeset_id] = $changesets[$changeset_id];
}
}
if (!empty($props['arc:lint'])) {
$changeset_paths = mpull($changesets, null, 'getFilename');
foreach ($props['arc:lint'] as $lint) {
$changeset = idx($changeset_paths, $lint['path']);
if ($changeset) {
$visible_changesets[$changeset->getID()] = $changeset;
}
}
}
} else {
$warning = null;
$visible_changesets = $changesets;
}
$revision_detail = new DifferentialRevisionDetailView();
$revision_detail->setRevision($revision);
$revision_detail->setDiff(reset($diffs));
$revision_detail->setAuxiliaryFields($aux_fields);
$actions = $this->getRevisionActions($revision);
$custom_renderer_class = PhabricatorEnv::getEnvConfig(
'differential.revision-custom-detail-renderer');
if ($custom_renderer_class) {
// TODO: build a better version of the action links and deprecate the
// whole DifferentialRevisionDetailRenderer class.
$custom_renderer = newv($custom_renderer_class, array());
$custom_renderer->setDiff($target);
if ($diff_vs) {
$custom_renderer->setVSDiff($diffs[$diff_vs]);
}
$actions = array_merge(
$actions,
$custom_renderer->generateActionLinks($revision, $target_manual));
}
$whitespace = $request->getStr(
'whitespace',
DifferentialChangesetParser::WHITESPACE_IGNORE_ALL);
if ($arc_project) {
list($symbol_indexes, $project_phids) = $this->buildSymbolIndexes(
$arc_project,
$visible_changesets);
} else {
$symbol_indexes = array();
$project_phids = null;
}
$revision_detail->setActions($actions);
$revision_detail->setUser($user);
$comment_view = new DifferentialRevisionCommentListView();
$comment_view->setComments($comments);
$comment_view->setHandles($handles);
$comment_view->setInlineComments($inlines);
$comment_view->setChangesets($all_changesets);
$comment_view->setUser($user);
$comment_view->setTargetDiff($target);
$comment_view->setVersusDiffID($diff_vs);
if ($arc_project) {
Javelin::initBehavior(
'repository-crossreference',
array(
'section' => $comment_view->getID(),
'projects' => $project_phids,
));
}
$changeset_view = new DifferentialChangesetListView();
$changeset_view->setChangesets($changesets);
$changeset_view->setVisibleChangesets($visible_changesets);
if (!$viewer_is_anonymous) {
$changeset_view->setInlineCommentControllerURI(
'/differential/comment/inline/edit/'.$revision->getID().'/');
}
$changeset_view->setStandaloneURI('/differential/changeset/');
$changeset_view->setRawFileURIs(
'/differential/changeset/?view=old',
'/differential/changeset/?view=new');
$changeset_view->setUser($user);
$changeset_view->setDiff($target);
$changeset_view->setRenderingReferences($rendering_references);
$changeset_view->setVsMap($vs_map);
$changeset_view->setWhitespace($whitespace);
if ($repository) {
$changeset_view->setRepository($repository);
}
$changeset_view->setSymbolIndexes($symbol_indexes);
$changeset_view->setTitle('Diff '.$target->getID());
$diff_history = new DifferentialRevisionUpdateHistoryView();
$diff_history->setDiffs($diffs);
$diff_history->setSelectedVersusDiffID($diff_vs);
$diff_history->setSelectedDiffID($target->getID());
$diff_history->setSelectedWhitespace($whitespace);
$diff_history->setUser($user);
$local_view = new DifferentialLocalCommitsView();
$local_view->setUser($user);
$local_view->setLocalCommits(idx($props, 'local:commits'));
if ($repository) {
$other_revisions = $this->loadOtherRevisions(
$changesets,
$target,
$repository);
} else {
$other_revisions = array();
}
$other_view = null;
if ($other_revisions) {
$other_view = $this->renderOtherRevisions($other_revisions);
}
$toc_view = new DifferentialDiffTableOfContentsView();
$toc_view->setChangesets($changesets);
$toc_view->setVisibleChangesets($visible_changesets);
$toc_view->setRenderingReferences($rendering_references);
$toc_view->setUnitTestData(idx($props, 'arc:unit', array()));
if ($repository) {
$toc_view->setRepository($repository);
}
$toc_view->setDiff($target);
$toc_view->setUser($user);
$toc_view->setRevisionID($revision->getID());
$toc_view->setWhitespace($whitespace);
$comment_form = null;
if (!$viewer_is_anonymous) {
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
'differential-comment-'.$revision->getID());
$reviewers = array();
$ccs = array();
if ($draft) {
$reviewers = idx($draft->getMetadata(), 'reviewers', array());
$ccs = idx($draft->getMetadata(), 'ccs', array());
if ($reviewers || $ccs) {
$handles = $this->loadViewerHandles(array_merge($reviewers, $ccs));
$reviewers = array_select_keys($handles, $reviewers);
$ccs = array_select_keys($handles, $ccs);
}
}
$comment_form = new DifferentialAddCommentView();
$comment_form->setRevision($revision);
$comment_form->setAuxFields($aux_fields);
$comment_form->setActions($this->getRevisionCommentActions($revision));
$comment_form->setActionURI('/differential/comment/save/');
$comment_form->setUser($user);
$comment_form->setDraft($draft);
$comment_form->setReviewers(mpull($reviewers, 'getFullName', 'getPHID'));
$comment_form->setCCs(mpull($ccs, 'getFullName', 'getPHID'));
}
$pane_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'differential-keyboard-navigation',
array(
'haunt' => $pane_id,
));
Javelin::initBehavior('differential-user-select');
$page_pane = id(new DifferentialPrimaryPaneView())
->setID($pane_id)
->appendChild(
$comment_view->render().
$diff_history->render().
$warning.
$local_view->render().
$toc_view->render().
$other_view.
$changeset_view->render());
if ($comment_form) {
$page_pane->appendChild($comment_form->render());
}
PhabricatorFeedStoryNotification::updateObjectNotificationViews(
$user, $revision->getPHID());
$object_id = 'D'.$revision->getID();
$top_anchor = id(new PhabricatorAnchorView())
->setAnchorName('top')
->setNavigationMarker(true);
$nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
->setAnchorName('top')
->setTitle('D'.$revision->getID())
->setBaseURI(new PhutilURI('/D'.$revision->getID()))
->build($changesets);
$nav->appendChild(
array(
$reviewer_warning,
$top_anchor,
$revision_detail,
$page_pane,
));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($object_id)
->setHref('/'.$object_id));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => $object_id.' '.$revision->getTitle(),
));
}
private function getImplicitComments(
DifferentialRevision $revision,
DifferentialDiff $diff) {
$author_phid = nonempty(
$diff->getAuthorPHID(),
$revision->getAuthorPHID());
$template = new DifferentialComment();
$template->setAuthorPHID($author_phid);
$template->setRevisionID($revision->getID());
$template->setDateCreated($revision->getDateCreated());
$comments = array();
if (strlen($revision->getSummary())) {
$summary_comment = clone $template;
$summary_comment->setContent($revision->getSummary());
$summary_comment->setAction(DifferentialAction::ACTION_SUMMARIZE);
$comments[] = $summary_comment;
}
if (strlen($revision->getTestPlan())) {
$testplan_comment = clone $template;
$testplan_comment->setContent($revision->getTestPlan());
$testplan_comment->setAction(DifferentialAction::ACTION_TESTPLAN);
$comments[] = $testplan_comment;
}
return $comments;
}
private function getRevisionActions(DifferentialRevision $revision) {
$user = $this->getRequest()->getUser();
$viewer_phid = $user->getPHID();
$viewer_is_owner = ($revision->getAuthorPHID() == $viewer_phid);
$viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
$viewer_is_cc = in_array($viewer_phid, $revision->getCCPHIDs());
$viewer_is_anonymous = !$this->getRequest()->getUser()->isLoggedIn();
$status = $revision->getStatus();
$revision_id = $revision->getID();
$revision_phid = $revision->getPHID();
$links = array();
if ($viewer_is_owner) {
$links[] = array(
'icon' => 'edit',
'href' => "/differential/revision/edit/{$revision_id}/",
'name' => pht('Edit Revision'),
);
}
if (!$viewer_is_anonymous) {
if (!$viewer_is_owner && !$viewer_is_reviewer) {
$action = $viewer_is_cc ? 'rem' : 'add';
$links[] = array(
'icon' => $viewer_is_cc ? 'subscribe-delete' : 'subscribe-add',
'href' => "/differential/subscribe/{$action}/{$revision_id}/",
'name' => $viewer_is_cc ? pht('Unsubscribe') : pht('Subscribe'),
'instant' => true,
'sigil' => 'workflow',
);
} else {
$links[] = array(
'icon' => 'subscribe-auto',
'name' => pht('Automatically Subscribed'),
'disabled' => true,
);
}
require_celerity_resource('phabricator-object-selector-css');
require_celerity_resource('javelin-behavior-phabricator-object-selector');
$links[] = array(
'icon' => 'link',
'name' => pht('Edit Dependencies'),
'href' => "/search/attach/{$revision_phid}/DREV/dependencies/",
'sigil' => 'workflow',
);
if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
$links[] = array(
'icon' => 'attach',
'name' => pht('Edit Maniphest Tasks'),
'href' => "/search/attach/{$revision_phid}/TASK/",
'sigil' => 'workflow',
);
}
if ($user->getIsAdmin()) {
$links[] = array(
'icon' => 'file',
'name' => pht('MetaMTA Transcripts'),
'href' => "/mail/?phid={$revision_phid}",
);
}
$links[] = array(
'icon' => 'file',
'name' => pht('Herald Transcripts'),
'href' => "/herald/transcript/?phid={$revision_phid}",
);
}
$request_uri = $this->getRequest()->getRequestURI();
$links[] = array(
'icon' => 'download',
'name' => pht('Download Raw Diff'),
'href' => $request_uri->alter('download', 'true')
);
return $links;
}
private function getRevisionCommentActions(DifferentialRevision $revision) {
$actions = array(
DifferentialAction::ACTION_COMMENT => true,
);
$viewer = $this->getRequest()->getUser();
$viewer_phid = $viewer->getPHID();
$viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID());
$viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
$viewer_did_accept = ($viewer_phid === $revision->loadReviewedBy());
$status = $revision->getStatus();
$allow_self_accept = PhabricatorEnv::getEnvConfig(
'differential.allow-self-accept');
$always_allow_close = PhabricatorEnv::getEnvConfig(
'differential.always-allow-close');
$allow_reopen = PhabricatorEnv::getEnvConfig(
'differential.allow-reopen');
if ($viewer_is_owner) {
switch ($status) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept;
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept;
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_REQUEST] = true;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_REQUEST] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
$actions[DifferentialAction::ACTION_CLOSE] = true;
break;
case ArcanistDifferentialRevisionStatus::CLOSED:
break;
case ArcanistDifferentialRevisionStatus::ABANDONED:
$actions[DifferentialAction::ACTION_RECLAIM] = true;
break;
}
} else {
switch ($status) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$actions[DifferentialAction::ACTION_ACCEPT] = true;
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$actions[DifferentialAction::ACTION_ACCEPT] = true;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] =
$viewer_is_reviewer && !$viewer_did_accept;
break;
case ArcanistDifferentialRevisionStatus::CLOSED:
case ArcanistDifferentialRevisionStatus::ABANDONED:
break;
}
if ($status != ArcanistDifferentialRevisionStatus::CLOSED) {
$actions[DifferentialAction::ACTION_CLAIM] = true;
$actions[DifferentialAction::ACTION_CLOSE] = $always_allow_close;
}
}
$actions[DifferentialAction::ACTION_ADDREVIEWERS] = true;
$actions[DifferentialAction::ACTION_ADDCCS] = true;
$actions[DifferentialAction::ACTION_REOPEN] = $allow_reopen &&
($status == ArcanistDifferentialRevisionStatus::CLOSED);
$actions = array_keys(array_filter($actions));
$actions_dict = array();
foreach ($actions as $action) {
$actions_dict[$action] = DifferentialAction::getActionVerb($action);
}
return $actions_dict;
}
private function loadInlineComments(array $comments, array &$changesets) {
assert_instances_of($comments, 'DifferentialComment');
assert_instances_of($changesets, 'DifferentialChangeset');
$inline_comments = array();
$comment_ids = array_filter(mpull($comments, 'getID'));
if (!$comment_ids) {
return $inline_comments;
}
$inline_comments = id(new DifferentialInlineComment())
->loadAllWhere(
'commentID in (%Ld)',
$comment_ids);
$load_changesets = array();
foreach ($inline_comments as $inline) {
$changeset_id = $inline->getChangesetID();
if (isset($changesets[$changeset_id])) {
continue;
}
$load_changesets[$changeset_id] = true;
}
$more_changesets = array();
if ($load_changesets) {
$changeset_ids = array_keys($load_changesets);
$more_changesets += id(new DifferentialChangeset())
->loadAllWhere(
'id IN (%Ld)',
$changeset_ids);
}
if ($more_changesets) {
$changesets += $more_changesets;
$changesets = msort($changesets, 'getSortKey');
}
return $inline_comments;
}
private function loadChangesetsAndVsMap(
DifferentialDiff $target,
DifferentialDiff $diff_vs = null,
PhabricatorRepository $repository = null) {
$load_ids = array();
if ($diff_vs) {
$load_ids[] = $diff_vs->getID();
}
$load_ids[] = $target->getID();
$raw_changesets = id(new DifferentialChangeset())
->loadAllWhere(
'diffID IN (%Ld)',
$load_ids);
$changeset_groups = mgroup($raw_changesets, 'getDiffID');
$changesets = idx($changeset_groups, $target->getID(), array());
$changesets = mpull($changesets, null, 'getID');
$refs = array();
$vs_map = array();
$vs_changesets = array();
if ($diff_vs) {
$vs_id = $diff_vs->getID();
$vs_changesets_path_map = array();
foreach (idx($changeset_groups, $vs_id, array()) as $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $diff_vs);
$vs_changesets_path_map[$path] = $changeset;
$vs_changesets[$changeset->getID()] = $changeset;
}
foreach ($changesets as $key => $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $target);
if (isset($vs_changesets_path_map[$path])) {
$vs_map[$changeset->getID()] =
$vs_changesets_path_map[$path]->getID();
$refs[$changeset->getID()] =
$changeset->getID().'/'.$vs_changesets_path_map[$path]->getID();
unset($vs_changesets_path_map[$path]);
} else {
$refs[$changeset->getID()] = $changeset->getID();
}
}
foreach ($vs_changesets_path_map as $path => $changeset) {
$changesets[$changeset->getID()] = $changeset;
$vs_map[$changeset->getID()] = -1;
$refs[$changeset->getID()] = $changeset->getID().'/-1';
}
} else {
foreach ($changesets as $changeset) {
$refs[$changeset->getID()] = $changeset->getID();
}
}
$changesets = msort($changesets, 'getSortKey');
return array($changesets, $vs_map, $vs_changesets, $refs);
}
private function loadAuxiliaryFields(DifferentialRevision $revision) {
$aux_fields = DifferentialFieldSelector::newSelector()
->getFieldSpecifications();
foreach ($aux_fields as $key => $aux_field) {
if (!$aux_field->shouldAppearOnRevisionView()) {
unset($aux_fields[$key]);
} else {
$aux_field->setUser($this->getRequest()->getUser());
}
}
$aux_fields = DifferentialAuxiliaryField::loadFromStorage(
$revision,
$aux_fields);
return $aux_fields;
}
private function buildSymbolIndexes(
PhabricatorRepositoryArcanistProject $arc_project,
array $visible_changesets) {
assert_instances_of($visible_changesets, 'DifferentialChangeset');
$engine = PhabricatorSyntaxHighlighter::newEngine();
$langs = $arc_project->getSymbolIndexLanguages();
if (!$langs) {
return array(array(), array());
}
$symbol_indexes = array();
$project_phids = array_merge(
array($arc_project->getPHID()),
nonempty($arc_project->getSymbolIndexProjects(), array()));
$indexed_langs = array_fill_keys($langs, true);
foreach ($visible_changesets as $key => $changeset) {
$lang = $engine->getLanguageFromFilename($changeset->getFilename());
if (isset($indexed_langs[$lang])) {
$symbol_indexes[$key] = array(
'lang' => $lang,
'projects' => $project_phids,
);
}
}
return array($symbol_indexes, $project_phids);
}
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();
}
$query = id(new DifferentialRevisionQuery())
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
->setOrder(DifferentialRevisionQuery::ORDER_PATH_MODIFIED)
->setLimit(10)
->needRelationships(true);
foreach ($path_map as $path => $path_id) {
$query->withPath($repository->getID(), $path_id);
}
$results = $query->execute();
// Strip out *this* revision.
foreach ($results as $key => $result) {
if ($result->getID() == $this->revisionID) {
unset($results[$key]);
}
}
return $results;
}
private function renderOtherRevisions(array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$view = id(new DifferentialRevisionListView())
->setRevisions($revisions)
->setFields(DifferentialRevisionListView::getDefaultFields())
->setUser($this->getRequest()->getUser())
->loadAssets();
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
return
id(new PhabricatorHeaderView())
->setHeader(pht('Open Revisions Affecting These Files'))
->render().
'
'.
$view->render().
'
';
}
/**
* Straight copy of the loadFileByPhid method in
* @{class:DifferentialReviewRequestMail}.
*
* This is because of the code similarity between the buildPatch method in
* @{class:DifferentialReviewRequestMail} and @{method:buildRawDiffResponse}
* in this class. Both of these methods end up using call_user_func and this
* piece of code is the lucky function.
*
* @return mixed (@{class:PhabricatorFile} if found, null if not)
*/
public function loadFileByPHID($phid) {
$file = id(new PhabricatorFile())->loadOneWhere(
'phid = %s',
$phid);
if (!$file) {
return null;
}
return $file->loadFileData();
}
/**
* Note this code is somewhat similar to the buildPatch method in
* @{class:DifferentialReviewRequestMail}.
*
* @return @{class:AphrontRedirectResponse}
*/
private function buildRawDiffResponse(
array $changesets,
array $vs_changesets,
array $vs_map,
PhabricatorRepository $repository = null) {
assert_instances_of($changesets, 'DifferentialChangeset');
assert_instances_of($vs_changesets, 'DifferentialChangeset');
$engine = new PhabricatorDifferenceEngine();
$generated_changesets = array();
foreach ($changesets as $changeset) {
$changeset->attachHunks($changeset->loadHunks());
$right = $changeset->makeNewFile();
$choice = $changeset;
$vs = idx($vs_map, $changeset->getID());
if ($vs == -1) {
$left = $right;
$right = $changeset->makeOldFile();
} else if ($vs) {
$choice = $vs_changeset = $vs_changesets[$vs];
$vs_changeset->attachHunks($vs_changeset->loadHunks());
$left = $vs_changeset->makeNewFile();
} else {
$left = $changeset->makeOldFile();
}
$synthetic = $engine->generateChangesetFromFileContent(
$left,
$right);
if (!$synthetic->getAffectedLineCount()) {
$filetype = $choice->getFileType();
if ($filetype == DifferentialChangeType::FILE_TEXT ||
$filetype == DifferentialChangeType::FILE_SYMLINK) {
continue;
}
}
$choice->attachHunks($synthetic->getHunks());
$generated_changesets[] = $choice;
}
$diff = new DifferentialDiff();
$diff->attachChangesets($generated_changesets);
$diff_dict = $diff->getDiffDict();
$changes = array();
foreach ($diff_dict['changes'] as $changedict) {
$changes[] = ArcanistDiffChange::newFromDictionary($changedict);
}
$bundle = ArcanistBundle::newFromChanges($changes);
$bundle->setLoadFileDataCallback(array($this, 'loadFileByPHID'));
$vcs = $repository ? $repository->getVersionControlSystem() : null;
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$raw_diff = $bundle->toGitPatch();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
default:
$raw_diff = $bundle->toUnifiedDiff();
break;
}
$request_uri = $this->getRequest()->getRequestURI();
// this ends up being something like
// D123.diff
// or the verbose
// D123.vs123.id123.whitespaceignore-all.diff
// lame but nice to include these options
$file_name = ltrim($request_uri->getPath(), '/').'.';
foreach ($request_uri->getQueryParams() as $key => $value) {
if ($key == 'download') {
continue;
}
$file_name .= $key.$value.'.';
}
$file_name .= 'diff';
$file = PhabricatorFile::buildFromFileDataOrHash(
$raw_diff,
array(
'name' => $file_name,
));
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
}
}
diff --git a/src/applications/diffusion/controller/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/DiffusionBrowseFileController.php
index e3f5592426..ac1e0abb80 100644
--- a/src/applications/diffusion/controller/DiffusionBrowseFileController.php
+++ b/src/applications/diffusion/controller/DiffusionBrowseFileController.php
@@ -1,948 +1,947 @@
getRequest();
$drequest = $this->getDiffusionRequest();
$before = $request->getStr('before');
if ($before) {
return $this->buildBeforeResponse($before);
}
$path = $drequest->getPath();
$selected = $request->getStr('view');
$preferences = $request->getUser()->loadPreferences();
if (!$selected) {
$selected = $preferences->getPreference(
PhabricatorUserPreferences::PREFERENCE_DIFFUSION_VIEW,
'highlighted');
} else if ($request->isFormPost() && $selected != 'raw') {
$preferences->setPreference(
PhabricatorUserPreferences::PREFERENCE_DIFFUSION_VIEW,
$selected);
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI()->alter('view', $selected));
}
$needs_blame = ($selected == 'blame' || $selected == 'plainblame');
$file_query = DiffusionFileContentQuery::newFromDiffusionRequest(
$this->diffusionRequest);
$file_query->setViewer($request->getUser());
$file_query->setNeedsBlame($needs_blame);
$file_query->loadFileContent();
$data = $file_query->getRawData();
if ($selected === 'raw') {
return $this->buildRawResponse($path, $data);
}
$this->loadLintMessages();
// Build the content of the file.
$corpus = $this->buildCorpus(
$selected,
$file_query,
$needs_blame,
$drequest,
$path,
$data);
require_celerity_resource('diffusion-source-css');
if ($this->corpusType == 'text') {
$view_select_panel = $this->renderViewSelectPanel($selected);
} else {
$view_select_panel = null;
}
// Render the page.
$content = array();
$follow = $request->getStr('follow');
if ($follow) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$notice->setTitle('Unable to Continue');
switch ($follow) {
case 'first':
$notice->appendChild(
"Unable to continue tracing the history of this file because ".
"this commit is the first commit in the repository.");
break;
case 'created':
$notice->appendChild(
"Unable to continue tracing the history of this file because ".
"this commit created the file.");
break;
}
$content[] = $notice;
}
$renamed = $request->getStr('renamed');
if ($renamed) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle('File Renamed');
$notice->appendChild(
"File history passes through a rename from '".
phutil_escape_html($drequest->getPath())."' to '".
phutil_escape_html($renamed)."'.");
$content[] = $notice;
}
$content[] = $view_select_panel;
$content[] = $corpus;
$content[] = $this->buildOpenRevisions();
$nav = $this->buildSideNav('browse', true);
$nav->appendChild($content);
$crumbs = $this->buildCrumbs(
array(
'branch' => true,
'path' => true,
'view' => 'browse',
));
$nav->setCrumbs($crumbs);
$basename = basename($this->getDiffusionRequest()->getPath());
return $this->buildApplicationPage(
$nav,
array(
'title' => $basename,
));
}
private function loadLintMessages() {
$drequest = $this->getDiffusionRequest();
$branch = $drequest->loadBranch();
if (!$branch || !$branch->getLintCommit()) {
return;
}
$this->lintCommit = $branch->getLintCommit();
$conn = id(new PhabricatorRepository())->establishConnection('r');
$where = '';
if ($drequest->getLint()) {
$where = qsprintf(
$conn,
'AND code = %s',
$drequest->getLint());
}
$this->lintMessages = queryfx_all(
$conn,
'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s',
PhabricatorRepository::TABLE_LINTMESSAGE,
$branch->getID(),
$where,
'/'.$drequest->getPath());
}
private function buildCorpus($selected,
DiffusionFileContentQuery $file_query,
$needs_blame,
DiffusionRequest $drequest,
$path,
$data) {
if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) {
$file = $this->loadFileForData($path, $data);
$file_uri = $file->getBestURI();
if ($file->isViewableImage()) {
$this->corpusType = 'image';
return $this->buildImageCorpus($file_uri);
} else {
$this->corpusType = 'binary';
return $this->buildBinaryCorpus($file_uri, $data);
}
}
switch ($selected) {
case 'plain':
$style =
"margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
$corpus = phutil_render_tag(
'textarea',
array(
'style' => $style,
),
phutil_escape_html($file_query->getRawData()));
break;
case 'plainblame':
$style =
"margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
list($text_list, $rev_list, $blame_dict) =
$file_query->getBlameData();
$rows = array();
foreach ($text_list as $k => $line) {
$rev = $rev_list[$k];
if (isset($blame_dict[$rev]['handle'])) {
$author = $blame_dict[$rev]['handle']->getName();
} else {
$author = $blame_dict[$rev]['author'];
}
$rows[] =
sprintf("%-10s %-20s %s", substr($rev, 0, 7), $author, $line);
}
$corpus = phutil_render_tag(
'textarea',
array(
'style' => $style,
),
phutil_escape_html(implode("\n", $rows)));
break;
case 'highlighted':
case 'blame':
default:
require_celerity_resource('syntax-highlighting-css');
list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData();
$text_list = implode("\n", $text_list);
$text_list = PhabricatorSyntaxHighlighter::highlightWithFilename(
$path,
$text_list);
$text_list = explode("\n", $text_list);
$rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict,
$needs_blame, $drequest, $file_query, $selected);
$id = celerity_generate_unique_node_id();
$projects = $drequest->loadArcanistProjects();
$langs = array();
foreach ($projects as $project) {
$ls = $project->getSymbolIndexLanguages();
if (!$ls) {
continue;
}
$dep_projects = $project->getSymbolIndexProjects();
$dep_projects[] = $project->getPHID();
foreach ($ls as $lang) {
if (!isset($langs[$lang])) {
$langs[$lang] = array();
}
$langs[$lang] += $dep_projects + array($project);
}
}
$lang = last(explode('.', $drequest->getPath()));
$prefs = $this->getRequest()->getUser()->loadPreferences();
$pref_symbols = $prefs->getPreference(
PhabricatorUserPreferences::PREFERENCE_DIFFUSION_SYMBOLS);
if (isset($langs[$lang]) && $pref_symbols != 'disabled') {
Javelin::initBehavior(
'repository-crossreference',
array(
'container' => $id,
'lang' => $lang,
'projects' => $langs[$lang],
));
}
$corpus_table = javelin_render_tag(
'table',
array(
'class' => "diffusion-source remarkup-code PhabricatorMonospaced",
'sigil' => 'diffusion-source',
),
implode("\n", $rows));
$corpus = phutil_render_tag(
'div',
array(
'style' => 'padding: 0 2em;',
'id' => $id,
),
$corpus_table);
break;
}
return $corpus;
}
private function renderViewSelectPanel($selected) {
$toggle_blame = array(
'highlighted' => 'blame',
'blame' => 'highlighted',
'plain' => 'plainblame',
'plainblame' => 'plain',
'raw' => 'raw', // not a real case.
);
$toggle_highlight = array(
'highlighted' => 'plain',
'blame' => 'plainblame',
'plain' => 'highlighted',
'plainblame' => 'blame',
'raw' => 'raw', // not a real case.
);
$user = $this->getRequest()->getUser();
$base_uri = $this->getRequest()->getRequestURI();
$blame_on = ($selected == 'blame' || $selected == 'plainblame');
if ($blame_on) {
$blame_text = pht('Disable Blame');
} else {
$blame_text = pht('Enable Blame');
}
$blame_button = $this->createViewAction(
$blame_text,
$base_uri->alter('view', $toggle_blame[$selected]),
$user);
$highlight_on = ($selected == 'blame' || $selected == 'highlighted');
if ($highlight_on) {
$highlight_text = pht('Disable Highlighting');
} else {
$highlight_text = pht('Enable Highlighting');
}
$highlight_button = $this->createViewAction(
$highlight_text,
$base_uri->alter('view', $toggle_highlight[$selected]),
$user);
$href = null;
if ($this->getRequest()->getStr('lint') !== null) {
$lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages));
$href = $base_uri->alter('lint', null);
} else if ($this->lintCommit === null) {
$lint_text = pht('Lint not Available');
} else {
$lint_text = pht(
'Show %d Lint Message(s)',
count($this->lintMessages));
$href = $this->getDiffusionRequest()->generateURI(array(
'action' => 'browse',
'commit' => $this->lintCommit,
))->alter('lint', '');
}
$lint_button = $this->createViewAction(
$lint_text,
$href,
$user);
if (!$href) {
$lint_button->setDisabled(true);
}
$raw_button = $this->createViewAction(
pht('View Raw File'),
$base_uri->alter('view', 'raw'),
$user,
'file');
$edit_button = $this->createEditAction();
return id(new PhabricatorActionListView())
->setUser($user)
->addAction($blame_button)
->addAction($highlight_button)
->addAction($lint_button)
->addAction($raw_button)
->addAction($edit_button);
}
private function createViewAction(
$localized_text,
$href,
$user,
$icon = null) {
return id(new PhabricatorActionView())
->setName($localized_text)
->setIcon($icon)
->setUser($user)
->setRenderAsForm(true)
->setHref($href);
}
private function createEditAction() {
$request = $this->getRequest();
$user = $request->getUser();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$path = $drequest->getPath();
$line = nonempty((int)$drequest->getLine(), 1);
$callsign = $repository->getCallsign();
$editor_link = $user->loadEditorLink($path, $line, $callsign);
$action = id(new PhabricatorActionView())
->setName(pht('Open in Editor'))
->setIcon('edit');
$action->setHref($editor_link);
$action->setDisabled(!$editor_link);
return $action;
}
private function buildDisplayRows(
array $text_list,
array $rev_list,
array $blame_dict,
$needs_blame,
DiffusionRequest $drequest,
DiffusionFileContentQuery $file_query,
$selected) {
if ($blame_dict) {
$epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch');
$epoch_min = min($epoch_list);
$epoch_max = max($epoch_list);
$epoch_range = ($epoch_max - $epoch_min) + 1;
}
$line_arr = array();
$line_str = $drequest->getLine();
$ranges = explode(',', $line_str);
foreach ($ranges as $range) {
if (strpos($range, '-') !== false) {
list($min, $max) = explode('-', $range, 2);
$line_arr[] = array(
'min' => min($min, $max),
'max' => max($min, $max),
);
} else if (strlen($range)) {
$line_arr[] = array(
'min' => $range,
'max' => $range,
);
}
}
$display = array();
$line_number = 1;
$last_rev = null;
$color = null;
foreach ($text_list as $k => $line) {
$display_line = array(
'color' => null,
'epoch' => null,
'commit' => null,
'author' => null,
'target' => null,
'highlighted' => null,
'line' => $line_number,
'data' => $line,
);
if ($needs_blame) {
// If the line's rev is same as the line above, show empty content
// with same color; otherwise generate blame info. The newer a change
// is, the more saturated the color.
$rev = idx($rev_list, $k, $last_rev);
if ($last_rev == $rev) {
$display_line['color'] = $color;
} else {
$blame = $blame_dict[$rev];
if (!isset($blame['epoch'])) {
$color = '#ffd'; // Render as warning.
} else {
$color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range;
$color_value = 0xF6 * (1.0 - $color_ratio);
$color = sprintf(
'#%02x%02x%02x',
$color_value,
0xF6,
$color_value);
}
$display_line['epoch'] = idx($blame, 'epoch');
$display_line['color'] = $color;
$display_line['commit'] = $rev;
if (isset($blame['handle'])) {
$author_link = $blame['handle']->renderLink();
} else {
$author_link = phutil_render_tag(
'span',
array(
),
phutil_escape_html($blame['author']));
}
$display_line['author'] = $author_link;
$last_rev = $rev;
}
}
if ($line_arr) {
if ($line_number == $line_arr[0]['min']) {
$display_line['target'] = true;
}
foreach ($line_arr as $range) {
if ($line_number >= $range['min'] &&
$line_number <= $range['max']) {
$display_line['highlighted'] = true;
}
}
}
$display[] = $display_line;
++$line_number;
}
$commits = array_filter(ipull($display, 'commit'));
if ($commits) {
$commits = id(new PhabricatorAuditCommitQuery())
->withIdentifiers($drequest->getRepository()->getID(), $commits)
->needCommitData(true)
->execute();
$commits = mpull($commits, null, 'getCommitIdentifier');
}
$revision_ids = id(new DifferentialRevision())
->loadIDsByCommitPHIDs(mpull($commits, 'getPHID'));
$revisions = array();
if ($revision_ids) {
$revisions = id(new DifferentialRevision())->loadAllWhere(
'id IN (%Ld)',
$revision_ids);
}
$request = $this->getRequest();
$user = $request->getUser();
Javelin::initBehavior('phabricator-oncopy', array());
$engine = null;
$inlines = array();
if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) {
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
foreach ($this->lintMessages as $message) {
$inline = id(new PhabricatorAuditInlineComment())
->setID($message['id'])
->setSyntheticAuthor(
ArcanistLintSeverity::getStringForSeverity($message['severity']).
' '.$message['code'].' ('.$message['name'].')')
->setLineNumber($message['line'])
->setContent($message['description']);
$inlines[$message['line']][] = $inline;
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
}
$engine->process();
require_celerity_resource('differential-changeset-view-css');
}
$rows = $this->renderInlines(
idx($inlines, 0, array()),
$needs_blame,
$engine);
foreach ($display as $line) {
$line_href = $drequest->generateURI(
array(
'action' => 'browse',
'line' => $line['line'],
'stable' => true,
));
$blame = array();
if ($line['color']) {
$color = $line['color'];
$before_link = null;
$commit_link = null;
$revision_link = null;
if (idx($line, 'commit')) {
$commit = $line['commit'];
$summary = 'Unknown';
if (idx($commits, $commit)) {
$summary = $commits[$commit]->getCommitData()->getSummary();
}
$tooltip = phabricator_date(
$line['epoch'],
$user)." \xC2\xB7 ".$summary;
Javelin::initBehavior('phabricator-tooltips', array());
require_celerity_resource('aphront-tooltip-css');
$commit_link = javelin_render_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'commit',
'commit' => $line['commit'],
)),
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $tooltip,
'align' => 'E',
'size' => 600,
),
),
phutil_escape_html(phutil_utf8_shorten($line['commit'], 9, '')));
$revision_id = null;
if (idx($commits, $commit)) {
$revision_id = idx($revision_ids, $commits[$commit]->getPHID());
}
if ($revision_id) {
$revision = idx($revisions, $revision_id);
if (!$revision) {
$tooltip = '(Invalid revision)';
} else {
$tooltip =
phabricator_date($revision->getDateModified(), $user).
" \xC2\xB7 ".
$revision->getTitle();
}
$revision_link = javelin_render_tag(
'a',
array(
'href' => '/D'.$revision_id,
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $tooltip,
'align' => 'E',
'size' => 600,
),
),
'D'.$revision_id);
}
$uri = $line_href->alter('before', $commit);
$before_link = javelin_render_tag(
'a',
array(
'href' => $uri->setQueryParam('view', 'blame'),
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => 'Skip Past This Commit',
'align' => 'E',
'size' => 300,
),
),
"\xC2\xAB");
}
$blame[] = phutil_render_tag(
'th',
array(
'class' => 'diffusion-blame-link',
'style' => 'background: '.$color,
),
$before_link);
$blame[] = phutil_render_tag(
'th',
array(
'class' => 'diffusion-rev-link',
'style' => 'background: '.$color,
),
$commit_link);
$blame[] = phutil_render_tag(
'th',
array(
'class' => 'diffusion-rev-link',
'style' => 'background: '.$color,
),
$revision_link);
$blame[] = phutil_render_tag(
'th',
array(
'class' => 'diffusion-author-link',
'style' => 'background: '.$color,
),
idx($line, 'author'));
}
$line_link = phutil_render_tag(
'a',
array(
'href' => $line_href,
),
phutil_escape_html($line['line']));
$blame[] = javelin_render_tag(
'th',
array(
'class' => 'diffusion-line-link',
'sigil' => 'diffusion-line-link',
'style' => isset($color) ? 'background: '.$color : null,
),
$line_link);
Javelin::initBehavior('diffusion-line-linker');
$blame = implode('', $blame);
if ($line['target']) {
Javelin::initBehavior(
'diffusion-jump-to',
array(
'target' => 'scroll_target',
));
$anchor_text = '';
} else {
$anchor_text = null;
}
$line_text = phutil_render_tag(
'td',
array(
),
$anchor_text.
"\xE2\x80\x8B". // NOTE: See phabricator-oncopy behavior.
$line['data']);
$rows[] = phutil_render_tag(
'tr',
array(
'class' => ($line['highlighted'] ? 'highlighted' : null),
),
$blame.
$line_text);
$rows = array_merge($rows, $this->renderInlines(
idx($inlines, $line['line'], array()),
$needs_blame,
$engine));
}
return $rows;
}
private function renderInlines(array $inlines, $needs_blame, $engine) {
$rows = array();
foreach ($inlines as $inline) {
$inline_view = id(new DifferentialInlineCommentView())
->setMarkupEngine($engine)
->setInlineComment($inline)
->render();
$rows[] =
''.
str_repeat(' | ', ($needs_blame ? 5 : 1)).
''.$inline_view.' | '.
'
';
}
return $rows;
}
private function loadFileForData($path, $data) {
return PhabricatorFile::buildFromFileDataOrHash(
$data,
array(
'name' => basename($path),
));
}
private function buildRawResponse($path, $data) {
$file = $this->loadFileForData($path, $data);
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
}
private function buildImageCorpus($file_uri) {
$properties = new PhabricatorPropertyListView();
$properties->addProperty(
pht('Image'),
phutil_render_tag(
'img',
array(
'src' => $file_uri,
)));
$actions = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
->addAction($this->createEditAction());
return array($actions, $properties);
}
private function buildBinaryCorpus($file_uri, $data) {
$properties = new PhabricatorPropertyListView();
$size = strlen($data);
$properties->addTextContent(
- pht('This is a binary file. It is %2$s byte(s) in length.',
- $size,
- PhutilTranslator::getInstance()->formatNumber($size))
- );
+ pht(
+ 'This is a binary file. It is %s byte(s) in length.',
+ new PhutilNumber($size)));
$actions = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
->addAction($this->createEditAction())
->addAction(id(new PhabricatorActionView())
->setName(pht('Download Binary File...'))
->setIcon('download')
->setHref($file_uri));
return array($actions, $properties);
}
private function buildBeforeResponse($before) {
$request = $this->getRequest();
$drequest = $this->getDiffusionRequest();
// NOTE: We need to get the grandparent so we can capture filename changes
// in the parent.
$parent = $this->loadParentRevisionOf($before);
$old_filename = null;
$was_created = false;
if ($parent) {
$grandparent = $this->loadParentRevisionOf(
$parent->getCommitIdentifier());
if ($grandparent) {
$rename_query = new DiffusionRenameHistoryQuery();
$rename_query->setRequest($drequest);
$rename_query->setOldCommit($grandparent->getCommitIdentifier());
$old_filename = $rename_query->loadOldFilename();
$was_created = $rename_query->getWasCreated();
}
}
$follow = null;
if ($was_created) {
// If the file was created in history, that means older commits won't
// have it. Since we know it existed at 'before', it must have been
// created then; jump there.
$target_commit = $before;
$follow = 'created';
} else if ($parent) {
// If we found a parent, jump to it. This is the normal case.
$target_commit = $parent->getCommitIdentifier();
} else {
// If there's no parent, this was probably created in the initial commit?
// And the "was_created" check will fail because we can't identify the
// grandparent. Keep the user at 'before'.
$target_commit = $before;
$follow = 'first';
}
$path = $drequest->getPath();
$renamed = null;
if ($old_filename !== null &&
$old_filename !== '/'.$path) {
$renamed = $path;
$path = $old_filename;
}
$line = null;
// If there's a follow error, drop the line so the user sees the message.
if (!$follow) {
$line = $this->getBeforeLineNumber($target_commit);
}
$before_uri = $drequest->generateURI(
array(
'action' => 'browse',
'commit' => $target_commit,
'line' => $line,
'path' => $path,
));
$before_uri->setQueryParams($request->getRequestURI()->getQueryParams());
$before_uri = $before_uri->alter('before', null);
$before_uri = $before_uri->alter('renamed', $renamed);
$before_uri = $before_uri->alter('follow', $follow);
return id(new AphrontRedirectResponse())->setURI($before_uri);
}
private function getBeforeLineNumber($target_commit) {
$drequest = $this->getDiffusionRequest();
$line = $drequest->getLine();
if (!$line) {
return null;
}
$diff_query = DiffusionRawDiffQuery::newFromDiffusionRequest($drequest);
$diff_query->setAgainstCommit($target_commit);
try {
$raw_diff = $diff_query->loadRawDiff();
$old_line = 0;
$new_line = 0;
foreach (explode("\n", $raw_diff) as $text) {
if ($text[0] == '-' || $text[0] == ' ') {
$old_line++;
}
if ($text[0] == '+' || $text[0] == ' ') {
$new_line++;
}
if ($new_line == $line) {
return $old_line;
}
}
// We didn't find the target line.
return $line;
} catch (Exception $ex) {
return $line;
}
}
private function loadParentRevisionOf($commit) {
$drequest = $this->getDiffusionRequest();
$before_req = DiffusionRequest::newFromDictionary(
array(
'repository' => $drequest->getRepository(),
'commit' => $commit,
));
$query = DiffusionCommitParentsQuery::newFromDiffusionRequest($before_req);
$parents = $query->loadParents();
return head($parents);
}
}
diff --git a/src/applications/paste/controller/PhabricatorPasteListController.php b/src/applications/paste/controller/PhabricatorPasteListController.php
index 32194daed1..d6c6b8eb4a 100644
--- a/src/applications/paste/controller/PhabricatorPasteListController.php
+++ b/src/applications/paste/controller/PhabricatorPasteListController.php
@@ -1,121 +1,120 @@
filter = idx($data, 'filter');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$query = id(new PhabricatorPasteQuery())
->setViewer($user)
->needContent(true);
$nav = $this->buildSideNavView($this->filter);
$filter = $nav->getSelectedFilter();
switch ($filter) {
case 'my':
$query->withAuthorPHIDs(array($user->getPHID()));
$title = pht('My Pastes');
$nodata = pht("You haven't created any Pastes yet.");
break;
case 'all':
$title = pht('All Pastes');
$nodata = pht("There are no Pastes yet.");
break;
}
$pager = new AphrontCursorPagerView();
$pager->readFromRequest($request);
$pastes = $query->executeWithCursorPager($pager);
$list = $this->buildPasteList($pastes);
$list->setPager($pager);
$list->setNoDataString($nodata);
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$nav->appendChild(
array(
$header,
$list,
));
$crumbs = $this
->buildApplicationCrumbs($nav)
->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($this->getApplicationURI('filter/'.$filter.'/')));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
)
);
}
private function buildPasteList(array $pastes) {
assert_instances_of($pastes, 'PhabricatorPaste');
$user = $this->getRequest()->getUser();
$this->loadHandles(mpull($pastes, 'getAuthorPHID'));
$lang_map = PhabricatorEnv::getEnvConfig('pygments.dropdown-choices');
$list = new PhabricatorObjectItemListView();
$list->setUser($user);
foreach ($pastes as $paste) {
$created = phabricator_date($paste->getDateCreated(), $user);
$author = $this->getHandle($paste->getAuthorPHID())->renderLink();
$source_code = $this->buildSourceCodeView($paste, 5)->render();
$source_code = phutil_render_tag(
'div',
array(
'class' => 'phabricator-source-code-summary',
),
$source_code);
$line_count = count(explode("\n", $paste->getContent()));
$line_count = pht(
- '%2$s Line(s)',
- $line_count,
- PhutilTranslator::getInstance()->formatNumber($line_count));
+ '%s Line(s)',
+ new PhutilNumber($line_count));
$item = id(new PhabricatorObjectItemView())
->setHeader($paste->getFullName())
->setHref('/P'.$paste->getID())
->setObject($paste)
->addAttribute(pht('Created %s by %s', $created, $author))
->addIcon('none', $line_count)
->appendChild($source_code);
$lang_name = $paste->getLanguage();
if ($lang_name) {
$lang_name = idx($lang_map, $lang_name, $lang_name);
$item->addIcon('none', phutil_escape_html($lang_name));
}
$list->addItem($item);
}
return $list;
}
}
diff --git a/src/infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php b/src/infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php
index 8b102e0822..ced807deff 100644
--- a/src/infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php
+++ b/src/infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php
@@ -1,296 +1,296 @@
array(
'Differential Revision',
'Differential Revisions',
),
'file(s)' => array('file', 'files'),
'Maniphest Task(s)' => array('Maniphest Task', 'Maniphest Tasks'),
'Please fix these errors and try again.' => array(
'Please fix this error and try again.',
'Please fix these errors and try again.',
),
'%d Error(s)' => array('%d Error', '%d Errors'),
'%d Warning(s)' => array('%d Warning', '%d Warnings'),
'%d Auto-Fix(es)' => array('%d Auto-Fix', '%d Auto-Fixes'),
'%d Advice(s)' => array('%d Advice', '%d Pieces of Advice'),
'%d Detail(s)' => array('%d Detail', '%d Details'),
'(%d line(s))' => array('(%d line)', '(%d lines)'),
'COMMIT(S)' => array('COMMIT', 'COMMITS'),
'%d line(s)' => array('%d line', '%d lines'),
'added %d commit(s): %s' => array(
'added commit: %2$s',
'added commits: %2$s',
),
'removed %d commit(s): %s' => array(
'removed commit: %2$s',
'removed commits: %2$s',
),
'changed %d commit(s), added %d: %s; removed %d: %s' =>
'changed commits, added: %3$s; removed: %5$s',
'ATTACHED %d COMMIT(S)' => array(
'ATTACHED COMMIT',
'ATTACHED COMMITS',
),
'added %d dependencie(s): %s' => array(
'added dependency: %2$s',
'added dependencies: %2$s',
),
'added %d dependent task(s): %s' => array(
'added dependent task: %2$s',
'added dependent tasks: %2$s',
),
'removed %d dependencie(s): %s' => array(
'removed dependency: %2$s',
'removed dependencies: %2$s',
),
'removed %d dependent task(s): %s' => array(
'removed dependent task: %2$s',
'removed dependent tasks: %2$s',
),
'changed %d dependencie(s), added %d: %s; removed %d: %s' =>
'changed dependencies, added: %3$s; removed: %5$s',
'changed %d dependent task(s), added %d: %s; removed %d: %s',
'changed dependent tasks, added: %3$s; removed: %5$s',
'DEPENDENT %d TASK(s)' => array(
'DEPENDENT TASK',
'DEPENDENT TASKS',
),
'DEPENDS ON %d TASK(S)' => array(
'DEPENDS ON TASK',
'DEPENDS ON TASKS',
),
'DIFFERENTIAL %d REVISION(S)' => array(
'DIFFERENTIAL REVISION',
'DIFFERENTIAL REVISIONS',
),
'added %d revision(s): %s' => array(
'added revision: %2$s',
'added revisions: %2$s',
),
'removed %d revision(s): %s' => array(
'removed revision: %2$s',
'removed revisions: %2$s',
),
'changed %d revision(s), added %d: %s; removed %d: %s' =>
'changed revisions, added %3$s; removed %5$s',
'There are %d raw fact(s) in storage.' => array(
'There is %d raw fact in storage.',
'There are %d raw facts in storage.',
),
'There are %d aggregate fact(s) in storage.' => array(
'There is %d aggregate fact in storage.',
'There are %d aggregate facts in storage.',
),
'%d Commit(s) Awaiting Audit' => array(
'%d Commit Awaiting Audit',
'%d Commits Awaiting Audit',
),
'%d Problem Commit(s)' => array(
'%d Problem Commit',
'%d Problem Commits',
),
'%d Review(s) Need Attention' => array(
'%d Review Needs Attention',
'%d Reviews Need Attention',
),
'%d Review(s) Waiting on Others' => array(
'%d Review Waiting on Others',
'%d Reviews Waiting on Others',
),
'%d Flagged Object(s)' => array(
'%d Flagged Object',
'%d Flagged Objects',
),
'%d Unbreak Now Task(s)!' => array(
'%d Unbreak Now Task!',
'%d Unbreak Now Tasks!',
),
'%d Assigned Task(s)' => array(
'%d Assigned Task',
'%d Assigned Tasks',
),
'Show %d Lint Message(s)' => array(
'Show %d Lint Message',
'Show %d Lint Messages',
),
'Hide %d Lint Message(s)' => array(
'Hide %d Lint Message',
'Hide %d Lint Messages',
),
'Switch for %d Lint Message(s)' => array(
'Switch for %d Lint Message',
'Switch for %d Lint Messages',
),
'%d Lint Message(s)' => array(
'%d Lint Message',
'%d Lint Messages',
),
- 'This is a binary file. It is %2$s byte(s) in length.' => array(
- 'This is a binary file. It is %2$s byte in length.',
- 'This is a binary file. It is %2$s bytes in length.',
+ 'This is a binary file. It is %s byte(s) in length.' => array(
+ 'This is a binary file. It is %s byte in length.',
+ 'This is a binary file. It is %s bytes in length.',
),
'%d Action(s) Have No Effect' => array(
'Action Has No Effect',
'Actions Have No Effect',
),
'%d Action(s) With No Effect' => array(
'Action With No Effect',
'Actions With No Effect',
),
'%s added %d subscriber(s): %s.' => array(
array(
'%s added a subscriber: %3$s.',
'%s added subscribers: %3$s.',
),
array(
'%s added a subscriber: %3$s.',
'%s added subscribers: %3$s.',
),
),
'%s removed %d subscriber(s): %s.' => array(
array(
'%s removed a subscriber: %3$s.',
'%s removed subscribers: %3$s.',
),
array(
'%s removed a subscriber: %3$s.',
'%s removed subscribers: %3$s.',
),
),
'%s added %d participant(s): %s.' => array(
array(
'%s added a participant: %3$s.',
'%s added participants: %3$s.',
),
array(
'%s added a participant: %3$s.',
'%s added participants: %3$s.',
),
),
'%s removed %d participant(s): %s.' => array(
array(
'%s removed a participant: %3$s.',
'%s removed participants: %3$s.',
),
array(
'%s removed a participant: %3$s.',
'%s removed participants: %3$s.',
),
),
- '%2$s Line(s)' => array(
- '%2$s Line',
- '%2$s Lines',
+ '%s Line(s)' => array(
+ '%s Line',
+ '%s Lines',
),
"Indexing %d object(s) of type %s." => array(
"Indexing %d object of type %s.",
"Indexing %d object of type %s.",
),
'Run these %d command(s):' => array(
'Run this command:',
'Run these commands:',
),
'Install these %d PHP extension(s):' => array(
'Install this %d PHP extension:',
'Install these %d PHP extensions:',
),
'The current Phabricator configuration has these %d value(s):' => array(
'The current Phabricator configuration has this %d value:',
'The current Phabricator configuration has these %d values:',
),
'To update these %d value(s), run these command(s) from the command line:'
=> array(
'To update this %d value, run this command from the command line:',
'To update these %d values, run these commands from the command line:',
),
'You can update these %d value(s) here:' => array(
'You can update this %d value here:',
'You can update these %d values here:',
),
'The current PHP configuration has these %d value(s):' => array(
'The current PHP configuration has this %d value:',
'The current PHP configuration has these %d values:',
),
'To update these %d value(s), edit your PHP configuration file.' => array(
'To update this %d value, edit your PHP configuration file.',
'To update these %d values, edit your PHP configuration file.',
),
'To update these %d value(s), edit your PHP configuration file, located '.
'here:' => array(
'To update this %d value, edit your PHP configuration file, located '.
'here:',
'To update these %d values, edit your PHP configuration file, located '.
'here:',
),
'PHP also loaded these configuration file(s):' => array(
'PHP also loaded this configuration file:',
'PHP also loaded these configuration files:',
),
'You have %d unresolved setup issue(s)...' => array(
'You have %d unresolved setup issue...',
'You have %d unresolved setup issues...',
),
);
}
}