Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F3282036
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
71 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/diffusion/controller/DiffusionCommitBranchesController.php b/src/applications/diffusion/controller/DiffusionCommitBranchesController.php
index afb9c45044..069a353a8f 100644
--- a/src/applications/diffusion/controller/DiffusionCommitBranchesController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitBranchesController.php
@@ -1,41 +1,45 @@
<?php
final class DiffusionCommitBranchesController extends DiffusionController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function willProcessRequest(array $data) {
$data['user'] = $this->getRequest()->getUser();
$this->diffusionRequest = DiffusionRequest::newFromDictionary($data);
}
public function processRequest() {
$request = $this->getDiffusionRequest();
$branches = array();
try {
$branches = $this->callConduitWithDiffusionRequest(
'diffusion.commitbranchesquery',
array('commit' => $request->getCommit()));
} catch (ConduitException $ex) {
if ($ex->getMessage() != 'ERR-UNSUPPORTED-VCS') {
throw $ex;
}
}
$branch_links = array();
foreach ($branches as $branch => $commit) {
$branch_links[] = phutil_tag(
'a',
array(
'href' => $request->generateURI(
array(
'action' => 'browse',
'branch' => $branch,
)),
),
$branch);
}
return id(new AphrontAjaxResponse())
->setContent($branch_links ? implode(', ', $branch_links) : 'None');
}
}
diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php
index 65f5cb10ee..99603a59e9 100644
--- a/src/applications/diffusion/controller/DiffusionCommitController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitController.php
@@ -1,1035 +1,1054 @@
<?php
final class DiffusionCommitController extends DiffusionController {
const CHANGES_LIMIT = 100;
private $auditAuthorityPHIDs;
private $highlightedAudits;
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function willProcessRequest(array $data) {
// This controller doesn't use blob/path stuff, just pass the dictionary
// in directly instead of using the AphrontRequest parsing mechanism.
$data['user'] = $this->getRequest()->getUser();
$drequest = DiffusionRequest::newFromDictionary($data);
$this->diffusionRequest = $drequest;
}
public function processRequest() {
$drequest = $this->getDiffusionRequest();
$request = $this->getRequest();
$user = $request->getUser();
if ($request->getStr('diff')) {
return $this->buildRawDiffResponse($drequest);
}
$callsign = $drequest->getRepository()->getCallsign();
$content = array();
$repository = $drequest->getRepository();
$commit = $drequest->loadCommit();
$crumbs = $this->buildCrumbs(array(
'commit' => true,
));
if (!$commit) {
$exists = $this->callConduitWithDiffusionRequest(
'diffusion.existsquery',
array('commit' => $drequest->getCommit()));
if (!$exists) {
return new Aphront404Response();
}
$error = id(new AphrontErrorView())
->setTitle(pht('Commit Still Parsing'))
->appendChild(
pht(
'Failed to load the commit because the commit has not been '.
'parsed yet.'));
return $this->buildApplicationPage(
array(
$crumbs,
$error,
),
array(
'title' => pht('Commit Still Parsing'),
));
}
$commit_data = $drequest->loadCommitData();
$commit->attachCommitData($commit_data);
$top_anchor = id(new PhabricatorAnchorView())
->setAnchorName('top')
->setNavigationMarker(true);
$audit_requests = id(new PhabricatorAuditQuery())
->withCommitPHIDs(array($commit->getPHID()))
->execute();
$this->auditAuthorityPHIDs =
PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
$is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
$changesets = null;
if ($is_foreign) {
$subpath = $commit_data->getCommitDetail('svn-subpath');
$error_panel = new AphrontErrorView();
$error_panel->setTitle(pht('Commit Not Tracked'));
$error_panel->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$error_panel->appendChild(
pht("This Diffusion repository is configured to track only one ".
"subdirectory of the entire Subversion repository, and this commit ".
"didn't affect the tracked subdirectory ('%s'), so no ".
"information is available.", $subpath));
$content[] = $error_panel;
$content[] = $top_anchor;
} else {
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
$engine->setConfig('viewer', $user);
require_celerity_resource('diffusion-commit-view-css');
require_celerity_resource('phabricator-remarkup-css');
$parents = $this->callConduitWithDiffusionRequest(
'diffusion.commitparentsquery',
array('commit' => $drequest->getCommit()));
$headsup_view = id(new PHUIHeaderView())
->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')));
$headsup_actions = $this->renderHeadsupActionList($commit, $repository);
$commit_properties = $this->loadCommitProperties(
$commit,
$commit_data,
$parents,
$audit_requests);
$property_list = id(new PhabricatorPropertyListView())
->setHasKeyboardShortcuts(true)
->setUser($user)
->setObject($commit);
foreach ($commit_properties as $key => $value) {
$property_list->addProperty($key, $value);
}
$message = $commit_data->getCommitMessage();
$revision = $commit->getCommitIdentifier();
$message = $this->linkBugtraq($message);
$message = $engine->markupText($message);
$property_list->invokeWillRenderEvent();
$property_list->addTextContent(
phutil_tag(
'div',
array(
'class' => 'diffusion-commit-message phabricator-remarkup',
),
$message));
$content[] = $top_anchor;
$content[] = $headsup_view;
$content[] = $headsup_actions;
$content[] = $property_list;
}
$content[] = $this->buildComments($commit);
$hard_limit = 1000;
$change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
$drequest);
$change_query->setLimit($hard_limit + 1);
$changes = $change_query->loadChanges();
$was_limited = (count($changes) > $hard_limit);
if ($was_limited) {
$changes = array_slice($changes, 0, $hard_limit);
}
$content[] = $this->buildMergesTable($commit);
// TODO: This is silly, but the logic to figure out which audits are
// highlighted currently lives in PhabricatorAuditListView. Refactor this
// to be less goofy.
$highlighted_audits = id(new PhabricatorAuditListView())
->setAudits($audit_requests)
->setAuthorityPHIDs($this->auditAuthorityPHIDs)
->setUser($user)
->setCommits(array($commit->getPHID() => $commit))
->getHighlightedAudits();
$owners_paths = array();
if ($highlighted_audits) {
$packages = id(new PhabricatorOwnersPackage())->loadAllWhere(
'phid IN (%Ls)',
mpull($highlighted_audits, 'getAuditorPHID'));
if ($packages) {
$owners_paths = id(new PhabricatorOwnersPath())->loadAllWhere(
'repositoryPHID = %s AND packageID IN (%Ld)',
$repository->getPHID(),
mpull($packages, 'getID'));
}
}
$change_table = new DiffusionCommitChangeTableView();
$change_table->setDiffusionRequest($drequest);
$change_table->setPathChanges($changes);
$change_table->setOwnersPaths($owners_paths);
$count = count($changes);
$bad_commit = null;
if ($count == 0) {
$bad_commit = queryfx_one(
id(new PhabricatorRepository())->establishConnection('r'),
'SELECT * FROM %T WHERE fullCommitName = %s',
PhabricatorRepository::TABLE_BADCOMMIT,
'r'.$callsign.$commit->getCommitIdentifier());
}
if ($bad_commit) {
$error_panel = new AphrontErrorView();
$error_panel->setTitle(pht('Bad Commit'));
$error_panel->appendChild($bad_commit['description']);
$content[] = $error_panel;
} else if ($is_foreign) {
// Don't render anything else.
} else if (!count($changes)) {
$no_changes = new AphrontErrorView();
$no_changes->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$no_changes->setTitle(pht('Not Yet Parsed'));
// TODO: This can also happen with weird SVN changes that don't do
// anything (or only alter properties?), although the real no-changes case
// is extremely rare and might be impossible to produce organically. We
// should probably write some kind of "Nothing Happened!" change into the
// DB once we parse these changes so we can distinguish between
// "not parsed yet" and "no changes".
$no_changes->appendChild(
pht("This commit hasn't been fully parsed yet (or doesn't affect any ".
"paths)."));
$content[] = $no_changes;
} else if ($was_limited) {
$huge_commit = new AphrontErrorView();
$huge_commit->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$huge_commit->setTitle(pht('Enormous Commit'));
$huge_commit->appendChild(
pht(
'This commit is enormous, and affects more than %d files. '.
'Changes are not shown.',
$hard_limit));
$content[] = $huge_commit;
} else {
// The user has clicked "Show All Changes", and we should show all the
// changes inline even if there are more than the soft limit.
$show_all_details = $request->getBool('show_all');
$change_panel = new AphrontPanelView();
$change_panel->setHeader("Changes (".number_format($count).")");
$change_panel->setID('toc');
if ($count > self::CHANGES_LIMIT && !$show_all_details) {
$show_all_button = phutil_tag(
'a',
array(
'class' => 'button green',
'href' => '?show_all=true',
),
pht('Show All Changes'));
$warning_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle('Very Large Commit')
->appendChild(phutil_tag(
'p',
array(),
pht("This commit is very large. Load each file individually.")));
$change_panel->appendChild($warning_view);
$change_panel->addButton($show_all_button);
}
$change_panel->appendChild($change_table);
$change_panel->setNoBackground();
$content[] = $change_panel;
$changesets = DiffusionPathChange::convertToDifferentialChangesets(
$changes);
$vcs = $repository->getVersionControlSystem();
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$vcs_supports_directory_changes = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$vcs_supports_directory_changes = false;
break;
default:
throw new Exception("Unknown VCS.");
}
$references = array();
foreach ($changesets as $key => $changeset) {
$file_type = $changeset->getFileType();
if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
if (!$vcs_supports_directory_changes) {
unset($changesets[$key]);
continue;
}
}
$references[$key] = $drequest->generateURI(
array(
'action' => 'rendering-ref',
'path' => $changeset->getFilename(),
));
}
// TODO: Some parts of the views still rely on properties of the
// DifferentialChangeset. Make the objects ephemeral to make sure we don't
// accidentally save them, and then set their ID to the appropriate ID for
// this application (the path IDs).
$path_ids = array_flip(mpull($changes, 'getPath'));
foreach ($changesets as $changeset) {
$changeset->makeEphemeral();
$changeset->setID($path_ids[$changeset->getFilename()]);
}
if ($count <= self::CHANGES_LIMIT || $show_all_details) {
$visible_changesets = $changesets;
} else {
$visible_changesets = array();
$inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere(
'commitPHID = %s AND (auditCommentID IS NOT NULL OR authorPHID = %s)',
$commit->getPHID(),
$user->getPHID());
$path_ids = mpull($inlines, null, 'getPathID');
foreach ($changesets as $key => $changeset) {
if (array_key_exists($changeset->getID(), $path_ids)) {
$visible_changesets[$key] = $changeset;
}
}
}
$change_list_title = DiffusionView::nameCommit(
$repository,
$commit->getCommitIdentifier());
$change_list = new DifferentialChangesetListView();
$change_list->setTitle($change_list_title);
$change_list->setChangesets($changesets);
$change_list->setVisibleChangesets($visible_changesets);
$change_list->setRenderingReferences($references);
$change_list->setRenderURI('/diffusion/'.$callsign.'/diff/');
$change_list->setRepository($repository);
$change_list->setUser($user);
// pick the first branch for "Browse in Diffusion" View Option
$branches = $commit_data->getCommitDetail('seenOnBranches', array());
$first_branch = reset($branches);
$change_list->setBranch($first_branch);
$change_list->setStandaloneURI(
'/diffusion/'.$callsign.'/diff/');
$change_list->setRawFileURIs(
// TODO: Implement this, somewhat tricky if there's an octopus merge
// or whatever?
null,
'/diffusion/'.$callsign.'/diff/?view=r');
$change_list->setInlineCommentControllerURI(
'/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/');
$change_references = array();
foreach ($changesets as $key => $changeset) {
$change_references[$changeset->getID()] = $references[$key];
}
$change_table->setRenderingReferences($change_references);
$content[] = $change_list->render();
}
$content[] = $this->renderAddCommentPanel($commit, $audit_requests);
$commit_id = 'r'.$callsign.$commit->getCommitIdentifier();
$short_name = DiffusionView::nameCommit(
$repository,
$commit->getCommitIdentifier());
$prefs = $user->loadPreferences();
$pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
$pref_collapse = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED;
$show_filetree = $prefs->getPreference($pref_filetree);
$collapsed = $prefs->getPreference($pref_collapse);
if ($changesets && $show_filetree) {
$nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
->setAnchorName('top')
->setTitle($short_name)
->setBaseURI(new PhutilURI('/'.$commit_id))
->build($changesets)
->setCrumbs($crumbs)
->setCollapsed((bool)$collapsed)
->appendChild($content);
$content = $nav;
} else {
$content = array($crumbs, $content);
}
return $this->buildApplicationPage(
$content,
array(
'title' => $commit_id,
'pageObjects' => array($commit->getPHID()),
));
}
private function loadCommitProperties(
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $data,
array $parents,
array $audit_requests) {
assert_instances_of($parents, 'PhabricatorRepositoryCommit');
$user = $this->getRequest()->getUser();
$commit_phid = $commit->getPHID();
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($commit_phid))
->withEdgeTypes(array(
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK,
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT,
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV,
));
$edges = $edge_query->execute();
$task_phids = array_keys(
$edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK]);
$proj_phids = array_keys(
$edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT]);
$revision_phid = key(
$edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV]);
$phids = $edge_query->getDestinationPHIDs(array($commit_phid));
if ($data->getCommitDetail('authorPHID')) {
$phids[] = $data->getCommitDetail('authorPHID');
}
if ($data->getCommitDetail('reviewerPHID')) {
$phids[] = $data->getCommitDetail('reviewerPHID');
}
if ($data->getCommitDetail('committerPHID')) {
$phids[] = $data->getCommitDetail('committerPHID');
}
if ($parents) {
foreach ($parents as $parent) {
$phids[] = $parent->getPHID();
}
}
$handles = array();
if ($phids) {
$handles = $this->loadViewerHandles($phids);
}
$props = array();
if ($commit->getAuditStatus()) {
$status = PhabricatorAuditCommitStatusConstants::getStatusName(
$commit->getAuditStatus());
$tag = id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_STATE)
->setName($status);
switch ($commit->getAuditStatus()) {
case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT:
$tag->setBackgroundColor(PhabricatorTagView::COLOR_ORANGE);
break;
case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED:
$tag->setBackgroundColor(PhabricatorTagView::COLOR_RED);
break;
case PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED:
$tag->setBackgroundColor(PhabricatorTagView::COLOR_BLUE);
break;
case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED:
$tag->setBackgroundColor(PhabricatorTagView::COLOR_GREEN);
break;
}
$props['Status'] = $tag;
}
if ($audit_requests) {
$props['Auditors'] = $this->renderAuditStatusView($audit_requests);
}
$props['Committed'] = phabricator_datetime($commit->getEpoch(), $user);
$author_phid = $data->getCommitDetail('authorPHID');
if ($data->getCommitDetail('authorPHID')) {
$props['Author'] = $handles[$author_phid]->renderLink();
} else {
$props['Author'] = $data->getAuthorName();
}
$reviewer_phid = $data->getCommitDetail('reviewerPHID');
if ($reviewer_phid) {
$props['Reviewer'] = $handles[$reviewer_phid]->renderLink();
}
$committer = $data->getCommitDetail('committer');
if ($committer) {
$committer_phid = $data->getCommitDetail('committerPHID');
if ($data->getCommitDetail('committerPHID')) {
$props['Committer'] = $handles[$committer_phid]->renderLink();
} else {
$props['Committer'] = $committer;
}
}
if ($revision_phid) {
$props['Differential Revision'] = $handles[$revision_phid]->renderLink();
}
if ($parents) {
$parent_links = array();
foreach ($parents as $parent) {
$parent_links[] = $handles[$parent->getPHID()]->renderLink();
}
$props['Parents'] = phutil_implode_html(" \xC2\xB7 ", $parent_links);
}
$request = $this->getDiffusionRequest();
$props['Branches'] = phutil_tag(
'span',
array(
'id' => 'commit-branches',
),
pht('Unknown'));
$props['Tags'] = phutil_tag(
'span',
array(
'id' => 'commit-tags',
),
pht('Unknown'));
$callsign = $request->getRepository()->getCallsign();
$root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier();
Javelin::initBehavior(
'diffusion-commit-branches',
array(
$root.'/branches/' => 'commit-branches',
$root.'/tags/' => 'commit-tags',
));
$refs = $this->buildRefs($request);
if ($refs) {
$props['References'] = $refs;
}
if ($task_phids) {
$task_list = array();
foreach ($task_phids as $phid) {
$task_list[] = $handles[$phid]->renderLink();
}
$task_list = phutil_implode_html(phutil_tag('br'), $task_list);
$props['Tasks'] = $task_list;
}
if ($proj_phids) {
$proj_list = array();
foreach ($proj_phids as $phid) {
$proj_list[] = $handles[$phid]->renderLink();
}
$proj_list = phutil_implode_html(phutil_tag('br'), $proj_list);
$props['Projects'] = $proj_list;
}
return $props;
}
private function buildComments(PhabricatorRepositoryCommit $commit) {
$user = $this->getRequest()->getUser();
$comments = id(new PhabricatorAuditComment())->loadAllWhere(
'targetPHID = %s ORDER BY dateCreated ASC',
$commit->getPHID());
$inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere(
'commitPHID = %s AND auditCommentID IS NOT NULL',
$commit->getPHID());
$path_ids = mpull($inlines, 'getPathID');
$path_map = array();
if ($path_ids) {
$path_map = id(new DiffusionPathQuery())
->withPathIDs($path_ids)
->execute();
$path_map = ipull($path_map, 'path', 'id');
}
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
foreach ($comments as $comment) {
$engine->addObject(
$comment,
PhabricatorAuditComment::MARKUP_FIELD_BODY);
}
foreach ($inlines as $inline) {
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
}
$engine->process();
$view = new DiffusionCommentListView();
$view->setMarkupEngine($engine);
$view->setUser($user);
$view->setComments($comments);
$view->setInlineComments($inlines);
$view->setPathMap($path_map);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
return $view;
}
private function renderAddCommentPanel(
PhabricatorRepositoryCommit $commit,
array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
- $user = $this->getRequest()->getUser();
+
+ $request = $this->getRequest();
+ $user = $request->getUser();
+
+ if (!$user->isLoggedIn()) {
+ return id(new PhabricatorApplicationTransactionCommentView())
+ ->setUser($user)
+ ->setRequestURI($request->getRequestURI());
+ }
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$pane_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'differential-keyboard-navigation',
array(
'haunt' => $pane_id,
));
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
'diffusion-audit-'.$commit->getID());
if ($draft) {
$draft = $draft->getDraft();
} else {
$draft = null;
}
$actions = $this->getAuditActions($commit, $audit_requests);
$form = id(new AphrontFormView())
->setUser($user)
->setShaded(true)
->setAction('/audit/addcomment/')
->addHiddenInput('commit', $commit->getPHID())
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Action'))
->setName('action')
->setID('audit-action')
->setOptions($actions))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Add Auditors'))
->setName('auditors')
->setControlID('add-auditors')
->setControlStyle('display: none')
->setID('add-auditors-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Add CCs'))
->setName('ccs')
->setControlID('add-ccs')
->setControlStyle('display: none')
->setID('add-ccs-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new PhabricatorRemarkupControl())
->setLabel(pht('Comments'))
->setName('content')
->setValue($draft)
->setID('audit-content')
->setUser($user))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($is_serious ? pht('Submit') : pht('Cook the Books')));
$header = new PHUIHeaderView();
$header->setHeader(
$is_serious ? pht('Audit Commit') : pht('Creative Accounting'));
require_celerity_resource('phabricator-transaction-view-css');
Javelin::initBehavior(
'differential-add-reviewers-and-ccs',
array(
'dynamic' => array(
'add-auditors-tokenizer' => array(
'actions' => array('add_auditors' => 1),
'src' => '/typeahead/common/users/',
'row' => 'add-auditors',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => pht('Type a user name...'),
),
'add-ccs-tokenizer' => array(
'actions' => array('add_ccs' => 1),
'src' => '/typeahead/common/mailable/',
'row' => 'add-ccs',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => pht('Type a user or mailing list...'),
),
),
'select' => 'audit-action',
));
Javelin::initBehavior('differential-feedback-preview', array(
'uri' => '/audit/preview/'.$commit->getID().'/',
'preview' => 'audit-preview',
'content' => 'audit-content',
'action' => 'audit-action',
'previewTokenizers' => array(
'auditors' => 'add-auditors-tokenizer',
'ccs' => 'add-ccs-tokenizer',
),
'inline' => 'inline-comment-preview',
'inlineuri' => '/diffusion/inline/preview/'.$commit->getPHID().'/',
));
$preview_panel = hsprintf(
'<div class="aphront-panel-preview aphront-panel-flush">
<div id="audit-preview">
<div class="aphront-panel-preview-loading-text">
Loading preview...
</div>
</div>
<div id="inline-comment-preview">
</div>
</div>');
// TODO: This is pretty awkward, unify the CSS between Diffusion and
// Differential better.
require_celerity_resource('differential-core-view-css');
return phutil_tag(
'div',
array(
'id' => $pane_id,
),
hsprintf(
'<div class="differential-add-comment-panel">%s%s%s%s</div>',
id(new PhabricatorAnchorView())
->setAnchorName('comment')
->setNavigationMarker(true)
->render(),
$header,
$form,
$preview_panel));
}
/**
* Return a map of available audit actions for rendering into a <select />.
* This shows the user valid actions, and does not show nonsense/invalid
* actions (like closing an already-closed commit, or resigning from a commit
* you have no association with).
*/
private function getAuditActions(
PhabricatorRepositoryCommit $commit,
array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$user = $this->getRequest()->getUser();
$user_is_author = ($commit->getAuthorPHID() == $user->getPHID());
$user_request = null;
foreach ($audit_requests as $audit_request) {
if ($audit_request->getAuditorPHID() == $user->getPHID()) {
$user_request = $audit_request;
break;
}
}
$actions = array();
$actions[PhabricatorAuditActionConstants::COMMENT] = true;
$actions[PhabricatorAuditActionConstants::ADD_CCS] = true;
$actions[PhabricatorAuditActionConstants::ADD_AUDITORS] = true;
// We allow you to accept your own commits. A use case here is that you
// notice an issue with your own commit and "Raise Concern" as an indicator
// to other auditors that you're on top of the issue, then later resolve it
// and "Accept". You can not accept on behalf of projects or packages,
// however.
$actions[PhabricatorAuditActionConstants::ACCEPT] = true;
$actions[PhabricatorAuditActionConstants::CONCERN] = true;
// To resign, a user must have authority on some request and not be the
// commit's author.
if (!$user_is_author) {
$may_resign = false;
$authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
foreach ($audit_requests as $request) {
if (empty($authority_map[$request->getAuditorPHID()])) {
continue;
}
$may_resign = true;
break;
}
// If the user has already resigned, don't show "Resign...".
$status_resigned = PhabricatorAuditStatusConstants::RESIGNED;
if ($user_request) {
if ($user_request->getAuditStatus() == $status_resigned) {
$may_resign = false;
}
}
if ($may_resign) {
$actions[PhabricatorAuditActionConstants::RESIGN] = true;
}
}
$status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
$concern_raised = ($commit->getAuditStatus() == $status_concern);
$can_close_option = PhabricatorEnv::getEnvConfig(
'audit.can-author-close-audit');
if ($can_close_option && $user_is_author && $concern_raised) {
$actions[PhabricatorAuditActionConstants::CLOSE] = true;
}
foreach ($actions as $constant => $ignored) {
$actions[$constant] =
PhabricatorAuditActionConstants::getActionName($constant);
}
return $actions;
}
private function buildMergesTable(PhabricatorRepositoryCommit $commit) {
$drequest = $this->getDiffusionRequest();
$limit = 50;
$merges = array();
try {
$merges = $this->callConduitWithDiffusionRequest(
'diffusion.mergedcommitsquery',
array(
'commit' => $drequest->getCommit(),
'limit' => $limit + 1));
} catch (ConduitException $ex) {
if ($ex->getMessage() != 'ERR-UNSUPPORTED-VCS') {
throw $ex;
}
}
if (!$merges) {
return null;
}
$caption = null;
if (count($merges) > $limit) {
$merges = array_slice($merges, 0, $limit);
$caption =
"This commit merges more than {$limit} changes. Only the first ".
"{$limit} are shown.";
}
$history_table = new DiffusionHistoryTableView();
$history_table->setUser($this->getRequest()->getUser());
$history_table->setDiffusionRequest($drequest);
$history_table->setHistory($merges);
$history_table->loadRevisions();
$phids = $history_table->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$history_table->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader(pht('Merged Changes'));
$panel->setCaption($caption);
$panel->appendChild($history_table);
$panel->setNoBackground();
return $panel;
}
private function renderHeadsupActionList(
PhabricatorRepositoryCommit $commit,
PhabricatorRepository $repository) {
$request = $this->getRequest();
$user = $request->getUser();
$actions = id(new PhabricatorActionListView())
->setUser($user)
->setObject($commit)
->setObjectURI($request->getRequestURI());
- // TODO -- integrate permissions into whether or not this action is shown
- $uri = '/diffusion/'.$repository->getCallSign().'/commit/'.
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $user,
+ $commit,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $uri = '/diffusion/'.$repository->getCallsign().'/commit/'.
$commit->getCommitIdentifier().'/edit/';
$action = id(new PhabricatorActionView())
->setName(pht('Edit Commit'))
->setHref($uri)
- ->setIcon('edit');
+ ->setIcon('edit')
+ ->setDisabled(!$can_edit)
+ ->setWorkflow(!$can_edit);
$actions->addAction($action);
require_celerity_resource('phabricator-object-selector-css');
require_celerity_resource('javelin-behavior-phabricator-object-selector');
$maniphest = 'PhabricatorApplicationManiphest';
if (PhabricatorApplication::isClassInstalled($maniphest)) {
$action = id(new PhabricatorActionView())
->setName(pht('Edit Maniphest Tasks'))
->setIcon('attach')
->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/')
- ->setWorkflow(true);
+ ->setWorkflow(true)
+ ->setDisabled(!$can_edit);
$actions->addAction($action);
}
$action = id(new PhabricatorActionView())
->setName(pht('Download Raw Diff'))
->setHref($request->getRequestURI()->alter('diff', true))
->setIcon('download');
$actions->addAction($action);
return $actions;
}
private function buildRefs(DiffusionRequest $request) {
// this is git-only, so save a conduit round trip and just get out of
// here if the repository isn't git
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
$repository = $request->getRepository();
if ($repository->getVersionControlSystem() != $type_git) {
return null;
}
$results = $this->callConduitWithDiffusionRequest(
'diffusion.refsquery',
array('commit' => $request->getCommit()));
$ref_links = array();
foreach ($results as $ref_data) {
$ref_links[] = phutil_tag('a',
array('href' => $ref_data['href']),
$ref_data['ref']);
}
return phutil_implode_html(', ', $ref_links);
}
private function buildRawDiffResponse(DiffusionRequest $drequest) {
$raw_diff = $this->callConduitWithDiffusionRequest(
'diffusion.rawdiffquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath()));
$file = PhabricatorFile::buildFromFileDataOrHash(
$raw_diff,
array(
'name' => $drequest->getCommit().'.diff',
));
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
}
private function renderAuditStatusView(array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$phids = mpull($audit_requests, 'getAuditorPHID');
$this->loadHandles($phids);
$authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
$view = new PHUIStatusListView();
foreach ($audit_requests as $request) {
$item = new PHUIStatusItemView();
switch ($request->getAuditStatus()) {
case PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED:
$item->setIcon('open-blue', pht('Commented'));
break;
case PhabricatorAuditStatusConstants::AUDIT_REQUIRED:
$item->setIcon('warning-blue', pht('Audit Required'));
break;
case PhabricatorAuditStatusConstants::CONCERNED:
$item->setIcon('reject-red', pht('Concern Raised'));
break;
case PhabricatorAuditStatusConstants::ACCEPTED:
$item->setIcon('accept-green', pht('Accepted'));
break;
case PhabricatorAuditStatusConstants::AUDIT_REQUESTED:
$item->setIcon('warning-dark', pht('Audit Requested'));
break;
case PhabricatorAuditStatusConstants::RESIGNED:
$item->setIcon('open-dark', pht('Accepted'));
break;
case PhabricatorAuditStatusConstants::CLOSED:
$item->setIcon('accept-blue', pht('Accepted'));
break;
case PhabricatorAuditStatusConstants::CC:
$item->setIcon('info-dark', pht('Subscribed'));
break;
}
$note = array();
foreach ($request->getAuditReasons() as $reason) {
$note[] = phutil_tag('div', array(), $reason);
}
$item->setNote($note);
$auditor_phid = $request->getAuditorPHID();
$target = $this->getHandle($auditor_phid)->renderLink();
$item->setTarget($target);
if (isset($authority_map[$auditor_phid])) {
$item->setHighlighted(true);
}
$view->addItem($item);
}
return $view;
}
private function linkBugtraq($corpus) {
$url = PhabricatorEnv::getEnvConfig('bugtraq.url');
if (!strlen($url)) {
return $corpus;
}
$regexes = PhabricatorEnv::getEnvConfig('bugtraq.logregex');
if (!$regexes) {
return $corpus;
}
$parser = id(new PhutilBugtraqParser())
->setBugtraqPattern("[[ {$url} | %BUGID% ]]")
->setBugtraqCaptureExpression(array_shift($regexes));
$select = array_shift($regexes);
if ($select) {
$parser->setBugtraqSelectExpression($select);
}
return $parser->processCorpus($corpus);
}
}
diff --git a/src/applications/diffusion/controller/DiffusionCommitTagsController.php b/src/applications/diffusion/controller/DiffusionCommitTagsController.php
index cac70f616e..986390c79e 100644
--- a/src/applications/diffusion/controller/DiffusionCommitTagsController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitTagsController.php
@@ -1,60 +1,64 @@
<?php
final class DiffusionCommitTagsController extends DiffusionController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function willProcessRequest(array $data) {
$data['user'] = $this->getRequest()->getUser();
$this->diffusionRequest = DiffusionRequest::newFromDictionary($data);
}
public function processRequest() {
$request = $this->getDiffusionRequest();
$tag_limit = 10;
$tags = array();
try {
$tags = DiffusionRepositoryTag::newFromConduit(
$this->callConduitWithDiffusionRequest(
'diffusion.tagsquery',
array(
'commit' => $request->getCommit(),
'limit' => $tag_limit + 1)));
} catch (ConduitException $ex) {
if ($ex->getMessage() != 'ERR-UNSUPPORTED-VCS') {
throw $ex;
}
}
$has_more_tags = (count($tags) > $tag_limit);
$tags = array_slice($tags, 0, $tag_limit);
$tag_links = array();
foreach ($tags as $tag) {
$tag_links[] = phutil_tag(
'a',
array(
'href' => $request->generateURI(
array(
'action' => 'browse',
'commit' => $tag->getName(),
)),
),
$tag->getName());
}
if ($has_more_tags) {
$tag_links[] = phutil_tag(
'a',
array(
'href' => $request->generateURI(
array(
'action' => 'tags',
)),
),
pht("More Tags\xE2\x80\xA6"));
}
return id(new AphrontAjaxResponse())
->setContent($tag_links ? implode(', ', $tag_links) : pht('None'));
}
}
diff --git a/src/applications/diffusion/controller/DiffusionExternalController.php b/src/applications/diffusion/controller/DiffusionExternalController.php
index 4ed7c30361..50a8e2aff1 100644
--- a/src/applications/diffusion/controller/DiffusionExternalController.php
+++ b/src/applications/diffusion/controller/DiffusionExternalController.php
@@ -1,139 +1,143 @@
<?php
final class DiffusionExternalController extends DiffusionController {
public function willProcessRequest(array $data) {
// Don't build a DiffusionRequest.
}
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function processRequest() {
$request = $this->getRequest();
$uri = $request->getStr('uri');
$id = $request->getStr('id');
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($request->getUser())
->execute();
if ($uri) {
$uri_path = id(new PhutilURI($uri))->getPath();
$matches = array();
// Try to figure out which tracked repository this external lives in by
// comparing repository metadata. We look for an exact match, but accept
// a partial match.
foreach ($repositories as $key => $repository) {
$remote_uri = new PhutilURI($repository->getRemoteURI());
if ($remote_uri->getPath() == $uri_path) {
$matches[$key] = 1;
}
if ($repository->getPublicRemoteURI() == $uri) {
$matches[$key] = 2;
}
if ($repository->getRemoteURI() == $uri) {
$matches[$key] = 3;
}
}
arsort($matches);
$best_match = head_key($matches);
if ($best_match) {
$repository = $repositories[$best_match];
$redirect = DiffusionRequest::generateDiffusionURI(
array(
'action' => 'browse',
'callsign' => $repository->getCallsign(),
'branch' => $repository->getDefaultBranch(),
'commit' => $id,
));
return id(new AphrontRedirectResponse())->setURI($redirect);
}
}
// TODO: This is a rare query but does a table scan, add a key?
$commits = id(new PhabricatorRepositoryCommit())->loadAllWhere(
'commitIdentifier = %s',
$id);
if (empty($commits)) {
$desc = null;
if ($uri) {
$desc = $uri.', at ';
}
$desc .= $id;
$content = id(new AphrontErrorView())
->setTitle(pht('Unknown External'))
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->appendChild(phutil_tag(
'p',
array(),
pht("This external (%s) does not appear in any tracked ".
"repository. It may exist in an untracked repository that ".
"Diffusion does not know about.", $desc)));
} else if (count($commits) == 1) {
$commit = head($commits);
$repo = $repositories[$commit->getRepositoryID()];
$redirect = DiffusionRequest::generateDiffusionURI(
array(
'action' => 'browse',
'callsign' => $repo->getCallsign(),
'branch' => $repo->getDefaultBranch(),
'commit' => $commit->getCommitIdentifier(),
));
return id(new AphrontRedirectResponse())->setURI($redirect);
} else {
$rows = array();
foreach ($commits as $commit) {
$repo = $repositories[$commit->getRepositoryID()];
$href = DiffusionRequest::generateDiffusionURI(
array(
'action' => 'browse',
'callsign' => $repo->getCallsign(),
'branch' => $repo->getDefaultBranch(),
'commit' => $commit->getCommitIdentifier(),
));
$rows[] = array(
phutil_tag(
'a',
array(
'href' => $href,
),
'r'.$repo->getCallsign().$commit->getCommitIdentifier()),
$commit->loadCommitData()->getSummary(),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Commit'),
pht('Description'),
));
$table->setColumnClasses(
array(
'pri',
'wide',
));
$content = new AphrontPanelView();
$content->setHeader(pht('Multiple Matching Commits'));
$content->setCaption(
pht('This external reference matches multiple known commits.'));
$content->appendChild($table);
}
return $this->buildApplicationPage(
$content,
array(
'title' => pht('Unresolvable External'),
'device' => true,
));
}
}
diff --git a/src/applications/diffusion/controller/DiffusionLastModifiedController.php b/src/applications/diffusion/controller/DiffusionLastModifiedController.php
index d8f96a6758..90f1c3839c 100644
--- a/src/applications/diffusion/controller/DiffusionLastModifiedController.php
+++ b/src/applications/diffusion/controller/DiffusionLastModifiedController.php
@@ -1,51 +1,55 @@
<?php
final class DiffusionLastModifiedController extends DiffusionController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function processRequest() {
$drequest = $this->getDiffusionRequest();
$request = $this->getRequest();
$commit = null;
$commit_data = null;
$conduit_result = $this->callConduitWithDiffusionRequest(
'diffusion.lastmodifiedquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath()
));
$c_dict = $conduit_result['commit'];
if ($c_dict) {
$commit = PhabricatorRepositoryCommit::newFromDictionary($c_dict);
}
$c_d_dict = $conduit_result['commitData'];
if ($c_d_dict) {
$commit_data =
PhabricatorRepositoryCommitData::newFromDictionary($c_d_dict);
}
$phids = array();
if ($commit_data) {
if ($commit_data->getCommitDetail('authorPHID')) {
$phids[$commit_data->getCommitDetail('authorPHID')] = true;
}
if ($commit_data->getCommitDetail('committerPHID')) {
$phids[$commit_data->getCommitDetail('committerPHID')] = true;
}
}
$phids = array_keys($phids);
$handles = $this->loadViewerHandles($phids);
$view = new DiffusionBrowseTableView();
$view->setUser($request->getUser());
$output = $view->renderLastModifiedColumns(
$drequest,
$handles,
$commit,
$commit_data);
return id(new AphrontAjaxResponse())
->setContent($output);
}
}
diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php
index 99d73dd08e..ae7b4b8eea 100644
--- a/src/applications/diffusion/request/DiffusionRequest.php
+++ b/src/applications/diffusion/request/DiffusionRequest.php
@@ -1,663 +1,666 @@
<?php
/**
* Contains logic to parse Diffusion requests, which have a complicated URI
* structure.
*
*
* @task new Creating Requests
* @task uri Managing Diffusion URIs
*
* @group diffusion
*/
abstract class DiffusionRequest {
protected $callsign;
protected $path;
protected $line;
protected $symbolicCommit;
protected $commit;
protected $commitType = 'commit';
protected $tagContent;
protected $branch;
protected $lint;
protected $repository;
protected $repositoryCommit;
protected $repositoryCommitData;
protected $stableCommitName;
protected $arcanistProjects;
private $initFromConduit = true;
private $user;
abstract protected function getSupportsBranches();
abstract protected function didInitialize();
/* -( Creating Requests )-------------------------------------------------- */
/**
* Create a new synthetic request from a parameter dictionary. If you need
* a @{class:DiffusionRequest} object in order to issue a DiffusionQuery, you
* can use this method to build one.
*
* Parameters are:
*
* - `callsign` Repository callsign. Provide this or `repository`.
* - `user` Viewing user. Required if `callsign` is provided.
* - `repository` Repository object. Provide this or `callsign`.
* - `branch` Optional, branch name.
* - `path` Optional, file path.
* - `commit` Optional, commit identifier.
* - `line` Optional, line range.
*
* @param map See documentation.
* @return DiffusionRequest New request object.
* @task new
*/
final public static function newFromDictionary(array $data) {
if (isset($data['repository']) && isset($data['callsign'])) {
throw new Exception(
"Specify 'repository' or 'callsign', but not both.");
} else if (!isset($data['repository']) && !isset($data['callsign'])) {
throw new Exception(
"One of 'repository' and 'callsign' is required.");
} else if (isset($data['callsign']) && empty($data['user'])) {
throw new Exception(
"Parameter 'user' is required if 'callsign' is provided.");
}
if (isset($data['repository'])) {
$object = self::newFromRepository($data['repository']);
} else {
$object = self::newFromCallsign($data['callsign'], $data['user']);
}
$object->initializeFromDictionary($data);
return $object;
}
/**
* Create a new request from an Aphront request dictionary. This is an
* internal method that you generally should not call directly; instead,
* call @{method:newFromDictionary}.
*
* @param map Map of Aphront request data.
* @return DiffusionRequest New request object.
* @task new
*/
final public static function newFromAphrontRequestDictionary(
array $data,
AphrontRequest $request) {
$callsign = phutil_unescape_uri_path_component(idx($data, 'callsign'));
$object = self::newFromCallsign($callsign, $request->getUser());
$use_branches = $object->getSupportsBranches();
$parsed = self::parseRequestBlob(idx($data, 'dblob'), $use_branches);
$object->setUser($request->getUser());
$object->initializeFromDictionary($parsed);
$object->lint = $request->getStr('lint');
return $object;
}
/**
* Internal.
*
* @task new
*/
final private function __construct() {
// <private>
}
/**
* Internal. Use @{method:newFromDictionary}, not this method.
*
* @param string Repository callsign.
* @param PhabricatorUser Viewing user.
* @return DiffusionRequest New request object.
* @task new
*/
final private static function newFromCallsign(
$callsign,
PhabricatorUser $viewer) {
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withCallsigns(array($callsign))
->executeOne();
if (!$repository) {
throw new Exception("No such repository '{$callsign}'.");
}
return self::newFromRepository($repository);
}
/**
* Internal. Use @{method:newFromDictionary}, not this method.
*
* @param PhabricatorRepository Repository object.
* @return DiffusionRequest New request object.
* @task new
*/
final private static function newFromRepository(
PhabricatorRepository $repository) {
$map = array(
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'DiffusionGitRequest',
PhabricatorRepositoryType::REPOSITORY_TYPE_SVN => 'DiffusionSvnRequest',
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL =>
'DiffusionMercurialRequest',
);
$class = idx($map, $repository->getVersionControlSystem());
if (!$class) {
throw new Exception("Unknown version control system!");
}
$object = new $class();
$object->repository = $repository;
$object->callsign = $repository->getCallsign();
return $object;
}
/**
* Internal. Use @{method:newFromDictionary}, not this method.
*
* @param map Map of parsed data.
* @return void
* @task new
*/
final private function initializeFromDictionary(array $data) {
$this->path = idx($data, 'path');
$this->symbolicCommit = idx($data, 'commit');
$this->commit = idx($data, 'commit');
$this->line = idx($data, 'line');
$this->initFromConduit = idx($data, 'initFromConduit', true);
if ($this->getSupportsBranches()) {
$this->branch = idx($data, 'branch');
}
if (!$this->getUser()) {
$user = idx($data, 'user');
if (!$user) {
throw new Exception(
'You must provide a PhabricatorUser in the dictionary!');
}
$this->setUser($user);
}
$this->didInitialize();
}
final protected function shouldInitFromConduit() {
return $this->initFromConduit;
}
final public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
final public function getUser() {
return $this->user;
}
public function getRepository() {
return $this->repository;
}
public function getCallsign() {
return $this->callsign;
}
public function setPath($path) {
$this->path = $path;
return $this;
}
public function getPath() {
return $this->path;
}
public function getLine() {
return $this->line;
}
public function getCommit() {
return $this->commit;
}
public function getSymbolicCommit() {
return $this->symbolicCommit;
}
public function getBranch() {
return $this->branch;
}
public function getLint() {
return $this->lint;
}
protected function getArcanistBranch() {
return $this->getBranch();
}
public function loadBranch() {
return id(new PhabricatorRepositoryBranch())->loadOneWhere(
'repositoryID = %d AND name = %s',
$this->getRepository()->getID(),
$this->getArcanistBranch());
}
public function getTagContent() {
return $this->tagContent;
}
public function loadCommit() {
if (empty($this->repositoryCommit)) {
$repository = $this->getRepository();
+ // TODO: (T603) This should be a real query, but we need to sort out
+ // the viewer.
$commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
'repositoryID = %d AND commitIdentifier = %s',
$repository->getID(),
$this->getCommit());
+ $commit->attachRepository($repository);
$this->repositoryCommit = $commit;
}
return $this->repositoryCommit;
}
public function loadArcanistProjects() {
if (empty($this->arcanistProjects)) {
$projects = id(new PhabricatorRepositoryArcanistProject())->loadAllWhere(
'repositoryID = %d',
$this->getRepository()->getID());
$this->arcanistProjects = $projects;
}
return $this->arcanistProjects;
}
public function loadCommitData() {
if (empty($this->repositoryCommitData)) {
$commit = $this->loadCommit();
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
if (!$data) {
$data = new PhabricatorRepositoryCommitData();
$data->setCommitMessage(
'(This commit has not been fully parsed yet.)');
}
$this->repositoryCommitData = $data;
}
return $this->repositoryCommitData;
}
/**
* Retrieve a stable, permanent commit name. This returns a non-symbolic
* identifier for the current commit: e.g., a specific commit hash in git
* (NOT a symbolic name like "origin/master") or a specific revision number
* in SVN (NOT a symbolic name like "HEAD").
*
* @return string Stable commit name, like a git hash or SVN revision. Not
* a symbolic commit reference.
*/
public function getStableCommitName() {
if (!$this->stableCommitName) {
$this->queryStableCommitName();
}
return $this->stableCommitName;
}
final public function getRawCommit() {
return $this->commit;
}
public function setCommit($commit) {
$this->commit = $commit;
return $this;
}
/* -( Managing Diffusion URIs )-------------------------------------------- */
/**
* Generate a Diffusion URI using this request to provide defaults. See
* @{method:generateDiffusionURI} for details. This method is the same, but
* preserves the request parameters if they are not overridden.
*
* @param map See @{method:generateDiffusionURI}.
* @return PhutilURI Generated URI.
* @task uri
*/
public function generateURI(array $params) {
if (empty($params['stable'])) {
$default_commit = $this->getRawCommit();
} else {
$default_commit = $this->getStableCommitName();
}
$defaults = array(
'callsign' => $this->getCallsign(),
'path' => $this->getPath(),
'branch' => $this->getBranch(),
'commit' => $default_commit,
'lint' => idx($params, 'lint', $this->getLint()),
);
foreach ($defaults as $key => $val) {
if (!isset($params[$key])) { // Overwrite NULL.
$params[$key] = $val;
}
}
return self::generateDiffusionURI($params);
}
/**
* Generate a Diffusion URI from a parameter map. Applies the correct encoding
* and formatting to the URI. Parameters are:
*
* - `action` One of `history`, `browse`, `change`, `lastmodified`,
* `branch`, `tags`, `branches`, or `revision-ref`. The action specified
* by the URI.
* - `callsign` Repository callsign.
* - `branch` Optional if action is not `branch`, branch name.
* - `path` Optional, path to file.
* - `commit` Optional, commit identifier.
* - `line` Optional, line range.
* - `lint` Optional, lint code.
* - `params` Optional, query parameters.
*
* The function generates the specified URI and returns it.
*
* @param map See documentation.
* @return PhutilURI Generated URI.
* @task uri
*/
public static function generateDiffusionURI(array $params) {
$action = idx($params, 'action');
$callsign = idx($params, 'callsign');
$path = idx($params, 'path');
$branch = idx($params, 'branch');
$commit = idx($params, 'commit');
$line = idx($params, 'line');
if (strlen($callsign)) {
$callsign = phutil_escape_uri_path_component($callsign).'/';
}
if (strlen($branch)) {
$branch = phutil_escape_uri_path_component($branch).'/';
}
if (strlen($path)) {
$path = ltrim($path, '/');
$path = str_replace(array(';', '$'), array(';;', '$$'), $path);
$path = phutil_escape_uri($path);
}
$path = "{$branch}{$path}";
if (strlen($commit)) {
$commit = str_replace('$', '$$', $commit);
$commit = ';'.phutil_escape_uri($commit);
}
if (strlen($line)) {
$line = '$'.phutil_escape_uri($line);
}
$req_callsign = false;
$req_branch = false;
$req_commit = false;
switch ($action) {
case 'history':
case 'browse':
case 'change':
case 'lastmodified':
case 'tags':
case 'branches':
case 'lint':
$req_callsign = true;
break;
case 'branch':
$req_callsign = true;
$req_branch = true;
break;
case 'commit':
$req_callsign = true;
$req_commit = true;
break;
}
if ($req_callsign && !strlen($callsign)) {
throw new Exception(
"Diffusion URI action '{$action}' requires callsign!");
}
if ($req_commit && !strlen($commit)) {
throw new Exception(
"Diffusion URI action '{$action}' requires commit!");
}
switch ($action) {
case 'change':
case 'history':
case 'browse':
case 'lastmodified':
case 'tags':
case 'branches':
case 'lint':
$uri = "/diffusion/{$callsign}{$action}/{$path}{$commit}{$line}";
break;
case 'branch':
if (strlen($path)) {
$uri = "/diffusion/{$callsign}repository/{$path}";
} else {
$uri = "/diffusion/{$callsign}";
}
break;
case 'external':
$commit = ltrim($commit, ';');
$uri = "/diffusion/external/{$commit}/";
break;
case 'rendering-ref':
// This isn't a real URI per se, it's passed as a query parameter to
// the ajax changeset stuff but then we parse it back out as though
// it came from a URI.
$uri = rawurldecode("{$path}{$commit}");
break;
case 'commit':
$commit = ltrim($commit, ';');
$callsign = rtrim($callsign, '/');
$uri = "/r{$callsign}{$commit}";
break;
default:
throw new Exception("Unknown Diffusion URI action '{$action}'!");
}
if ($action == 'rendering-ref') {
return $uri;
}
$uri = new PhutilURI($uri);
if (isset($params['lint'])) {
$params['params'] = idx($params, 'params', array()) + array(
'lint' => $params['lint'],
);
}
if (idx($params, 'params')) {
$uri->setQueryParams($params['params']);
}
return $uri;
}
/**
* Internal. Public only for unit tests.
*
* Parse the request URI into components.
*
* @param string URI blob.
* @param bool True if this VCS supports branches.
* @return map Parsed URI.
*
* @task uri
*/
public static function parseRequestBlob($blob, $supports_branches) {
$result = array(
'branch' => null,
'path' => null,
'commit' => null,
'line' => null,
);
$matches = null;
if ($supports_branches) {
// Consume the front part of the URI, up to the first "/". This is the
// path-component encoded branch name.
if (preg_match('@^([^/]+)/@', $blob, $matches)) {
$result['branch'] = phutil_unescape_uri_path_component($matches[1]);
$blob = substr($blob, strlen($matches[1]) + 1);
}
}
// Consume the back part of the URI, up to the first "$". Use a negative
// lookbehind to prevent matching '$$'. We double the '$' symbol when
// encoding so that files with names like "money/$100" will survive.
$pattern = '@(?:(?:^|[^$])(?:[$][$])*)[$]([\d-,]+)$@';
if (preg_match($pattern, $blob, $matches)) {
$result['line'] = $matches[1];
$blob = substr($blob, 0, -(strlen($matches[1]) + 1));
}
// We've consumed the line number if it exists, so unescape "$" in the
// rest of the string.
$blob = str_replace('$$', '$', $blob);
// Consume the commit name, stopping on ';;'. We allow any character to
// appear in commits names, as they can sometimes be symbolic names (like
// tag names or refs).
if (preg_match('@(?:(?:^|[^;])(?:;;)*);([^;].*)$@', $blob, $matches)) {
$result['commit'] = $matches[1];
$blob = substr($blob, 0, -(strlen($matches[1]) + 1));
}
// We've consumed the commit if it exists, so unescape ";" in the rest
// of the string.
$blob = str_replace(';;', ';', $blob);
if (strlen($blob)) {
$result['path'] = $blob;
}
$parts = explode('/', $result['path']);
foreach ($parts as $part) {
// Prevent any hyjinx since we're ultimately shipping this to the
// filesystem under a lot of workflows.
if ($part == '..') {
throw new Exception("Invalid path URI.");
}
}
return $result;
}
/**
* Check that the working copy of the repository is present and readable.
*
* @param string Path to the working copy.
*/
protected function validateWorkingCopy($path) {
if (!is_readable(dirname($path))) {
$this->raisePermissionException();
}
if (!Filesystem::pathExists($path)) {
$this->raiseCloneException();
}
}
protected function raisePermissionException() {
$host = php_uname('n');
$callsign = $this->getRepository()->getCallsign();
throw new DiffusionSetupException(
"The clone of this repository ('{$callsign}') on the local machine " .
"('{$host}') could not be read. Ensure that the repository is in a " .
"location where the web server has read permissions.");
}
protected function raiseCloneException() {
$host = php_uname('n');
$callsign = $this->getRepository()->getCallsign();
throw new DiffusionSetupException(
"The working copy for this repository ('{$callsign}') hasn't been ".
"cloned yet on this machine ('{$host}'). Make sure you've started the ".
"Phabricator daemons. If this problem persists for longer than a clone ".
"should take, check the daemon logs (in the Daemon Console) to see if ".
"there were errors cloning the repository. Consult the 'Diffusion User ".
"Guide' in the documentation for help setting up repositories.");
}
final protected function expandCommitName() {
if ($this->shouldInitFromConduit()) {
$commit_data = DiffusionQuery::callConduitWithDiffusionRequest(
$this->getUser(),
$this,
'diffusion.expandshortcommitquery',
array(
'commit' => $this->commit
));
} else {
$repository = $this->getRepository();
$this->validateWorkingCopy($repository->getLocalPath());
$query = DiffusionExpandShortNameQuery::newFromRepository(
$repository);
$query->setCommit($this->commit);
$commit_data = $query->expand();
}
$this->commit = $commit_data['commit'];
$this->commitType = $commit_data['commitType'];
$this->tagContent = $commit_data['tagContent'];
}
private function queryStableCommitName() {
if ($this->commit) {
$this->stableCommitName = $this->commit;
} else if ($this->shouldInitFromConduit()) {
$this->stableCommitName = DiffusionQuery::callConduitWithDiffusionRequest(
$this->getUser(),
$this,
'diffusion.stablecommitnamequery',
array(
'branch' => $this->getBranch()
));
} else {
$query = DiffusionStableCommitNameQuery::newFromRepository(
$this->getRepository());
$query->setBranch($this->getBranch());
$this->stableCommitName = $query->load();
}
return $this->stableCommitName;
}
}
diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php
index 82340ffdb2..5d747a41af 100644
--- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php
+++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php
@@ -1,207 +1,215 @@
<?php
final class PhabricatorRepositoryCommit
extends PhabricatorRepositoryDAO
implements
PhabricatorPolicyInterface,
PhabricatorTokenReceiverInterface {
protected $repositoryID;
protected $phid;
protected $commitIdentifier;
protected $epoch;
protected $mailKey;
protected $authorPHID;
protected $auditStatus = PhabricatorAuditCommitStatusConstants::NONE;
protected $summary = '';
private $commitData = self::ATTACHABLE;
private $audits;
private $isUnparsed;
private $repository = self::ATTACHABLE;
public function attachRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function getRepository() {
return $this->assertAttached($this->repository);
}
public function setIsUnparsed($is_unparsed) {
$this->isUnparsed = $is_unparsed;
return $this;
}
public function getIsUnparsed() {
return $this->isUnparsed;
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorRepositoryPHIDTypeCommit::TYPECONST);
}
public function loadCommitData() {
if (!$this->getID()) {
return null;
}
return id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$this->getID());
}
public function attachCommitData(PhabricatorRepositoryCommitData $data) {
$this->commitData = $data;
return $this;
}
public function getCommitData() {
return $this->assertAttached($this->commitData);
}
public function attachAudits(array $audits) {
assert_instances_of($audits, 'PhabricatorAuditComment');
$this->audits = $audits;
return $this;
}
public function getAudits() {
return $this->audits;
}
public function save() {
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
return parent::save();
}
public function delete() {
$data = $this->loadCommitData();
$audits = id(new PhabricatorRepositoryAuditRequest())
->loadAllWhere('commitPHID = %s', $this->getPHID());
$this->openTransaction();
if ($data) {
$data->delete();
}
foreach ($audits as $audit) {
$audit->delete();
}
$result = parent::delete();
$this->saveTransaction();
return $result;
}
public function getDateCreated() {
// This is primarily to make analysis of commits with the Fact engine work.
return $this->getEpoch();
}
/**
* Synchronize a commit's overall audit status with the individual audit
* triggers.
*/
public function updateAuditStatus(array $requests) {
assert_instances_of($requests, 'PhabricatorRepositoryAuditRequest');
$any_concern = false;
$any_accept = false;
$any_need = false;
foreach ($requests as $request) {
switch ($request->getAuditStatus()) {
case PhabricatorAuditStatusConstants::AUDIT_REQUIRED:
$any_need = true;
break;
case PhabricatorAuditStatusConstants::ACCEPTED:
$any_accept = true;
break;
case PhabricatorAuditStatusConstants::CONCERNED:
$any_concern = true;
break;
}
}
if ($any_concern) {
$status = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
} else if ($any_accept) {
if ($any_need) {
$status = PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED;
} else {
$status = PhabricatorAuditCommitStatusConstants::FULLY_AUDITED;
}
} else if ($any_need) {
$status = PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT;
} else {
$status = PhabricatorAuditCommitStatusConstants::NONE;
}
return $this->setAuditStatus($status);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
- return $this->getRepository()->getPolicy($capability);
+ switch ($capability) {
+ case PhabricatorPolicyCapability::CAN_VIEW:
+ return $this->getRepository()->getPolicy($capability);
+ case PhabricatorPolicyCapability::CAN_EDIT:
+ // TODO: (T603) Who should be able to edit a commit? For now, retain
+ // the existing policy.
+ return PhabricatorPolicies::POLICY_USER;
+ }
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getRepository()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return pht(
'Commits inherit the policies of the repository they belong to.');
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
return array(
$this->getAuthorPHID(),
);
}
/* -( Stuff for serialization )---------------------------------------------- */
/**
* NOTE: this is not a complete serialization; only the 'protected' fields are
* involved. This is due to ease of (ab)using the Lisk abstraction to get this
* done, as well as complexity of the other fields.
*/
public function toDictionary() {
return array(
'repositoryID' => $this->getRepositoryID(),
'phid' => $this->getPHID(),
'commitIdentifier' => $this->getCommitIdentifier(),
'epoch' => $this->getEpoch(),
'mailKey' => $this->getMailKey(),
'authorPHID' => $this->getAuthorPHID(),
'auditStatus' => $this->getAuditStatus(),
'summary' => $this->getSummary());
}
public static function newFromDictionary(array $dict) {
return id(new PhabricatorRepositoryCommit())
->loadFromArray($dict);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Mar 24, 03:58 (1 d, 1 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1132346
Default Alt Text
(71 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment