Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2890694
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
104 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php
index 4bd0cb5655..e0e9d1f8ec 100644
--- a/src/applications/differential/controller/DifferentialRevisionViewController.php
+++ b/src/applications/differential/controller/DifferentialRevisionViewController.php
@@ -1,835 +1,835 @@
<?php
/*
* Copyright 2012 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.
*/
final class DifferentialRevisionViewController extends DifferentialController {
private $revisionID;
public function shouldRequireLogin() {
return !$this->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;
}
list($aux_fields, $props) = $this->loadAuxiliaryFieldsAndProperties(
$revision,
$target_manual,
array(
'local:commits',
'arc:lint',
'arc:unit',
));
$arc_project = $target->loadArcanistProject();
$repository = ($arc_project ? $arc_project->loadRepository() : null);
list($changesets, $vs_map, $rendering_references) =
$this->loadChangesetsAndVsMap(
$target,
idx($diffs, $diff_vs),
$repository);
$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_phids[$key] = $aux_field->getRequiredHandlePHIDsForRevisionView();
}
$object_phids = array_merge($object_phids, array_mergev($aux_phids));
$object_phids = array_unique($object_phids);
$handles = id(new PhabricatorObjectHandleData($object_phids))
->loadHandles();
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;
$has_live_reviewer = false;
foreach ($revision->getReviewers() as $reviewer) {
if (!$handles[$reviewer]->isDisabled()) {
$has_live_reviewer = true;
}
}
if (!$has_live_reviewer) {
$reviewer_warning = new AphrontErrorView();
$reviewer_warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$reviewer_warning->setTitle('No Active Reviewers');
if ($revision->getReviewers()) {
$reviewer_warning->appendChild(
'<p>All specified reviewers are disabled. You may want to add '.
'some new reviewers.</p>');
} else {
$reviewer_warning->appendChild(
'<p>This revision has no specified reviewers. You may want to '.
'add some.</p>');
}
}
$request_uri = $request->getRequestURI();
$limit = 100;
$large = $request->getStr('large');
if (count($changesets) > $limit && !$large) {
$count = number_format(count($changesets));
$warning = new AphrontErrorView();
$warning->setTitle('Very Large Diff');
$warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$warning->setWidth(AphrontErrorView::WIDTH_WIDE);
$warning->appendChild(
"<p>This diff is very large and affects {$count} files. Load ".
"each file individually. ".
"<strong>".
phutil_render_tag(
'a',
array(
'href' => $request_uri
->alter('large', 'true')
->setFragment('differential-review-toc'),
),
'Show All Files Inline').
"</strong>");
$warning = $warning->render();
$visible_changesets = array();
foreach ($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->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());
$actions = array_merge(
$actions,
$custom_renderer->generateActionLinks($revision, $target_manual));
}
$whitespace = $request->getStr(
'whitespace',
DifferentialChangesetParser::WHITESPACE_IGNORE_ALL);
if ($arc_project) {
$symbol_indexes = $this->buildSymbolIndexes(
$arc_project,
$visible_changesets);
} else {
$symbol_indexes = array();
}
$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);
$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, $target);
+ $changeset_view->setRepository($repository);
}
$changeset_view->setSymbolIndexes($symbol_indexes);
$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());
if ($draft) {
$draft = $draft->getDraft();
} else {
$draft = null;
}
$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);
}
$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())
->setLineWidthFromChangesets($changesets)
->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());
return $this->buildStandardPageResponse(
array(
$reviewer_warning,
$revision_detail,
$page_pane,
),
array(
'title' => 'D'.$revision->getID().' '.$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(
'class' => 'revision-edit',
'href' => "/differential/revision/edit/{$revision_id}/",
'name' => 'Edit Revision',
);
}
if (!$viewer_is_anonymous) {
require_celerity_resource('phabricator-flag-css');
$flag = PhabricatorFlagQuery::loadUserFlag($user, $revision_phid);
if ($flag) {
$class = PhabricatorFlagColor::getCSSClass($flag->getColor());
$color = PhabricatorFlagColor::getColorName($flag->getColor());
$links[] = array(
'class' => 'flag-clear '.$class,
'href' => '/flag/delete/'.$flag->getID().'/',
'name' => phutil_escape_html('Remove '.$color.' Flag'),
'sigil' => 'workflow',
);
} else {
$links[] = array(
'class' => 'flag-add phabricator-flag-ghost',
'href' => '/flag/edit/'.$revision_phid.'/',
'name' => 'Flag Revision',
'sigil' => 'workflow',
);
}
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',
'instant' => true,
);
} else {
$links[] = array(
'class' => 'subscribe-rem unavailable',
'name' => 'Automatically Subscribed',
);
}
require_celerity_resource('phabricator-object-selector-css');
require_celerity_resource('javelin-behavior-phabricator-object-selector');
$links[] = array(
'class' => 'action-dependencies',
'name' => 'Edit Dependencies',
'href' => "/search/attach/{$revision_phid}/DREV/dependencies/",
'sigil' => 'workflow',
);
if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
$links[] = array(
'class' => 'attach-maniphest',
'name' => 'Edit Maniphest Tasks',
'href' => "/search/attach/{$revision_phid}/TASK/",
'sigil' => 'workflow',
);
}
if ($user->getIsAdmin()) {
$links[] = array(
'class' => 'transcripts-metamta',
'name' => 'MetaMTA Transcripts',
'href' => "/mail/?phid={$revision_phid}",
);
}
$links[] = array(
'class' => 'transcripts-herald',
'name' => 'Herald Transcripts',
'href' => "/herald/transcript/?phid={$revision_phid}",
);
}
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();
if ($viewer_is_owner) {
switch ($status) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$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_ADDREVIEWERS] = true;
$actions[DifferentialAction::ACTION_ADDCCS] = true;
$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();
foreach ($changesets as $changeset) {
$refs[$changeset->getID()] = $changeset->getID();
}
$vs_map = array();
if ($diff_vs) {
$vs_changesets = array();
$vs_id = $diff_vs->getID();
foreach (idx($changeset_groups, $vs_id, array()) as $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $diff_vs);
$vs_changesets[$path] = $changeset;
}
foreach ($changesets as $key => $changeset) {
$file = $changeset->getAbsoluteRepositoryPath($repository, $target);
if (isset($vs_changesets[$file])) {
$vs_map[$changeset->getID()] = $vs_changesets[$file]->getID();
$refs[$changeset->getID()] =
$changeset->getID().'/'.$vs_changesets[$file]->getID();
unset($vs_changesets[$file]);
} else {
$refs[$changeset->getID()] = $changeset->getID();
}
}
foreach ($vs_changesets as $changeset) {
$changesets[$changeset->getID()] = $changeset;
$vs_map[$changeset->getID()] = -1;
$refs[$changeset->getID()] = $changeset->getID().'/-1';
}
}
$changesets = msort($changesets, 'getSortKey');
return array($changesets, $vs_map, $refs);
}
private function loadAuxiliaryFieldsAndProperties(
DifferentialRevision $revision,
DifferentialDiff $diff,
array $special_properties) {
$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);
$aux_props = array();
foreach ($aux_fields as $key => $aux_field) {
$aux_field->setDiff($diff);
$aux_props[$key] = $aux_field->getRequiredDiffProperties();
}
$required_properties = array_mergev($aux_props);
$required_properties = array_merge(
$required_properties,
$special_properties);
$property_map = array();
if ($required_properties) {
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID = %d AND name IN (%Ls)',
$diff->getID(),
$required_properties);
$property_map = mpull($properties, 'getData', 'getName');
}
foreach ($aux_fields as $key => $aux_field) {
// Give each field only the properties it specifically required, and
// set 'null' for each requested key which we didn't actually load a
// value for (otherwise, getDiffProperty() will throw).
if ($aux_props[$key]) {
$props = array_select_keys($property_map, $aux_props[$key]) +
array_fill_keys($aux_props[$key], null);
} else {
$props = array();
}
$aux_field->setDiffProperties($props);
}
return array(
$aux_fields,
array_select_keys(
$property_map,
$special_properties));
}
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();
}
$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 $symbol_indexes;
}
private function loadOtherRevisions(
array $changesets,
DifferentialDiff $target,
PhabricatorRepository $repository) {
assert_instances_of($changesets, 'DifferentialChangeset');
$paths = array();
foreach ($changesets as $changeset) {
$paths[] = $changeset->getAbsoluteRepositoryPath(
$repository,
$target);
}
if (!$paths) {
return array();
}
$path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs();
if (!$path_map) {
return array();
}
$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());
$phids = $view->getRequiredHandlePHIDs();
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$view->setHandles($handles);
return
'<div class="differential-panel">'.
'<h1>Open Revisions Affecting These Files</h1>'.
$view->render().
'</div>';
}
}
diff --git a/src/applications/herald/config/HeraldActionConfig.php b/src/applications/herald/config/HeraldActionConfig.php
index c5a359d80a..57bce933f5 100644
--- a/src/applications/herald/config/HeraldActionConfig.php
+++ b/src/applications/herald/config/HeraldActionConfig.php
@@ -1,152 +1,152 @@
<?php
/*
* Copyright 2012 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.
*/
final class HeraldActionConfig {
const ACTION_ADD_CC = 'addcc';
const ACTION_REMOVE_CC = 'remcc';
const ACTION_EMAIL = 'email';
const ACTION_NOTHING = 'nothing';
const ACTION_AUDIT = 'audit';
const ACTION_FLAG = 'flag';
public static function getActionMessageMapForRuleType($rule_type) {
$generic_mappings = array(
self::ACTION_NOTHING => 'Do nothing',
self::ACTION_ADD_CC => 'Add emails to CC',
self::ACTION_REMOVE_CC => 'Remove emails from CC',
self::ACTION_EMAIL => 'Send an email to',
self::ACTION_AUDIT => 'Trigger an Audit',
self::ACTION_FLAG => 'Mark with flag',
);
switch ($rule_type) {
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
$specific_mappings = array(
self::ACTION_AUDIT => 'Trigger an Audit for project',
);
break;
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
$specific_mappings = array(
self::ACTION_ADD_CC => 'CC me',
self::ACTION_REMOVE_CC => 'Remove me from CC',
self::ACTION_EMAIL => 'Email me',
self::ACTION_AUDIT => 'Trigger an Audit by me',
);
break;
case null:
$specific_mappings = array();
// Use generic mappings, used on transcript.
break;
default:
throw new Exception("Unknown rule type '${rule_type}'");
}
return $specific_mappings + $generic_mappings;
}
public static function getActionMessageMap($content_type,
$rule_type) {
$map = self::getActionMessageMapForRuleType($rule_type);
switch ($content_type) {
case HeraldContentTypeConfig::CONTENT_TYPE_DIFFERENTIAL:
return array_select_keys(
$map,
array(
self::ACTION_ADD_CC,
self::ACTION_REMOVE_CC,
self::ACTION_EMAIL,
self::ACTION_FLAG,
self::ACTION_NOTHING,
));
case HeraldContentTypeConfig::CONTENT_TYPE_COMMIT:
return array_select_keys(
$map,
array(
self::ACTION_EMAIL,
self::ACTION_AUDIT,
self::ACTION_FLAG,
self::ACTION_NOTHING,
));
case HeraldContentTypeConfig::CONTENT_TYPE_MERGE:
return array_select_keys(
$map,
array(
self::ACTION_EMAIL,
self::ACTION_NOTHING,
));
case HeraldContentTypeConfig::CONTENT_TYPE_OWNERS:
return array_select_keys(
$map,
array(
self::ACTION_EMAIL,
self::ACTION_NOTHING,
));
default:
- throw new Exception("Unknown content type '{$type}'.");
+ throw new Exception("Unknown content type '{$content_type}'.");
}
}
/**
* Create a HeraldAction to save from data.
*
* $data is of the form:
* array(
* 0 => <action type>
* 1 => array(<targets>)
* )
*/
public static function willSaveAction($rule_type,
$author_phid,
$data) {
$obj = new HeraldAction();
$obj->setAction($data[0]);
// for personal rule types, set the target to be the owner of the rule
if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
switch ($obj->getAction()) {
case HeraldActionConfig::ACTION_EMAIL:
case HeraldActionConfig::ACTION_ADD_CC:
case HeraldActionConfig::ACTION_REMOVE_CC:
case HeraldActionConfig::ACTION_AUDIT:
$data[1] = array($author_phid => $author_phid);
break;
case HeraldActionConfig::ACTION_FLAG:
// Make sure flag color is valid; set to blue if not.
$color_map = PhabricatorFlagColor::getColorNameMap();
if (empty($color_map[$data[1]])) {
$data[1] = PhabricatorFlagColor::COLOR_BLUE;
}
break;
case HeraldActionConfig::ACTION_NOTHING:
break;
default:
throw new Exception('Unrecognized action type: ' .
$obj->getAction());
}
}
if (is_array($data[1])) {
$obj->setTarget(array_keys($data[1]));
} else {
$obj->setTarget($data[1]);
}
return $obj;
}
}
diff --git a/src/applications/maniphest/controller/ManiphestTaskEditController.php b/src/applications/maniphest/controller/ManiphestTaskEditController.php
index fbbb31d14f..42b8d36192 100644
--- a/src/applications/maniphest/controller/ManiphestTaskEditController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskEditController.php
@@ -1,569 +1,569 @@
<?php
/*
* Copyright 2012 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.
*/
/**
* @group maniphest
*/
final class ManiphestTaskEditController extends ManiphestController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$files = array();
$parent_task = null;
$template_id = null;
if ($this->id) {
$task = id(new ManiphestTask())->load($this->id);
if (!$task) {
return new Aphront404Response();
}
} else {
$task = new ManiphestTask();
$task->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
$task->setAuthorPHID($user->getPHID());
// These allow task creation with defaults.
if (!$request->isFormPost()) {
$task->setTitle($request->getStr('title'));
$default_projects = $request->getStr('projects');
if ($default_projects) {
$task->setProjectPHIDs(explode(';', $default_projects));
}
}
$file_phids = $request->getArr('files', array());
if (!$file_phids) {
// Allow a single 'file' key instead, mostly since Mac OS X urlencodes
// square brackets in URLs when passed to 'open', so you can't 'open'
// a URL like '?files[]=xyz' and have PHP interpret it correctly.
$phid = $request->getStr('file');
if ($phid) {
$file_phids = array($phid);
}
}
if ($file_phids) {
$files = id(new PhabricatorFile())->loadAllWhere(
'phid IN (%Ls)',
$file_phids);
}
$template_id = $request->getInt('template');
// You can only have a parent task if you're creating a new task.
$parent_id = $request->getInt('parent');
if ($parent_id) {
$parent_task = id(new ManiphestTask())->load($parent_id);
}
}
$errors = array();
$e_title = true;
$extensions = ManiphestTaskExtensions::newExtensions();
$aux_fields = $extensions->getAuxiliaryFieldSpecifications();
if ($request->isFormPost()) {
$changes = array();
$new_title = $request->getStr('title');
$new_desc = $request->getStr('description');
$new_status = $request->getStr('status');
$workflow = '';
if ($task->getID()) {
if ($new_title != $task->getTitle()) {
$changes[ManiphestTransactionType::TYPE_TITLE] = $new_title;
}
if ($new_desc != $task->getDescription()) {
$changes[ManiphestTransactionType::TYPE_DESCRIPTION] = $new_desc;
}
if ($new_status != $task->getStatus()) {
$changes[ManiphestTransactionType::TYPE_STATUS] = $new_status;
}
} else {
$task->setTitle($new_title);
$task->setDescription($new_desc);
$changes[ManiphestTransactionType::TYPE_STATUS] =
ManiphestTaskStatus::STATUS_OPEN;
$workflow = 'create';
}
$owner_tokenizer = $request->getArr('assigned_to');
$owner_phid = reset($owner_tokenizer);
if (!strlen($new_title)) {
$e_title = 'Required';
$errors[] = 'Title is required.';
}
foreach ($aux_fields as $aux_field) {
$aux_field->setValueFromRequest($request);
if ($aux_field->isRequired() && !strlen($aux_field->getValue())) {
$errors[] = $aux_field->getLabel() . ' is required.';
$aux_field->setError('Required');
}
if (strlen($aux_field->getValue())) {
try {
$aux_field->validate();
} catch (Exception $e) {
$errors[] = $e->getMessage();
$aux_field->setError('Invalid');
}
}
}
if ($errors) {
$task->setPriority($request->getInt('priority'));
$task->setOwnerPHID($owner_phid);
$task->setCCPHIDs($request->getArr('cc'));
$task->setProjectPHIDs($request->getArr('projects'));
} else {
if ($request->getInt('priority') != $task->getPriority()) {
$changes[ManiphestTransactionType::TYPE_PRIORITY] =
$request->getInt('priority');
}
if ($owner_phid != $task->getOwnerPHID()) {
$changes[ManiphestTransactionType::TYPE_OWNER] = $owner_phid;
}
if ($request->getArr('cc') != $task->getCCPHIDs()) {
$changes[ManiphestTransactionType::TYPE_CCS] = $request->getArr('cc');
}
$new_proj_arr = $request->getArr('projects');
$new_proj_arr = array_values($new_proj_arr);
sort($new_proj_arr);
$cur_proj_arr = $task->getProjectPHIDs();
$cur_proj_arr = array_values($cur_proj_arr);
sort($cur_proj_arr);
if ($new_proj_arr != $cur_proj_arr) {
$changes[ManiphestTransactionType::TYPE_PROJECTS] = $new_proj_arr;
}
if ($files) {
$file_map = mpull($files, 'getPHID');
$file_map = array_fill_keys($file_map, array());
$changes[ManiphestTransactionType::TYPE_ATTACH] = array(
PhabricatorPHIDConstants::PHID_TYPE_FILE => $file_map,
);
}
$content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_WEB,
array(
'ip' => $request->getRemoteAddr(),
));
$template = new ManiphestTransaction();
$template->setAuthorPHID($user->getPHID());
$template->setContentSource($content_source);
$transactions = array();
foreach ($changes as $type => $value) {
$transaction = clone $template;
$transaction->setTransactionType($type);
$transaction->setNewValue($value);
$transactions[] = $transaction;
}
if ($aux_fields) {
$task->loadAndAttachAuxiliaryAttributes();
foreach ($aux_fields as $aux_field) {
$transaction = clone $template;
$transaction->setTransactionType(
ManiphestTransactionType::TYPE_AUXILIARY);
$aux_key = $aux_field->getAuxiliaryKey();
$transaction->setMetadataValue('aux:key', $aux_key);
$transaction->setNewValue($aux_field->getValueForStorage());
$transactions[] = $transaction;
}
}
if ($transactions) {
$is_new = !$task->getID();
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK,
array(
'task' => $task,
'new' => $is_new,
'transactions' => $transactions,
));
$event->setUser($user);
$event->setAphrontRequest($request);
PhutilEventEngine::dispatchEvent($event);
$task = $event->getValue('task');
$transactions = $event->getValue('transactions');
$editor = new ManiphestTransactionEditor();
$editor->setAuxiliaryFields($aux_fields);
$editor->applyTransactions($task, $transactions);
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK,
array(
'task' => $task,
'new' => $is_new,
'transactions' => $transactions,
));
$event->setUser($user);
$event->setAphrontRequest($request);
PhutilEventEngine::dispatchEvent($event);
}
if ($parent_task) {
$type_task = PhabricatorPHIDConstants::PHID_TYPE_TASK;
// NOTE: It's safe to simply apply this transaction without doing
// cycle detection because we know the new task has no children.
$new_value = $parent_task->getAttached();
$new_value[$type_task][$task->getPHID()] = array();
$parent_xaction = clone $template;
$attach_type = ManiphestTransactionType::TYPE_ATTACH;
$parent_xaction->setTransactionType($attach_type);
$parent_xaction->setNewValue($new_value);
$editor = new ManiphestTransactionEditor();
$editor->setAuxiliaryFields($aux_fields);
$editor->applyTransactions($parent_task, array($parent_xaction));
$workflow = $parent_task->getID();
}
$redirect_uri = '/T'.$task->getID();
if ($workflow) {
$redirect_uri .= '?workflow='.$workflow;
}
return id(new AphrontRedirectResponse())
->setURI($redirect_uri);
}
} else {
if ($aux_fields) {
$task->loadAndAttachAuxiliaryAttributes();
foreach ($aux_fields as $aux_field) {
$aux_key = $aux_field->getAuxiliaryKey();
$value = $task->getAuxiliaryAttribute($aux_key);
$aux_field->setValueFromStorage($value);
}
}
if (!$task->getID()) {
$task->setCCPHIDs(array(
$user->getPHID(),
));
if ($template_id) {
$template_task = id(new ManiphestTask())->load($template_id);
if ($template_task) {
$task->setCCPHIDs($template_task->getCCPHIDs());
$task->setProjectPHIDs($template_task->getProjectPHIDs());
$task->setOwnerPHID($template_task->getOwnerPHID());
if ($aux_fields) {
$template_task->loadAndAttachAuxiliaryAttributes();
foreach ($aux_fields as $aux_field) {
if (!$aux_field->shouldCopyWhenCreatingSimilarTask()) {
continue;
}
$aux_key = $aux_field->getAuxiliaryKey();
$value = $template_task->getAuxiliaryAttribute($aux_key);
$aux_field->setValueFromStorage($value);
}
}
}
}
}
}
$phids = array_merge(
array($task->getOwnerPHID()),
$task->getCCPHIDs(),
$task->getProjectPHIDs());
if ($parent_task) {
$phids[] = $parent_task->getPHID();
}
$phids = array_filter($phids);
$phids = array_unique($phids);
$handles = id(new PhabricatorObjectHandleData($phids))
- ->loadHandles($phids);
+ ->loadHandles();
$tvalues = mpull($handles, 'getFullName', 'getPHID');
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
$error_view->setTitle('Form Errors');
}
$priority_map = ManiphestTaskPriority::getTaskPriorityMap();
if ($task->getOwnerPHID()) {
$assigned_value = array(
$task->getOwnerPHID() => $handles[$task->getOwnerPHID()]->getFullName(),
);
} else {
$assigned_value = array();
}
if ($task->getCCPHIDs()) {
$cc_value = array_select_keys($tvalues, $task->getCCPHIDs());
} else {
$cc_value = array();
}
if ($task->getProjectPHIDs()) {
$projects_value = array_select_keys($tvalues, $task->getProjectPHIDs());
} else {
$projects_value = array();
}
$cancel_id = nonempty($task->getID(), $template_id);
if ($cancel_id) {
$cancel_uri = '/T'.$cancel_id;
} else {
$cancel_uri = '/maniphest/';
}
if ($task->getID()) {
$button_name = 'Save Task';
$header_name = 'Edit Task';
} else if ($parent_task) {
$cancel_uri = '/T'.$parent_task->getID();
$button_name = 'Create Task';
$header_name = 'Create New Subtask';
} else {
$button_name = 'Create Task';
$header_name = 'Create New Task';
}
require_celerity_resource('maniphest-task-edit-css');
$project_tokenizer_id = celerity_generate_unique_node_id();
$form = new AphrontFormView();
$form
->setUser($user)
->setAction($request->getRequestURI()->getPath())
->addHiddenInput('template', $template_id);
if ($parent_task) {
$form
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Parent Task')
->setValue($handles[$parent_task->getPHID()]->getFullName()))
->addHiddenInput('parent', $parent_task->getID());
}
$form
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Title')
->setName('title')
->setError($e_title)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
->setValue($task->getTitle()));
if ($task->getID()) {
// Only show this in "edit" mode, not "create" mode, since creating a
// non-open task is kind of silly and it would just clutter up the
// "create" interface.
$form
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Status')
->setName('status')
->setValue($task->getStatus())
->setOptions(ManiphestTaskStatus::getTaskStatusMap()));
}
$form
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel('Assigned To')
->setName('assigned_to')
->setValue($assigned_value)
->setUser($user)
->setDatasource('/typeahead/common/users/')
->setLimit(1))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel('CC')
->setName('cc')
->setValue($cc_value)
->setUser($user)
->setDatasource('/typeahead/common/mailable/'))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Priority')
->setName('priority')
->setOptions($priority_map)
->setValue($task->getPriority()))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel('Projects')
->setName('projects')
->setValue($projects_value)
->setID($project_tokenizer_id)
->setCaption(
javelin_render_tag(
'a',
array(
'href' => '/project/create/',
'mustcapture' => true,
'sigil' => 'project-create',
),
'Create New Project'))
->setDatasource('/typeahead/common/projects/'));
if ($aux_fields) {
foreach ($aux_fields as $aux_field) {
if ($aux_field->isRequired() &&
!$aux_field->getError() &&
!$aux_field->getValue()) {
$aux_field->setError(true);
}
$aux_control = $aux_field->renderControl();
$form->appendChild($aux_control);
}
}
require_celerity_resource('aphront-error-view-css');
Javelin::initBehavior('maniphest-project-create', array(
'tokenizerID' => $project_tokenizer_id,
));
if ($files) {
$file_display = array();
foreach ($files as $file) {
$file_display[] = phutil_escape_html($file->getName());
}
$file_display = implode('<br />', $file_display);
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel('Files')
->setValue($file_display));
foreach ($files as $ii => $file) {
$form->addHiddenInput('files['.$ii.']', $file->getPHID());
}
}
$email_create = PhabricatorEnv::getEnvConfig(
'metamta.maniphest.public-create-email');
$email_hint = null;
if (!$task->getID() && $email_create) {
$email_hint = 'You can also create tasks by sending an email to: '.
'<tt>'.phutil_escape_html($email_create).'</tt>';
}
$panel_id = celerity_generate_unique_node_id();
$form
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Description')
->setName('description')
->setID('description-textarea')
->setCaption($email_hint)
->setValue($task->getDescription()));
if (!$task->getID()) {
$form
->appendChild(
id(new AphrontFormDragAndDropUploadControl())
->setLabel('Attached Files')
->setName('files')
->setDragAndDropTarget($panel_id)
->setActivatedClass('aphront-panel-view-drag-and-drop'));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($button_name));
$panel = new AphrontPanelView();
$panel->setWidth(AphrontPanelView::WIDTH_FULL);
$panel->setHeader($header_name);
$panel->setID($panel_id);
$panel->appendChild($form);
$description_preview_panel =
'<div class="aphront-panel-preview aphront-panel-preview-full">
<div class="maniphest-description-preview-header">
Description Preview
</div>
<div id="description-preview">
<div class="aphront-panel-preview-loading-text">
Loading preview...
</div>
</div>
</div>';
Javelin::initBehavior(
'maniphest-description-preview',
array(
'preview' => 'description-preview',
'textarea' => 'description-textarea',
'uri' => '/maniphest/task/descriptionpreview/',
));
return $this->buildStandardPageResponse(
array(
$error_view,
$panel,
$description_preview_panel
),
array(
'title' => $header_name,
));
}
}
diff --git a/src/applications/oauthserver/controller/PhabricatorOAuthServerTestController.php b/src/applications/oauthserver/controller/PhabricatorOAuthServerTestController.php
index f0554c0a83..7036d8b393 100644
--- a/src/applications/oauthserver/controller/PhabricatorOAuthServerTestController.php
+++ b/src/applications/oauthserver/controller/PhabricatorOAuthServerTestController.php
@@ -1,69 +1,69 @@
<?php
/*
* Copyright 2012 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.
*/
/**
* @group oauthserver
*/
final class PhabricatorOAuthServerTestController
extends PhabricatorOAuthServerController {
public function shouldRequireLogin() {
return true;
}
public function processRequest() {
$request = $this->getRequest();
$current_user = $request->getUser();
- $server = new PhabricatorOAuthServer($current_user);
+ $server = new PhabricatorOAuthServer();
$panels = array();
$results = array();
if ($request->isFormPost()) {
$action = $request->getStr('action');
switch ($action) {
case 'testclientauthorization':
$user_phid = $current_user->getPHID();
$client_phid = $request->getStr('client_phid');
$client = id(new PhabricatorOAuthServerClient)
->loadOneWhere('phid = %s', $client_phid);
if (!$client) {
throw new Exception('Failed to load client!');
}
if ($client->getCreatorPHID() != $user_phid ||
$current_user->getPHID() != $user_phid) {
throw new Exception(
'Only allowed to make test data for yourself '.
'for clients you own!'
);
}
// blankclientauthorizations don't get scope
$scope = array();
$server->setUser($current_user);
$server->setClient($client);
$authorization = $server->authorizeClient($scope);
return id(new AphrontRedirectResponse())
->setURI('/oauthserver/clientauthorization/?edited='.
$authorization->getPHID());
break;
default:
break;
}
}
}
}
diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php
index 8e4d1d106e..94db9b16ad 100644
--- a/src/applications/phame/controller/post/PhamePostViewController.php
+++ b/src/applications/phame/controller/post/PhamePostViewController.php
@@ -1,150 +1,150 @@
<?php
/*
* Copyright 2012 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.
*/
/**
* @group phame
*/
final class PhamePostViewController
extends PhameController {
private $postPHID;
private $phameTitle;
private $bloggerName;
private function setPostPHID($post_phid) {
$this->postPHID = $post_phid;
return $this;
}
private function getPostPHID() {
return $this->postPHID;
}
private function setPhameTitle($phame_title) {
$this->phameTitle = $phame_title;
return $this;
}
private function getPhameTitle() {
return $this->phameTitle;
}
private function setBloggerName($blogger_name) {
$this->bloggerName = $blogger_name;
return $this;
}
private function getBloggerName() {
return $this->bloggerName;
}
protected function getSideNavFilter() {
$filter = 'post/view/'.$this->getPostPHID();
return $filter;
}
protected function getSideNavExtraPostFilters() {
$filters = array(
array('key' => $this->getSideNavFilter(),
'name' => $this->getPhameTitle())
);
return $filters;
}
public function willProcessRequest(array $data) {
$this->setPostPHID(idx($data, 'phid'));
$this->setPhameTitle(idx($data, 'phametitle'));
$this->setBloggerName(idx($data, 'bloggername'));
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$post_phid = null;
if ($this->getPostPHID()) {
$post_phid = $this->getPostPHID();
if (!$post_phid) {
return new Aphront404Response();
}
$post = id(new PhamePost())->loadOneWhere(
'phid = %s',
$post_phid);
if ($post) {
$this->setPhameTitle($post->getPhameTitle());
}
$blogger = id(new PhabricatorUser())->loadOneWhere(
'phid = %s', $post->getBloggerPHID());
if (!$blogger) {
return new Aphront404Response();
}
} else if ($this->getBloggerName() && $this->getPhameTitle()) {
$phame_title = $this->getPhameTitle();
$phame_title = PhabricatorSlug::normalize($phame_title);
- if ($phame_title != $this->getPhameTitle()) {
- $uri = $post->getViewURI($this->getBloggerName());
- return id(new AphrontRedirectResponse())->setURI($uri);
- }
$blogger = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$this->getBloggerName());
if (!$blogger) {
return new Aphront404Response();
}
$post = id(new PhamePost())->loadOneWhere(
'bloggerPHID = %s AND phameTitle = %s',
$blogger->getPHID(),
- $this->getPhameTitle());
+ $phame_title);
+ if ($post && $phame_title != $this->getPhameTitle()) {
+ $uri = $post->getViewURI($this->getBloggerName());
+ return id(new AphrontRedirectResponse())->setURI($uri);
+ }
}
if (!$post) {
return new Aphront404Response();
}
if ($post->isDraft() &&
$post->getBloggerPHID() != $user->getPHID()) {
return new Aphront404Response();
}
if ($post->isDraft()) {
$notice = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle('You are previewing a draft.')
->setErrors(array(
'Only you can see this draft until you publish it.',
'If you chose a comment widget it will show up when you publish.',
));
} else {
$notice = null;
}
$page = id(new PhamePostDetailView())
->setUser($user)
->setRequestURI($request->getRequestURI())
->setBlogger($blogger)
->setPost($post);
$this->setShowSideNav(false);
return $this->buildStandardPageResponse(
array(
$notice,
$page,
),
array(
'title' => $post->getTitle(),
));
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php
index 9a7528810d..f12a7d9f82 100644
--- a/src/applications/project/controller/PhabricatorProjectProfileController.php
+++ b/src/applications/project/controller/PhabricatorProjectProfileController.php
@@ -1,381 +1,382 @@
<?php
/*
* Copyright 2012 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.
*/
final class PhabricatorProjectProfileController
extends PhabricatorProjectController {
private $id;
private $page;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
$this->page = idx($data, 'page');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$project = id(new PhabricatorProject())->load($this->id);
if (!$project) {
return new Aphront404Response();
}
$profile = $project->loadProfile();
if (!$profile) {
$profile = new PhabricatorProjectProfile();
}
$src_phid = $profile->getProfileImagePHID();
if (!$src_phid) {
$src_phid = $user->getProfileImagePHID();
}
$file = id(new PhabricatorFile())->loadOneWhere('phid = %s',
$src_phid);
if ($file) {
$picture = $file->getBestURI();
} else {
$picture = PhabricatorUser::getDefaultProfileImageURI();
}
$members = mpull($project->loadAffiliations(), null, 'getUserPHID');
$nav_view = new AphrontSideNavFilterView();
$uri = new PhutilURI('/project/view/'.$project->getID().'/');
$nav_view->setBaseURI($uri);
$external_arrow = "\xE2\x86\x97";
$tasks_uri = '/maniphest/view/all/?projects='.$project->getPHID();
$slug = PhabricatorSlug::normalize($project->getName());
$phriction_uri = '/w/projects/'.$slug;
$edit_uri = '/project/edit/'.$project->getID().'/';
$nav_view->addFilter('dashboard', 'Dashboard');
$nav_view->addSpacer();
$nav_view->addFilter('feed', 'Feed');
$nav_view->addFilter(null, 'Tasks '.$external_arrow, $tasks_uri);
$nav_view->addFilter(null, 'Wiki '.$external_arrow, $phriction_uri);
$nav_view->addFilter('people', 'People');
$nav_view->addFilter('about', 'About');
$nav_view->addSpacer();
$nav_view->addFilter(null, "Edit Project\xE2\x80\xA6", $edit_uri);
$this->page = $nav_view->selectFilter($this->page, 'dashboard');
require_celerity_resource('phabricator-profile-css');
switch ($this->page) {
case 'dashboard':
$content = $this->renderTasksPage($project, $profile);
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(
array(
$project->getPHID(),
));
$stories = $query->execute();
$content .= $this->renderStories($stories);
break;
case 'about':
$content = $this->renderAboutPage($project, $profile);
break;
case 'people':
$content = $this->renderPeoplePage($project, $profile);
break;
case 'feed':
$content = $this->renderFeedPage($project, $profile);
break;
default:
throw new Exception("Unimplemented filter '{$this->page}'.");
}
$content = '<div style="padding: 1em;">'.$content.'</div>';
$nav_view->appendChild($content);
$header = new PhabricatorProfileHeaderView();
$header->setName($project->getName());
$header->setDescription(
phutil_utf8_shorten($profile->getBlurb(), 1024));
$header->setProfilePicture($picture);
$action = null;
if (empty($members[$user->getPHID()])) {
$action = phabricator_render_form(
$user,
array(
'action' => '/project/update/'.$project->getID().'/join/',
'method' => 'post',
),
phutil_render_tag(
'button',
array(
'class' => 'green',
),
'Join Project'));
} else {
$action = javelin_render_tag(
'a',
array(
'href' => '/project/update/'.$project->getID().'/leave/',
'sigil' => 'workflow',
'class' => 'grey button',
),
'Leave Project...');
}
$header->addAction($action);
$header->appendChild($nav_view);
return $this->buildStandardPageResponse(
$header,
array(
'title' => $project->getName().' Project',
));
}
private function renderAboutPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$viewer = $this->getRequest()->getUser();
$blurb = $profile->getBlurb();
$blurb = phutil_escape_html($blurb);
$blurb = str_replace("\n", '<br />', $blurb);
$phids = array_merge(
array($project->getAuthorPHID()),
$project->getSubprojectPHIDs()
);
$phids = array_unique($phids);
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$timestamp = phabricator_datetime($project->getDateCreated(), $viewer);
$about =
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">About</h1>
<div class="phabricator-profile-info-pane">
<table class="phabricator-profile-info-table">
<tr>
<th>Creator</th>
<td>'.$handles[$project->getAuthorPHID()]->renderLink().'</td>
</tr>
<tr>
<th>Created</th>
<td>'.$timestamp.'</td>
</tr>
<tr>
<th>PHID</th>
<td>'.phutil_escape_html($project->getPHID()).'</td>
</tr>
<tr>
<th>Blurb</th>
<td>'.$blurb.'</td>
</tr>
</table>
</div>
</div>';
if ($project->getSubprojectPHIDs()) {
$table = $this->renderSubprojectTable(
$handles,
$project->getSubprojectPHIDs());
$subproject_list = $table->render();
} else {
$subproject_list = '<p><em>No subprojects.</em></p>';
}
$about .=
'<div class="phabricator-profile-info-group">'.
'<h1 class="phabricator-profile-info-header">Subprojects</h1>'.
'<div class="phabricator-profile-info-pane">'.
$subproject_list.
'</div>'.
'</div>';
return $about;
}
private function renderPeoplePage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$affiliations = $project->loadAffiliations();
$phids = mpull($affiliations, 'getUserPHID');
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$affiliated = array();
foreach ($affiliations as $affiliation) {
$user = $handles[$affiliation->getUserPHID()]->renderLink();
$role = phutil_escape_html($affiliation->getRole());
$affiliated[] = '<li>'.$user.' — '.$role.'</li>';
}
if ($affiliated) {
$affiliated = '<ul>'.implode("\n", $affiliated).'</ul>';
} else {
$affiliated = '<p><em>No one is affiliated with this project.</em></p>';
}
return
'<div class="phabricator-profile-info-group">'.
'<h1 class="phabricator-profile-info-header">People</h1>'.
'<div class="phabricator-profile-info-pane">'.
$affiliated.
'</div>'.
'</div>';
}
private function renderFeedPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(array($project->getPHID()));
$stories = $query->execute();
if (!$stories) {
return 'There are no stories about this project.';
}
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(
array(
$project->getPHID(),
));
$stories = $query->execute();
return $this->renderStories($stories);
}
private function renderStories(array $stories) {
assert_instances_of($stories, 'PhabricatorFeedStory');
$builder = new PhabricatorFeedBuilder($stories);
$builder->setUser($this->getRequest()->getUser());
$view = $builder->buildView();
return
'<div class="phabricator-profile-info-group">'.
'<h1 class="phabricator-profile-info-header">Activity Feed</h1>'.
'<div class="phabricator-profile-info-pane">'.
$view->render().
'</div>'.
'</div>';
}
private function renderTasksPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$query = id(new ManiphestTaskQuery())
->withProjects(array($project->getPHID()))
->withStatus(ManiphestTaskQuery::STATUS_OPEN)
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
->setLimit(10)
->setCalculateRows(true);
$tasks = $query->execute();
$count = $query->getRowCount();
$phids = mpull($tasks, 'getOwnerPHID');
$phids = array_filter($phids);
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$task_views = array();
foreach ($tasks as $task) {
$view = id(new ManiphestTaskSummaryView())
->setTask($task)
->setHandles($handles)
->setUser($this->getRequest()->getUser());
$task_views[] = $view->render();
}
if (empty($tasks)) {
$task_views = '<em>No open tasks.</em>';
} else {
$task_views = implode('', $task_views);
}
$open = number_format($count);
$more_link = phutil_render_tag(
'a',
array(
'href' => '/maniphest/view/all/?projects='.$project->getPHID(),
),
"View All Open Tasks \xC2\xBB");
$content =
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">'.
"Open Tasks ({$open})".
'</h1>'.
'<div class="phabricator-profile-info-pane">'.
$task_views.
'<div class="phabricator-profile-info-pane-more-link">'.
$more_link.
'</div>'.
'</div>
</div>';
return $content;
}
private function renderSubprojectTable(
- PhabricatorObjectHandleData $handles,
- $subprojects_phids) {
+ array $handles,
+ array $subprojects_phids) {
+ assert_instances_of($handles, 'PhabricatorObjectHandle');
$rows = array();
foreach ($subprojects_phids as $subproject_phid) {
$phid = $handles[$subproject_phid]->getPHID();
$rows[] = array(
phutil_escape_html($handles[$phid]->getFullName()),
phutil_render_tag(
'a',
array(
'class' => 'small grey button',
'href' => $handles[$phid]->getURI(),
),
'View Project Profile'),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Name',
'',
));
$table->setColumnClasses(
array(
'pri',
'action right',
));
return $table;
}
}
diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
index a7663517dd..62a1b40395 100644
--- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
+++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
@@ -1,800 +1,802 @@
<?php
/*
* Copyright 2012 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.
*/
/**
* Run pull commands on local working copies to keep them up to date. This
* daemon handles all repository types.
*
* By default, the daemon pulls **every** repository. If you want it to be
* responsible for only some repositories, you can launch it with a list of
* PHIDs or callsigns:
*
* ./phd launch repositorypulllocal -- X Q Z
*
* You can also launch a daemon which is responsible for all //but// one or
* more repositories:
*
* ./phd launch repositorypulllocal -- --not A --not B
*
* If you have a very large number of repositories and some aren't being pulled
* as frequently as you'd like, you can either change the pull frequency of
* the less-important repositories to a larger number (so the daemon will skip
* them more often) or launch one daemon for all the less-important repositories
* and one for the more important repositories (or one for each more important
* repository).
*
* @task pull Pulling Repositories
* @task git Git Implementation
* @task hg Mercurial Implementation
*/
final class PhabricatorRepositoryPullLocalDaemon
extends PhabricatorDaemon {
private static $commitCache = array();
/* -( Pulling Repositories )----------------------------------------------- */
/**
* @task pull
*/
public function run() {
$argv = $this->getArgv();
array_unshift($argv, __CLASS__);
$args = new PhutilArgumentParser($argv);
$args->parse(
array(
array(
'name' => 'no-discovery',
'help' => 'Pull only, without discovering commits.',
),
array(
'name' => 'not',
'param' => 'repository',
+ 'default' => array(),
'repeat' => true,
'help' => 'Do not pull __repository__.',
),
array(
'name' => 'repositories',
+ 'default' => array(),
'wildcard' => true,
'help' => 'Pull specific __repositories__ instead of all.',
),
));
$no_discovery = $args->getArg('no-discovery');
- $repo_names = $args->getArg('repositories', array());
- $exclude_names = $args->getArg('not', array());
+ $repo_names = $args->getArg('repositories');
+ $exclude_names = $args->getArg('not');
// Each repository has an individual pull frequency; after we pull it,
// wait that long to pull it again. When we start up, try to pull everything
// serially.
$retry_after = array();
$min_sleep = 15;
while (true) {
$repositories = $this->loadRepositories($repo_names);
if ($exclude_names) {
$exclude = $this->loadRepositories($exclude_names);
$repositories = array_diff_key($repositories, $exclude);
}
// Shuffle the repositories, then re-key the array since shuffle()
// discards keys. This is mostly for startup, we'll use soft priorities
// later.
shuffle($repositories);
$repositories = mpull($repositories, null, 'getID');
// If any repositories were deleted, remove them from the retry timer map
// so we don't end up with a retry timer that never gets updated and
// causes us to sleep for the minimum amount of time.
$retry_after = array_select_keys(
$retry_after,
array_keys($repositories));
// Assign soft priorities to repositories based on how frequently they
// should pull again.
asort($retry_after);
$repositories = array_select_keys(
$repositories,
array_keys($retry_after)) + $repositories;
foreach ($repositories as $id => $repository) {
$after = idx($retry_after, $id, 0);
if ($after > time()) {
continue;
}
$tracked = $repository->isTracked();
if (!$tracked) {
continue;
}
try {
self::pullRepository($repository);
if (!$no_discovery) {
// TODO: It would be nice to discover only if we pulled something,
// but this isn't totally trivial.
self::discoverRepository($repository);
}
$sleep_for = $repository->getDetail('pull-frequency', $min_sleep);
$retry_after[$id] = time() + $sleep_for;
} catch (Exception $ex) {
$retry_after[$id] = time() + $min_sleep;
phlog($ex);
}
$this->stillWorking();
}
if ($retry_after) {
$sleep_until = max(min($retry_after), time() + $min_sleep);
} else {
$sleep_until = time() + $min_sleep;
}
$this->sleep($sleep_until - time());
}
}
/**
* @task pull
*/
protected function loadRepositories(array $names) {
if (!count($names)) {
return id(new PhabricatorRepository())->loadAll();
} else {
return PhabricatorRepository::loadAllByPHIDOrCallsign($names);
}
}
/**
* @task pull
*/
public static function pullRepository(PhabricatorRepository $repository) {
$vcs = $repository->getVersionControlSystem();
$is_svn = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN);
$is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
$is_hg = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL);
if ($is_svn) {
return;
}
$callsign = $repository->getCallsign();
if (!$is_git && !$is_hg) {
throw new Exception(
"Unknown VCS '{$vcs}' for repository '{$callsign}'!");
}
$local_path = $repository->getDetail('local-path');
if (!$local_path) {
throw new Exception(
"No local path is available for repository '{$callsign}'.");
}
if (!Filesystem::pathExists($local_path)) {
$dirname = dirname($local_path);
if (!Filesystem::pathExists($dirname)) {
echo "Creating new directory '{$dirname}' ".
"for repository '{$callsign}'.\n";
Filesystem::createDirectory($dirname, 0755, $recursive = true);
}
if ($is_git) {
return self::executeGitCreate($repository, $local_path);
} else if ($is_hg) {
return self::executeHgCreate($repository, $local_path);
}
} else {
if ($is_git) {
return self::executeGitUpdate($repository, $local_path);
} else if ($is_hg) {
return self::executeHgUpdate($repository, $local_path);
}
}
}
public static function discoverRepository(PhabricatorRepository $repository) {
$vcs = $repository->getVersionControlSystem();
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
return self::executeGitDiscover($repository);
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
return self::executeSvnDiscover($repository);
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
return self::executeHgDiscover($repository);
default:
throw new Exception("Unknown VCS '{$vcs}'!");
}
}
private static function isKnownCommit(
PhabricatorRepository $repository,
$target) {
if (self::getCache($repository, $target)) {
return true;
}
$commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
'repositoryID = %s AND commitIdentifier = %s',
$repository->getID(),
$target);
if (!$commit) {
return false;
}
self::setCache($repository, $target);
while (count(self::$commitCache) > 2048) {
array_shift(self::$commitCache);
}
return true;
}
private static function recordCommit(
PhabricatorRepository $repository,
$commit_identifier,
$epoch) {
$commit = new PhabricatorRepositoryCommit();
$commit->setRepositoryID($repository->getID());
$commit->setCommitIdentifier($commit_identifier);
$commit->setEpoch($epoch);
try {
$commit->save();
$event = new PhabricatorTimelineEvent(
'cmit',
array(
'id' => $commit->getID(),
));
$event->recordEvent();
self::insertTask($repository, $commit);
queryfx(
$repository->establishConnection('w'),
'INSERT INTO %T (repositoryID, size, lastCommitID, epoch)
VALUES (%d, 1, %d, %d)
ON DUPLICATE KEY UPDATE
size = size + 1,
lastCommitID =
IF(VALUES(epoch) > epoch, VALUES(lastCommitID), lastCommitID),
epoch = IF(VALUES(epoch) > epoch, VALUES(epoch), epoch)',
PhabricatorRepository::TABLE_SUMMARY,
$repository->getID(),
$commit->getID(),
$epoch);
self::setCache($repository, $commit_identifier);
} catch (AphrontQueryDuplicateKeyException $ex) {
// Ignore. This can happen because we discover the same new commit
// more than once when looking at history, or because of races or
// data inconsistency or cosmic radiation; in any case, we're still
// in a good state if we ignore the failure.
self::setCache($repository, $commit_identifier);
}
}
private static function insertTask(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$vcs = $repository->getVersionControlSystem();
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$class = 'PhabricatorRepositoryGitCommitMessageParserWorker';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$class = 'PhabricatorRepositorySvnCommitMessageParserWorker';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$class = 'PhabricatorRepositoryMercurialCommitMessageParserWorker';
break;
default:
throw new Exception("Unknown repository type '{$vcs}'!");
}
$task = new PhabricatorWorkerTask();
$task->setTaskClass($class);
$task->setData(
array(
'commitID' => $commit->getID(),
));
$task->save();
}
private static function setCache(
PhabricatorRepository $repository,
$commit_identifier) {
$key = self::getCacheKey($repository, $commit_identifier);
self::$commitCache[$key] = true;
}
private static function getCache(
PhabricatorRepository $repository,
$commit_identifier) {
$key = self::getCacheKey($repository, $commit_identifier);
return idx(self::$commitCache, $key, false);
}
private static function getCacheKey(
PhabricatorRepository $repository,
$commit_identifier) {
return $repository->getID().':'.$commit_identifier;
}
/* -( Git Implementation )------------------------------------------------- */
/**
* @task git
*/
private static function executeGitCreate(
PhabricatorRepository $repository,
$path) {
$repository->execxRemoteCommand(
'clone --origin origin %s %s',
$repository->getRemoteURI(),
rtrim($path, '/'));
}
/**
* @task git
*/
private static function executeGitUpdate(
PhabricatorRepository $repository,
$path) {
// Run a bunch of sanity checks to detect people checking out repositories
// inside other repositories, making empty directories, pointing the local
// path at some random file or path, etc.
list($err, $stdout) = $repository->execLocalCommand(
'rev-parse --show-toplevel');
if ($err) {
// Try to raise a more tailored error message in the more common case
// of the user creating an empty directory. (We could try to remove it,
// but might not be able to, and it's much simpler to raise a good
// message than try to navigate those waters.)
if (is_dir($path)) {
$files = Filesystem::listDirectory($path, $include_hidden = true);
if (!$files) {
throw new Exception(
"Expected to find a git repository at '{$path}', but there ".
"is an empty directory there. Remove the directory: the daemon ".
"will run 'git clone' for you.");
}
}
throw new Exception(
"Expected to find a git repository at '{$path}', but there is ".
"a non-repository directory (with other stuff in it) there. Move or ".
"remove this directory (or reconfigure the repository to use a ".
"different directory), and then either clone a repository yourself ".
"or let the daemon do it.");
} else {
$repo_path = rtrim($stdout, "\n");
if (empty($repo_path)) {
throw new Exception(
"Expected to find a git repository at '{$path}', but ".
"there was no result from `git rev-parse --show-toplevel`. ".
"Something is misconfigured or broken. The git repository ".
"may be inside a '.git/' directory.");
}
if (!Filesystem::pathsAreEquivalent($repo_path, $path)) {
throw new Exception(
"Expected to find repo at '{$path}', but the actual ".
"git repository root for this directory is '{$repo_path}'. ".
"Something is misconfigured. The repository's 'Local Path' should ".
"be set to some place where the daemon can check out a working ".
"copy, and should not be inside another git repository.");
}
}
// This is a local command, but needs credentials.
$future = $repository->getRemoteCommandFuture('fetch --all --prune');
$future->setCWD($path);
$future->resolvex();
}
/**
* @task git
*/
private static function executeGitDiscover(
PhabricatorRepository $repository) {
list($remotes) = $repository->execxLocalCommand(
'remote show -n origin');
$matches = null;
if (!preg_match('/^\s*Fetch URL:\s*(.*?)\s*$/m', $remotes, $matches)) {
throw new Exception(
"Expected 'Fetch URL' in 'git remote show -n origin'.");
}
self::executeGitverifySameOrigin(
$matches[1],
$repository->getRemoteURI(),
$repository->getLocalPath());
list($stdout) = $repository->execxLocalCommand(
'branch -r --verbose --no-abbrev');
$branches = DiffusionGitBranchQuery::parseGitRemoteBranchOutput(
$stdout,
$only_this_remote = DiffusionBranchInformation::DEFAULT_GIT_REMOTE);
$tracked_something = false;
foreach ($branches as $name => $commit) {
if (!$repository->shouldTrackBranch($name)) {
continue;
}
$tracked_something = true;
if (self::isKnownCommit($repository, $commit)) {
continue;
} else {
self::executeGitDiscoverCommit($repository, $commit);
}
}
if (!$tracked_something) {
$repo_name = $repository->getName();
$repo_callsign = $repository->getCallsign();
throw new Exception(
"Repository r{$repo_callsign} '{$repo_name}' has no tracked branches! ".
"Verify that your branch filtering settings are correct.");
}
}
/**
* @task git
*/
private static function executeGitDiscoverCommit(
PhabricatorRepository $repository,
$commit) {
$discover = array($commit);
$insert = array($commit);
$seen_parent = array();
while (true) {
$target = array_pop($discover);
list($parents) = $repository->execxLocalCommand(
'log -n1 --pretty="%%P" %s',
$target);
$parents = array_filter(explode(' ', trim($parents)));
foreach ($parents as $parent) {
if (isset($seen_parent[$parent])) {
// We end up in a loop here somehow when we parse Arcanist if we
// don't do this. TODO: Figure out why and draw a pretty diagram
// since it's not evident how parsing a DAG with this causes the
// loop to stop terminating.
continue;
}
$seen_parent[$parent] = true;
if (!self::isKnownCommit($repository, $parent)) {
$discover[] = $parent;
$insert[] = $parent;
}
}
if (empty($discover)) {
break;
}
}
while (true) {
$target = array_pop($insert);
list($epoch) = $repository->execxLocalCommand(
'log -n1 --pretty="%%ct" %s',
$target);
$epoch = trim($epoch);
self::recordCommit($repository, $target, $epoch);
if (empty($insert)) {
break;
}
}
}
/**
* @task git
*/
public static function executeGitVerifySameOrigin($remote, $expect, $where) {
$remote_uri = PhabricatorRepository::newPhutilURIFromGitURI($remote);
$expect_uri = PhabricatorRepository::newPhutilURIFromGitURI($expect);
$remote_path = $remote_uri->getPath();
$expect_path = $expect_uri->getPath();
$remote_match = self::executeGitNormalizePath($remote_path);
$expect_match = self::executeGitNormalizePath($expect_path);
if ($remote_match != $expect_match) {
throw new Exception(
"Working copy at '{$where}' has a mismatched origin URL. It has ".
"origin URL '{$remote}' (with remote path '{$remote_path}'), but the ".
"configured URL '{$expect}' (with remote path '{$expect_path}') is ".
"expected. Refusing to proceed because this may indicate that the ".
"working copy is actually some other repository.");
}
}
/**
* @task git
*/
private static function executeGitNormalizePath($path) {
// Strip away trailing "/" and ".git", so similar paths correctly match.
$path = rtrim($path, '/');
$path = preg_replace('/\.git$/', '', $path);
return $path;
}
/* -( Mercurial Implementation )------------------------------------------- */
/**
* @task hg
*/
private static function executeHgCreate(
PhabricatorRepository $repository,
$path) {
$repository->execxRemoteCommand(
'clone %s %s',
$repository->getRemoteURI(),
rtrim($path, '/'));
}
/**
* @task hg
*/
private static function executeHgUpdate(
PhabricatorRepository $repository,
$path) {
// This is a local command, but needs credentials.
$future = $repository->getRemoteCommandFuture('pull -u');
$future->setCWD($path);
try {
$future->resolvex();
} catch (CommandException $ex) {
$err = $ex->getError();
$stdout = $ex->getStdOut();
// NOTE: Between versions 2.1 and 2.1.1, Mercurial changed the behavior
// of "hg pull" to return 1 in case of a successful pull with no changes.
// This behavior has been reverted, but users who updated between Feb 1,
// 2012 and Mar 1, 2012 will have the erroring version. Do a dumb test
// against stdout to check for this possibility.
// See: https://github.com/facebook/phabricator/issues/101/
// NOTE: Mercurial has translated versions, which translate this error
// string. In a translated version, the string will be something else,
// like "aucun changement trouve". There didn't seem to be an easy way
// to handle this (there are hard ways but this is not a common problem
// and only creates log spam, not application failures). Assume English.
// TODO: Remove this once we're far enough in the future that deployment
// of 2.1 is exceedingly rare?
if ($err == 1 && preg_match('/no changes found/', $stdout)) {
return;
} else {
throw $ex;
}
}
}
private static function executeHgDiscover(PhabricatorRepository $repository) {
// NOTE: "--debug" gives us 40-character hashes.
list($stdout) = $repository->execxLocalCommand('--debug branches');
$branches = ArcanistMercurialParser::parseMercurialBranches($stdout);
$got_something = false;
foreach ($branches as $name => $branch) {
$commit = $branch['rev'];
if (self::isKnownCommit($repository, $commit)) {
continue;
} else {
self::executeHgDiscoverCommit($repository, $commit);
$got_something = true;
}
}
return $got_something;
}
private static function executeHgDiscoverCommit(
PhabricatorRepository $repository,
$commit) {
$discover = array($commit);
$insert = array($commit);
$seen_parent = array();
$stream = new PhabricatorMercurialGraphStream($repository);
// For all the new commits at the branch heads, walk backward until we
// find only commits we've aleady seen.
while ($discover) {
$target = array_pop($discover);
$parents = $stream->getParents($target);
foreach ($parents as $parent) {
if (isset($seen_parent[$parent])) {
continue;
}
$seen_parent[$parent] = true;
if (!self::isKnownCommit($repository, $parent)) {
$discover[] = $parent;
$insert[] = $parent;
}
}
}
foreach ($insert as $target) {
$epoch = $stream->getCommitDate($target);
self::recordCommit($repository, $target, $epoch);
}
}
/* -( Subversion Implementation )------------------------------------------ */
private static function executeSvnDiscover(
PhabricatorRepository $repository) {
$uri = self::executeSvnGetBaseSVNLogURI($repository);
list($xml) = $repository->execxRemoteCommand(
'log --xml --quiet --limit 1 %s@HEAD',
$uri);
$results = self::executeSvnParseLogXML($xml);
$commit = head_key($results);
$epoch = head($results);
if (self::isKnownCommit($repository, $commit)) {
return false;
}
self::executeSvnDiscoverCommit($repository, $commit, $epoch);
return true;
}
private static function executeSvnDiscoverCommit(
PhabricatorRepository $repository,
$commit,
$epoch) {
$uri = self::executeSvnGetBaseSVNLogURI($repository);
$discover = array(
$commit => $epoch,
);
$upper_bound = $commit;
$limit = 1;
while ($upper_bound > 1 &&
!self::isKnownCommit($repository, $upper_bound)) {
// Find all the unknown commits on this path. Note that we permit
// importing an SVN subdirectory rather than the entire repository, so
// commits may be nonsequential.
list($err, $xml, $stderr) = $repository->execRemoteCommand(
' log --xml --quiet --limit %d %s@%d',
$limit,
$uri,
$upper_bound - 1);
if ($err) {
if (preg_match('/(path|File) not found/', $stderr)) {
// We've gone all the way back through history and this path was not
// affected by earlier commits.
break;
} else {
throw new Exception("svn log error #{$err}: {$stderr}");
}
}
$discover += self::executeSvnParseLogXML($xml);
$upper_bound = min(array_keys($discover));
// Discover 2, 4, 8, ... 256 logs at a time. This allows us to initially
// import large repositories fairly quickly, while pulling only as much
// data as we need in the common case (when we've already imported the
// repository and are just grabbing one commit at a time).
$limit = min($limit * 2, 256);
}
// NOTE: We do writes only after discovering all the commits so that we're
// never left in a state where we've missed commits -- if the discovery
// script terminates it can always resume and restore the import to a good
// state. This is also why we sort the discovered commits so we can do
// writes forward from the smallest one.
ksort($discover);
foreach ($discover as $commit => $epoch) {
self::recordCommit($repository, $commit, $epoch);
}
}
private static function executeSvnParseLogXML($xml) {
$xml = phutil_utf8ize($xml);
$result = array();
$log = new SimpleXMLElement($xml);
foreach ($log->logentry as $entry) {
$commit = (int)$entry['revision'];
$epoch = (int)strtotime((string)$entry->date[0]);
$result[$commit] = $epoch;
}
return $result;
}
private static function executeSvnGetBaseSVNLogURI(
PhabricatorRepository $repository) {
$uri = $repository->getDetail('remote-uri');
$subpath = $repository->getDetail('svn-subpath');
return $uri.$subpath;
}
}
diff --git a/src/applications/search/engine/PhabricatorJumpNavHandler.php b/src/applications/search/engine/PhabricatorJumpNavHandler.php
index 16468afc10..f992996b89 100644
--- a/src/applications/search/engine/PhabricatorJumpNavHandler.php
+++ b/src/applications/search/engine/PhabricatorJumpNavHandler.php
@@ -1,120 +1,120 @@
<?php
/*
* Copyright 2012 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.
*/
final class PhabricatorJumpNavHandler {
public static function jumpPostResponse($jump) {
$jump = trim($jump);
$help_href = PhabricatorEnv::getDocLink(
'article/Jump_Nav_User_Guide.html');
$patterns = array(
'/^help/i' => 'uri:'.$help_href,
'/^a$/i' => 'uri:/audit/',
'/^f$/i' => 'uri:/feed/',
'/^d$/i' => 'uri:/differential/',
'/^r$/i' => 'uri:/diffusion/',
'/^t$/i' => 'uri:/maniphest/',
'/^p$/i' => 'uri:/project/',
'/^u$/i' => 'uri:/people/',
'/^r([A-Z]+)$/' => 'repository',
'/^r([A-Z]+)(\S+)$/' => 'commit',
'/^d(\d+)$/i' => 'revision',
'/^t(\d+)$/i' => 'task',
'/^p\s+(.+)$/i' => 'project',
'/^u\s+(\S+)$/i' => 'user',
'/^task:\s*(.+)/i' => 'create-task',
'/^(?:s|symbol)\s+(\S+)/i' => 'find-symbol',
);
foreach ($patterns as $pattern => $effect) {
$matches = null;
if (preg_match($pattern, $jump, $matches)) {
if (!strncmp($effect, 'uri:', 4)) {
return id(new AphrontRedirectResponse())
->setURI(substr($effect, 4));
} else {
switch ($effect) {
case 'repository':
return id(new AphrontRedirectResponse())
->setURI('/diffusion/'.$matches[1].'/');
case 'commit':
return id(new AphrontRedirectResponse())
->setURI('/'.$matches[0]);
case 'revision':
return id(new AphrontRedirectResponse())
->setURI('/D'.$matches[1]);
case 'task':
return id(new AphrontRedirectResponse())
->setURI('/T'.$matches[1]);
case 'user':
return id(new AphrontRedirectResponse())
->setURI('/p/'.$matches[1].'/');
case 'project':
$project = static::findCloselyNamedProject($matches[1]);
if ($project) {
return id(new AphrontRedirectResponse())
->setURI('/project/view/'.$project->getID().'/');
} else {
$jump = $matches[1];
}
break;
case 'find-symbol':
return id(new AphrontRedirectResponse())
->setURI('/diffusion/symbol/'.$matches[1].'/?jump=true');
case 'create-task':
return id(new AphrontRedirectResponse())
->setURI('/maniphest/task/create/?title='
.phutil_escape_uri($matches[1]));
default:
throw new Exception("Unknown jump effect '{$effect}'!");
}
}
}
}
return null;
}
private static function findCloselyNamedProject($name) {
$project = id(new PhabricatorProject())->loadOneWhere(
'name = %s',
- name);
+ $name);
if ($project) {
return $project;
} else { // no exact match, try a fuzzy match
$projects = id(new PhabricatorProject())->loadAllWhere(
'name LIKE %~',
$name);
if ($projects) {
$min_name_length = PHP_INT_MAX;
$best_project = null;
foreach ($projects as $project) {
$name_length = strlen($project->getName());
if ($name_length <= $min_name_length) {
$min_name_length = $name_length;
$best_project = $project;
}
}
return $best_project;
} else {
return null;
}
}
}
}
diff --git a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php
index e3fdb6c418..754b9b5609 100644
--- a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php
+++ b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php
@@ -1,128 +1,128 @@
<?php
/*
* Copyright 2012 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.
*/
final class PhabricatorTaskmasterDaemon extends PhabricatorDaemon {
public function run() {
$lease_ownership_name = $this->getLeaseOwnershipName();
$task_table = new PhabricatorWorkerTask();
$taskdata_table = new PhabricatorWorkerTaskData();
$sleep = 0;
do {
$conn_w = $task_table->establishConnection('w');
queryfx(
$conn_w,
'UPDATE %T SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + 15
WHERE leaseOwner IS NULL LIMIT 1',
$task_table->getTableName(),
$lease_ownership_name);
$rows = $conn_w->getAffectedRows();
if (!$rows) {
- $rows = queryfx(
+ queryfx(
$conn_w,
'UPDATE %T SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + 15
WHERE leaseExpires < UNIX_TIMESTAMP() LIMIT 1',
- $task_table->getTableName(),
- $lease_ownership_name);
+ $task_table->getTableName(),
+ $lease_ownership_name);
$rows = $conn_w->getAffectedRows();
}
if ($rows) {
$data = queryfx_all(
$conn_w,
'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime
FROM %T task LEFT JOIN %T taskdata
ON taskdata.id = task.dataID
WHERE leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP()
LIMIT 1',
$task_table->getTableName(),
$taskdata_table->getTableName(),
$lease_ownership_name);
$tasks = $task_table->loadAllFromArray($data);
$tasks = mpull($tasks, null, 'getID');
$task_data = array();
foreach ($data as $row) {
$tasks[$row['id']]->setServerTime($row['_serverTime']);
if ($row['_taskData']) {
$task_data[$row['id']] = json_decode($row['_taskData'], true);
} else {
$task_data[$row['id']] = null;
}
}
foreach ($tasks as $task) {
// TODO: We should detect if we acquired a task with an expired lease
// and log about it / bump up failure count.
// TODO: We should detect if we acquired a task with an excessive
// failure count and fail it permanently.
$data = idx($task_data, $task->getID());
$class = $task->getTaskClass();
try {
if (!class_exists($class) ||
!is_subclass_of($class, 'PhabricatorWorker')) {
throw new Exception(
"Task class '{$class}' does not extend PhabricatorWorker.");
}
$worker = newv($class, array($data));
$lease = $worker->getRequiredLeaseTime();
if ($lease !== null) {
$task->setLeaseDuration($lease);
}
$worker->executeTask();
$task->delete();
if ($data !== null) {
queryfx(
$conn_w,
'DELETE FROM %T WHERE id = %d',
$taskdata_table->getTableName(),
$task->getDataID());
}
} catch (Exception $ex) {
$task->setFailureCount($task->getFailureCount() + 1);
$task->save();
throw $ex;
}
}
$sleep = 0;
} else {
$sleep = min($sleep + 1, 30);
}
$this->sleep($sleep);
} while (true);
}
private function getLeaseOwnershipName() {
static $name = null;
if ($name === null) {
$name = getmypid().':'.time().':'.php_uname('n');
}
return $name;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 13:57 (3 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125292
Default Alt Text
(104 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment