diff --git a/resources/sql/autopatches/20141119.commitpedge.sql b/resources/sql/autopatches/20141119.commitpedge.sql
new file mode 100644
index 0000000000..bb5c1ec56c
--- /dev/null
+++ b/resources/sql/autopatches/20141119.commitpedge.sql
@@ -0,0 +1,11 @@
+INSERT IGNORE INTO {$NAMESPACE}_repository.edge
+ (src, type, dst, dateCreated, seq)
+ SELECT src, 41, dst, dateCreated, seq
+ FROM {$NAMESPACE}_repository.edge
+ WHERE type = 15;
+
+INSERT IGNORE INTO {$NAMESPACE}_project.edge
+ (src, type, dst, dateCreated, seq)
+ SELECT src, 42, dst, dateCreated, seq
+ FROM {$NAMESPACE}_project.edge
+ WHERE type = 16;
diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php
index f4006f82c7..7b92ae062e 100644
--- a/src/applications/diffusion/controller/DiffusionCommitController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitController.php
@@ -1,1109 +1,1097 @@
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);
}
$repository = $drequest->getRepository();
$callsign = $repository->getCallsign();
$content = array();
$commit = id(new DiffusionCommitQuery())
->setViewer($request->getUser())
->withRepository($repository)
->withIdentifiers(array($drequest->getCommit()))
->needCommitData(true)
->needAuditRequests(true)
->executeOne();
$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'),
'device' => false,
));
}
$top_anchor = id(new PhabricatorAnchorView())
->setAnchorName('top')
->setNavigationMarker(true);
$audit_requests = $commit->getAudits();
$this->auditAuthorityPHIDs =
PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
$commit_data = $commit->getCommitData();
$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('phabricator-remarkup-css');
$parents = $this->callConduitWithDiffusionRequest(
'diffusion.commitparentsquery',
array('commit' => $drequest->getCommit()));
if ($parents) {
$parents = id(new DiffusionCommitQuery())
->setViewer($user)
->withRepository($repository)
->withIdentifiers($parents)
->execute();
}
$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 PHUIPropertyListView())
->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->setActionList($headsup_actions);
$detail_list = new PHUIPropertyListView();
$detail_list->addSectionHeader(
pht('Description'),
PHUIPropertyListView::ICON_SUMMARY);
$detail_list->addTextContent(
phutil_tag(
'div',
array(
'class' => 'diffusion-commit-message phabricator-remarkup',
),
$message));
$content[] = $top_anchor;
$object_box = id(new PHUIObjectBoxView())
->setHeader($headsup_view)
->addPropertyList($property_list)
->addPropertyList($detail_list);
$content[] = $object_box;
}
$content[] = $this->buildComments($commit);
$hard_limit = 1000;
if ($commit->isImported()) {
$change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
$drequest);
$change_query->setLimit($hard_limit + 1);
$changes = $change_query->loadChanges();
} else {
$changes = array();
}
$was_limited = (count($changes) > $hard_limit);
if ($was_limited) {
$changes = array_slice($changes, 0, $hard_limit);
}
$content[] = $this->buildMergesTable($commit);
$highlighted_audits = $commit->getAuthorityAudits(
$user,
$this->auditAuthorityPHIDs);
$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) {
$content[] = $this->renderStatusMessage(
pht('Bad Commit'),
$bad_commit['description']);
} else if ($is_foreign) {
// Don't render anything else.
} else if (!$commit->isImported()) {
$content[] = $this->renderStatusMessage(
pht('Still Importing...'),
pht(
'This commit is still importing. Changes will be visible once '.
'the import finishes.'));
} else if (!count($changes)) {
$content[] = $this->renderStatusMessage(
pht('Empty Commit'),
pht(
'This commit is empty and does not affect any paths.'));
} else if ($was_limited) {
$content[] = $this->renderStatusMessage(
pht('Enormous Commit'),
pht(
'This commit is enormous, and affects more than %d files. '.
'Changes are not shown.',
$hard_limit));
} 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 PHUIObjectBoxView();
$header = new PHUIHeaderView();
$header->setHeader('Changes ('.number_format($count).')');
$change_panel->setID('toc');
if ($count > self::CHANGES_LIMIT && !$show_all_details) {
$icon = id(new PHUIIconView())
->setIconFont('fa-files-o');
$button = id(new PHUIButtonView())
->setText(pht('Show All Changes'))
->setHref('?show_all=true')
->setTag('a')
->setIcon($icon);
$warning_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle('Very Large Commit')
->appendChild(
pht('This commit is very large. Load each file individually.'));
$change_panel->setErrorView($warning_view);
$header->addActionLink($button);
}
$change_panel->appendChild($change_table);
$change_panel->setHeader($header);
$content[] = $change_panel;
$changesets = DiffusionPathChange::convertToDifferentialChangesets(
$user,
$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 = PhabricatorAuditInlineComment::loadDraftAndPublishedComments(
$user,
$commit->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);
// TODO: Try to setBranch() to something reasonable here?
$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()),
'device' => false,
));
}
private function loadCommitProperties(
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $data,
array $parents,
array $audit_requests) {
assert_instances_of($parents, 'PhabricatorRepositoryCommit');
$viewer = $this->getRequest()->getUser();
$commit_phid = $commit->getPHID();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($commit_phid))
->withEdgeTypes(array(
DiffusionCommitHasTaskEdgeType::EDGECONST,
- PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT,
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV,
));
$edges = $edge_query->execute();
$task_phids = array_keys(
$edges[$commit_phid][DiffusionCommitHasTaskEdgeType::EDGECONST]);
- $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();
}
}
// NOTE: We should never normally have more than a single push log, but
// it can occur naturally if a commit is pushed, then the branch it was
// on is deleted, then the commit is pushed again (or through other similar
// chains of events). This should be rare, but does not indicate a bug
// or data issue.
// NOTE: We never query push logs in SVN because the commiter is always
// the pusher and the commit time is always the push time; the push log
// is redundant and we save a query by skipping it.
$push_logs = array();
if ($repository->isHosted() && !$repository->isSVN()) {
$push_logs = id(new PhabricatorRepositoryPushLogQuery())
->setViewer($viewer)
->withRepositoryPHIDs(array($repository->getPHID()))
->withNewRefs(array($commit->getCommitIdentifier()))
->withRefTypes(array(PhabricatorRepositoryPushLog::REFTYPE_COMMIT))
->execute();
foreach ($push_logs as $log) {
$phids[] = $log->getPusherPHID();
}
}
$handles = array();
if ($phids) {
$handles = $this->loadViewerHandles($phids);
}
$props = array();
if ($commit->getAuditStatus()) {
$status = PhabricatorAuditCommitStatusConstants::getStatusName(
$commit->getAuditStatus());
$tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_STATE)
->setName($status);
switch ($commit->getAuditStatus()) {
case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT:
$tag->setBackgroundColor(PHUITagView::COLOR_ORANGE);
break;
case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED:
$tag->setBackgroundColor(PHUITagView::COLOR_RED);
break;
case PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED:
$tag->setBackgroundColor(PHUITagView::COLOR_BLUE);
break;
case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED:
$tag->setBackgroundColor(PHUITagView::COLOR_GREEN);
break;
}
$props['Status'] = $tag;
}
if ($audit_requests) {
$user_requests = array();
$other_requests = array();
foreach ($audit_requests as $audit_request) {
if ($audit_request->isUser()) {
$user_requests[] = $audit_request;
} else {
$other_requests[] = $audit_request;
}
}
if ($user_requests) {
$props['Auditors'] = $this->renderAuditStatusView(
$user_requests);
}
if ($other_requests) {
$props['Project/Package Auditors'] = $this->renderAuditStatusView(
$other_requests);
}
}
$author_phid = $data->getCommitDetail('authorPHID');
$author_name = $data->getAuthorName();
if (!$repository->isSVN()) {
$authored_info = id(new PHUIStatusItemView());
// TODO: In Git, a distinct authorship date is available. When present,
// we should show it here.
if ($author_phid) {
$authored_info->setTarget($handles[$author_phid]->renderLink());
} else if (strlen($author_name)) {
$authored_info->setTarget($author_name);
}
$props['Authored'] = id(new PHUIStatusListView())
->addItem($authored_info);
}
$committed_info = id(new PHUIStatusItemView())
->setNote(phabricator_datetime($commit->getEpoch(), $viewer));
$committer_phid = $data->getCommitDetail('committerPHID');
$committer_name = $data->getCommitDetail('committer');
if ($committer_phid) {
$committed_info->setTarget($handles[$committer_phid]->renderLink());
} else if (strlen($committer_name)) {
$committed_info->setTarget($committer_name);
} else if ($author_phid) {
$committed_info->setTarget($handles[$author_phid]->renderLink());
} else if (strlen($author_name)) {
$committed_info->setTarget($author_name);
}
$props['Committed'] = id(new PHUIStatusListView())
->addItem($committed_info);
if ($push_logs) {
$pushed_list = new PHUIStatusListView();
foreach ($push_logs as $push_log) {
$pushed_item = id(new PHUIStatusItemView())
->setTarget($handles[$push_log->getPusherPHID()]->renderLink())
->setNote(phabricator_datetime($push_log->getEpoch(), $viewer));
$pushed_list->addItem($pushed_item);
}
$props['Pushed'] = $pushed_list;
}
$reviewer_phid = $data->getCommitDetail('reviewerPHID');
if ($reviewer_phid) {
$props['Reviewer'] = $handles[$reviewer_phid]->renderLink();
}
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);
}
$props['Branches'] = phutil_tag(
'span',
array(
'id' => 'commit-branches',
),
pht('Unknown'));
$props['Tags'] = phutil_tag(
'span',
array(
'id' => 'commit-tags',
),
pht('Unknown'));
$callsign = $repository->getCallsign();
$root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier();
Javelin::initBehavior(
'diffusion-commit-branches',
array(
$root.'/branches/' => 'commit-branches',
$root.'/tags/' => 'commit-tags',
));
$refs = $this->buildRefs($drequest);
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) {
$viewer = $this->getRequest()->getUser();
$xactions = id(new PhabricatorAuditTransactionQuery())
->setViewer($viewer)
->withObjectPHIDs(array($commit->getPHID()))
->needComments(true)
->execute();
$path_ids = array();
foreach ($xactions as $xaction) {
if ($xaction->hasComment()) {
$path_id = $xaction->getComment()->getPathID();
if ($path_id) {
$path_ids[] = $path_id;
}
}
}
$path_map = array();
if ($path_ids) {
$path_map = id(new DiffusionPathQuery())
->withPathIDs($path_ids)
->execute();
$path_map = ipull($path_map, 'path', 'id');
}
return id(new PhabricatorAuditTransactionView())
->setUser($viewer)
->setObjectPHID($commit->getPHID())
->setPathMap($path_map)
->setTransactions($xactions);
}
private function renderAddCommentPanel(
PhabricatorRepositoryCommit $commit,
array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$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)
->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(pht('Submit')));
$header = new PHUIHeaderView();
$header->setHeader(
$is_serious ? pht('Audit Commit') : pht('Creative Accounting'));
require_celerity_resource('phabricator-transaction-view-css');
$mailable_source = new PhabricatorMetaMTAMailableDatasource();
$auditor_source = new DiffusionAuditorDatasource();
Javelin::initBehavior(
'differential-add-reviewers-and-ccs',
array(
'dynamic' => array(
'add-auditors-tokenizer' => array(
'actions' => array('add_auditors' => 1),
'src' => $auditor_source->getDatasourceURI(),
'row' => 'add-auditors',
'placeholder' => $auditor_source->getPlaceholderText(),
),
'add-ccs-tokenizer' => array(
'actions' => array('add_ccs' => 1),
'src' => $mailable_source->getDatasourceURI(),
'row' => 'add-ccs',
'placeholder' => $mailable_source->getPlaceholderText(),
),
),
'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().'/',
));
$loading = phutil_tag_div(
'aphront-panel-preview-loading-text',
pht('Loading preview...'));
$preview_panel = phutil_tag_div(
'aphront-panel-preview aphront-panel-flush',
array(
phutil_tag('div', array('id' => 'audit-preview'), $loading),
phutil_tag('div', array('id' => 'inline-comment-preview')),
));
// TODO: This is pretty awkward, unify the CSS between Diffusion and
// Differential better.
require_celerity_resource('differential-core-view-css');
$anchor = id(new PhabricatorAnchorView())
->setAnchorName('comment')
->setNavigationMarker(true)
->render();
$comment_box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($form);
return phutil_tag(
'div',
array(
'id' => $pane_id,
),
phutil_tag_div(
'differential-add-comment-panel',
array($anchor, $comment_box, $preview_panel)));
}
/**
* Return a map of available audit actions for rendering into a .
* 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());
$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('fa-pencil')
->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 = 'PhabricatorManiphestApplication';
if (PhabricatorApplication::isClassInstalled($maniphest)) {
$action = id(new PhabricatorActionView())
->setName(pht('Edit Maniphest Tasks'))
->setIcon('fa-anchor')
->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/')
->setWorkflow(true)
->setDisabled(!$can_edit);
$actions->addAction($action);
}
$action = id(new PhabricatorActionView())
->setName(pht('Download Raw Diff'))
->setHref($request->getRequestURI()->alter('diff', true))
->setIcon('fa-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',
'ttl' => (60 * 60 * 24),
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$file->attachToObject($drequest->getRepository()->getPHID());
unset($unguarded);
return $file->getRedirectResponse();
}
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) {
$code = $request->getAuditStatus();
$item = new PHUIStatusItemView();
$item->setIcon(
PhabricatorAuditStatusConstants::getStatusIcon($code),
PhabricatorAuditStatusConstants::getStatusColor($code),
PhabricatorAuditStatusConstants::getStatusName($code));
$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/DiffusionCommitEditController.php b/src/applications/diffusion/controller/DiffusionCommitEditController.php
index 5515f3e322..75b4a31ea4 100644
--- a/src/applications/diffusion/controller/DiffusionCommitEditController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitEditController.php
@@ -1,139 +1,133 @@
getRequest()->getUser();
$this->diffusionRequest = DiffusionRequest::newFromDictionary($data);
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$drequest = $this->getDiffusionRequest();
$callsign = $drequest->getRepository()->getCallsign();
$repository = $drequest->getRepository();
$commit = $drequest->loadCommit();
$data = $commit->loadCommitData();
$page_title = pht('Edit Diffusion Commit');
if (!$commit) {
return new Aphront404Response();
}
$commit_phid = $commit->getPHID();
- $edge_type = PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT;
+ $edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$current_proj_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$commit_phid,
$edge_type);
$handles = $this->loadViewerHandles($current_proj_phids);
$proj_t_values = $handles;
if ($request->isFormPost()) {
+ $xactions = array();
$proj_phids = $request->getArr('projects');
- $new_proj_phids = array_values($proj_phids);
- $rem_proj_phids = array_diff($current_proj_phids,
- $new_proj_phids);
-
- $editor = id(new PhabricatorEdgeEditor());
- foreach ($rem_proj_phids as $phid) {
- $editor->removeEdge($commit_phid, $edge_type, $phid);
- }
- foreach ($new_proj_phids as $phid) {
- $editor->addEdge($commit_phid, $edge_type, $phid);
- }
- $editor->save();
-
- id(new PhabricatorSearchIndexer())
- ->queueDocumentForIndexing($commit->getPHID());
-
+ $xactions[] = id(new PhabricatorAuditTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
+ ->setMetadataValue('edge:type', $edge_type)
+ ->setNewValue(array('=' => array_fuse($proj_phids)));
+ $editor = id(new PhabricatorAuditEditor())
+ ->setActor($user)
+ ->setContinueOnNoEffect(true)
+ ->setContentSourceFromRequest($request);
+ $xactions = $editor->applyTransactions($commit, $xactions);
return id(new AphrontRedirectResponse())
->setURI('/r'.$callsign.$commit->getCommitIdentifier());
}
$tokenizer_id = celerity_generate_unique_node_id();
$form = id(new AphrontFormView())
->setUser($user)
->setAction($request->getRequestURI()->getPath())
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Projects'))
->setName('projects')
->setValue($proj_t_values)
->setID($tokenizer_id)
->setCaption(
javelin_tag(
'a',
array(
'href' => '/project/create/',
'mustcapture' => true,
'sigil' => 'project-create',
),
pht('Create New Project')))
->setDatasource(new PhabricatorProjectDatasource()));
$reason = $data->getCommitDetail('autocloseReason', false);
if ($reason !== false) {
switch ($reason) {
case PhabricatorRepository::BECAUSE_REPOSITORY_IMPORTING:
$desc = pht('No, Repository Importing');
break;
case PhabricatorRepository::BECAUSE_AUTOCLOSE_DISABLED:
$desc = pht('No, Autoclose Disabled');
break;
case PhabricatorRepository::BECAUSE_NOT_ON_AUTOCLOSE_BRANCH:
$desc = pht('No, Not On Autoclose Branch');
break;
case null:
$desc = pht('Yes');
break;
default:
$desc = pht('Unknown');
break;
}
$doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: Autoclose');
$doc_link = phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
pht('Learn More'));
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Autoclose?'))
->setValue(array($desc, " \xC2\xB7 ", $doc_link)));
}
Javelin::initBehavior('project-create', array(
'tokenizerID' => $tokenizer_id,
));
$submit = id(new AphrontFormSubmitControl())
->setValue(pht('Save'))
->addCancelButton('/r'.$callsign.$commit->getCommitIdentifier());
$form->appendChild($submit);
$crumbs = $this->buildCrumbs(array(
'commit' => true,
));
$crumbs->addTextCrumb(pht('Edit'));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($page_title)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$form_box,
),
array(
'title' => $page_title,
));
}
}
diff --git a/src/applications/maniphest/search/ManiphestSearchIndexer.php b/src/applications/maniphest/search/ManiphestSearchIndexer.php
index 290d7c92da..3b2ffe48a6 100644
--- a/src/applications/maniphest/search/ManiphestSearchIndexer.php
+++ b/src/applications/maniphest/search/ManiphestSearchIndexer.php
@@ -1,83 +1,75 @@
loadDocumentByPHID($phid);
$doc = new PhabricatorSearchAbstractDocument();
$doc->setPHID($task->getPHID());
$doc->setDocumentType(ManiphestTaskPHIDType::TYPECONST);
$doc->setDocumentTitle($task->getTitle());
$doc->setDocumentCreated($task->getDateCreated());
$doc->setDocumentModified($task->getDateModified());
$doc->addField(
PhabricatorSearchField::FIELD_BODY,
$task->getDescription());
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR,
$task->getAuthorPHID(),
PhabricatorPeopleUserPHIDType::TYPECONST,
$task->getDateCreated());
$doc->addRelationship(
$task->isClosed()
? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED
: PhabricatorSearchRelationship::RELATIONSHIP_OPEN,
$task->getPHID(),
ManiphestTaskPHIDType::TYPECONST,
time());
$this->indexTransactions(
$doc,
new ManiphestTransactionQuery(),
array($phid));
- foreach ($task->getProjectPHIDs() as $phid) {
- $doc->addRelationship(
- PhabricatorSearchRelationship::RELATIONSHIP_PROJECT,
- $phid,
- PhabricatorProjectProjectPHIDType::TYPECONST,
- $task->getDateModified()); // Bogus.
- }
-
$owner = $task->getOwnerPHID();
if ($owner) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_OWNER,
$owner,
PhabricatorPeopleUserPHIDType::TYPECONST,
time());
} else {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED,
$task->getPHID(),
PhabricatorPHIDConstants::PHID_TYPE_VOID,
$task->getDateCreated());
}
// We need to load handles here since non-users may subscribe (mailing
// lists, e.g.)
$ccs = $task->getCCPHIDs();
$handles = id(new PhabricatorHandleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs($ccs)
->execute();
foreach ($ccs as $cc) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER,
$handles[$cc]->getPHID(),
$handles[$cc]->getType(),
time());
}
return $doc;
}
}
diff --git a/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php b/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php
index f7ecf57ba6..1c17e39af9 100644
--- a/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php
+++ b/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php
@@ -1,76 +1,63 @@
loadDocumentByPHID($phid);
$commit_data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
$date_created = $commit->getEpoch();
$commit_message = $commit_data->getCommitMessage();
$author_phid = $commit_data->getCommitDetail('authorPHID');
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->withIDs(array($commit->getRepositoryID()))
->executeOne();
if (!$repository) {
throw new Exception('No such repository!');
}
$title = 'r'.$repository->getCallsign().$commit->getCommitIdentifier().
' '.$commit_data->getSummary();
$doc = new PhabricatorSearchAbstractDocument();
$doc->setPHID($commit->getPHID());
$doc->setDocumentType(PhabricatorRepositoryCommitPHIDType::TYPECONST);
$doc->setDocumentCreated($date_created);
$doc->setDocumentModified($date_created);
$doc->setDocumentTitle($title);
$doc->addField(
PhabricatorSearchField::FIELD_BODY,
$commit_message);
if ($author_phid) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR,
$author_phid,
PhabricatorPeopleUserPHIDType::TYPECONST,
$date_created);
}
- $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
- $commit->getPHID(),
- PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT);
- if ($project_phids) {
- foreach ($project_phids as $project_phid) {
- $doc->addRelationship(
- PhabricatorSearchRelationship::RELATIONSHIP_PROJECT,
- $project_phid,
- PhabricatorProjectProjectPHIDType::TYPECONST,
- $date_created);
- }
- }
-
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY,
$repository->getPHID(),
PhabricatorRepositoryRepositoryPHIDType::TYPECONST,
$date_created);
$this->indexTransactions(
$doc,
new PhabricatorAuditTransactionQuery(),
array($commit->getPHID()));
return $doc;
}
}
diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php
index 782bc018d3..2a3c4551bb 100644
--- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php
+++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php
@@ -1,404 +1,405 @@
repository = $repository;
return $this;
}
public function getRepository($assert_attached = true) {
if ($assert_attached) {
return $this->assertAttached($this->repository);
}
return $this->repository;
}
public function isPartiallyImported($mask) {
return (($mask & $this->getImportStatus()) == $mask);
}
public function isImported() {
return $this->isPartiallyImported(self::IMPORTED_ALL);
}
public function writeImportStatusFlag($flag) {
queryfx(
$this->establishConnection('w'),
'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d',
$this->getTableName(),
$flag,
$this->getID());
$this->setImportStatus($this->getImportStatus() | $flag);
return $this;
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'commitIdentifier' => 'text40',
'mailKey' => 'bytes20',
'authorPHID' => 'phid?',
'auditStatus' => 'uint32',
'summary' => 'text80',
'importStatus' => 'uint32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'repositoryID' => array(
'columns' => array('repositoryID', 'importStatus'),
),
'authorPHID' => array(
'columns' => array('authorPHID', 'auditStatus', 'epoch'),
),
'repositoryID_2' => array(
'columns' => array('repositoryID', 'epoch'),
),
'key_commit_identity' => array(
'columns' => array('commitIdentifier', 'repositoryID'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorRepositoryCommitPHIDType::TYPECONST);
}
public function loadCommitData() {
if (!$this->getID()) {
return null;
}
return id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$this->getID());
}
public function attachCommitData(
PhabricatorRepositoryCommitData $data = null) {
$this->commitData = $data;
return $this;
}
public function getCommitData() {
return $this->assertAttached($this->commitData);
}
public function attachAudits(array $audits) {
assert_instances_of($audits, 'PhabricatorRepositoryAuditRequest');
$this->audits = $audits;
return $this;
}
public function getAudits() {
return $this->assertAttached($this->audits);
}
public function getAuthorityAudits(
PhabricatorUser $user,
array $authority_phids) {
$authority = array_fill_keys($authority_phids, true);
$audits = $this->getAudits();
$authority_audits = array();
foreach ($audits as $audit) {
$has_authority = !empty($authority[$audit->getAuditorPHID()]);
if ($has_authority) {
$commit_author = $this->getAuthorPHID();
// You don't have authority over package and project audits on your
// own commits.
$auditor_is_user = ($audit->getAuditorPHID() == $user->getPHID());
$user_is_author = ($commit_author == $user->getPHID());
if ($auditor_is_user || !$user_is_author) {
$authority_audits[$audit->getID()] = $audit;
}
}
}
return $authority_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();
}
public function getURI() {
$repository = $this->getRepository();
$callsign = $repository->getCallsign();
$commit_identifier = $this->getCommitIdentifier();
return '/r'.$callsign.$commit_identifier;
}
/**
* 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) {
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(),
'importStatus' => $this->getImportStatus(),
);
}
public static function newFromDictionary(array $dict) {
return id(new PhabricatorRepositoryCommit())
->loadFromArray($dict);
}
/* -( HarbormasterBuildableInterface )------------------------------------- */
public function getHarbormasterBuildablePHID() {
return $this->getPHID();
}
public function getHarbormasterContainerPHID() {
return $this->getRepository()->getPHID();
}
public function getBuildVariables() {
$results = array();
$results['buildable.commit'] = $this->getCommitIdentifier();
$repo = $this->getRepository();
$results['repository.callsign'] = $repo->getCallsign();
$results['repository.vcs'] = $repo->getVersionControlSystem();
$results['repository.uri'] = $repo->getPublicCloneURI();
return $results;
}
public function getAvailableBuildVariables() {
return array(
'buildable.commit' => pht('The commit identifier, if applicable.'),
'repository.callsign' =>
pht('The callsign of the repository in Phabricator.'),
'repository.vcs' =>
pht('The version control system, either "svn", "hg" or "git".'),
'repository.uri' =>
pht('The URI to clone or checkout the repository from.'),
);
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
public function getCustomFieldSpecificationForRole($role) {
// TODO: We could make this configurable eventually, but just use the
// defaults for now.
return array();
}
public function getCustomFieldBaseClass() {
return 'PhabricatorCommitCustomField';
}
public function getCustomFields() {
return $this->assertAttached($this->customFields);
}
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
$this->customFields = $fields;
return $this;
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
// TODO: This should also list auditors, but handling that is a bit messy
// right now because we are not guaranteed to have the data.
return ($phid == $this->getAuthorPHID());
}
public function shouldShowSubscribersProperty() {
return true;
}
public function shouldAllowSubscription($phid) {
return true;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorAuditEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorAuditTransaction();
}
}
diff --git a/src/applications/search/index/PhabricatorSearchDocumentIndexer.php b/src/applications/search/index/PhabricatorSearchDocumentIndexer.php
index fbecdc8213..83b450cd0e 100644
--- a/src/applications/search/index/PhabricatorSearchDocumentIndexer.php
+++ b/src/applications/search/index/PhabricatorSearchDocumentIndexer.php
@@ -1,155 +1,178 @@
getIndexableObject();
return (phid_get_type($phid) == phid_get_type($object->generatePHID()));
}
public function getIndexIterator() {
$object = $this->getIndexableObject();
return new LiskMigrationIterator($object);
}
protected function loadDocumentByPHID($phid) {
$object = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs(array($phid))
->executeOne();
if (!$object) {
throw new Exception("Unable to load object by phid '{$phid}'!");
}
return $object;
}
public function indexDocumentByPHID($phid) {
try {
$document = $this->buildAbstractDocumentByPHID($phid);
$object = $this->loadDocumentByPHID($phid);
// Automatically rebuild CustomField indexes if the object uses custom
// fields.
if ($object instanceof PhabricatorCustomFieldInterface) {
$this->indexCustomFields($document, $object);
}
// Automatically rebuild subscriber indexes if the object is subscribable.
if ($object instanceof PhabricatorSubscribableInterface) {
$this->indexSubscribers($document);
}
+ // Automatically build project relationships
+ if ($object instanceof PhabricatorProjectInterface) {
+ $this->indexProjects($document, $object);
+ }
+
$engine = PhabricatorSearchEngineSelector::newSelector()->newEngine();
try {
$engine->reindexAbstractDocument($document);
} catch (Exception $ex) {
$phid = $document->getPHID();
$class = get_class($engine);
phlog("Unable to index document {$phid} with engine {$class}.");
phlog($ex);
}
$this->dispatchDidUpdateIndexEvent($phid, $document);
} catch (Exception $ex) {
$class = get_class($this);
phlog("Unable to build document {$phid} with indexer {$class}.");
phlog($ex);
}
return $this;
}
protected function newDocument($phid) {
return id(new PhabricatorSearchAbstractDocument())
->setPHID($phid)
->setDocumentType(phid_get_type($phid));
}
protected function indexSubscribers(
PhabricatorSearchAbstractDocument $doc) {
$subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$doc->getPHID());
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->getViewer())
->withPHIDs($subscribers)
->execute();
foreach ($handles as $phid => $handle) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER,
$phid,
$handle->getType(),
$doc->getDocumentModified()); // Bogus timestamp.
}
}
+ protected function indexProjects(
+ PhabricatorSearchAbstractDocument $doc,
+ PhabricatorProjectInterface $object) {
+
+ $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
+ $object->getPHID(),
+ PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
+ if ($project_phids) {
+ foreach ($project_phids as $project_phid) {
+ $doc->addRelationship(
+ PhabricatorSearchRelationship::RELATIONSHIP_PROJECT,
+ $project_phid,
+ PhabricatorProjectProjectPHIDType::TYPECONST,
+ $doc->getDocumentModified()); // Bogus timestamp.
+ }
+ }
+ }
+
protected function indexTransactions(
PhabricatorSearchAbstractDocument $doc,
PhabricatorApplicationTransactionQuery $query,
array $phids) {
$xactions = id(clone $query)
->setViewer($this->getViewer())
->withObjectPHIDs($phids)
->execute();
foreach ($xactions as $xaction) {
if (!$xaction->hasComment()) {
continue;
}
$comment = $xaction->getComment();
$doc->addField(
PhabricatorSearchField::FIELD_COMMENT,
$comment->getContent());
}
}
protected function indexCustomFields(
PhabricatorSearchAbstractDocument $document,
PhabricatorCustomFieldInterface $object) {
// Rebuild the ApplicationSearch indexes. These are internal and not part of
// the fulltext search, but putting them in this workflow allows users to
// use the same tools to rebuild the indexes, which is easy to understand.
$field_list = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_DEFAULT);
$field_list->setViewer($this->getViewer());
$field_list->readFieldsFromStorage($object);
// Rebuild ApplicationSearch indexes.
$field_list->rebuildIndexes($object);
// Rebuild global search indexes.
$field_list->updateAbstractDocument($document);
}
private function dispatchDidUpdateIndexEvent(
$phid,
PhabricatorSearchAbstractDocument $document) {
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_SEARCH_DIDUPDATEINDEX,
array(
'phid' => $phid,
'object' => $this->loadDocumentByPHID($phid),
'document' => $document,
));
$event->setUser($this->getViewer());
PhutilEventEngine::dispatchEvent($event);
}
}
diff --git a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php
index db725776ab..9738dec2a8 100644
--- a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php
+++ b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php
@@ -1,510 +1,499 @@
List of edge types that objects should not be built for.
* This is used to avoid constructing duplicate objects for edge constants
* which have migrated and already have a real object.
* @return list Real-looking edge type objects for
* unmigrated edge types.
*/
public static function getLegacyTypes(array $exclude) {
$consts = array_merge(
range(1, 50),
array(9000),
range(80000, 80005));
+ $exclude[] = 15; // Was TYPE_COMMIT_HAS_PROJECT
+ $exclude[] = 16; // Was TYPE_PROJECT_HAS_COMMIT
+
$exclude[] = 27; // Was TYPE_ACCOUNT_HAS_MEMBER
$exclude[] = 28; // Was TYPE_MEMBER_HAS_ACCOUNT
$exclude[] = 43; // Was TYPE_OBJECT_HAS_COLUMN
$exclude[] = 44; // Was TYPE_COLUMN_HAS_OBJECT
$consts = array_diff($consts, $exclude);
$map = array();
foreach ($consts as $const) {
$prevent_cycles = self::shouldPreventCycles($const);
$inverse_constant = self::getInverse($const);
$map[$const] = id(new PhabricatorLegacyEdgeType())
->setEdgeConstant($const)
->setShouldPreventCycles($prevent_cycles)
->setInverseEdgeConstant($inverse_constant)
->setStrings(
array(
self::getAddStringForEdgeType($const),
self::getRemoveStringForEdgeType($const),
self::getEditStringForEdgeType($const),
self::getFeedStringForEdgeType($const),
));
}
return $map;
}
private static function getInverse($edge_type) {
static $map = array(
self::TYPE_TASK_DEPENDS_ON_TASK => self::TYPE_TASK_DEPENDED_ON_BY_TASK,
self::TYPE_TASK_DEPENDED_ON_BY_TASK => self::TYPE_TASK_DEPENDS_ON_TASK,
self::TYPE_DREV_DEPENDS_ON_DREV => self::TYPE_DREV_DEPENDED_ON_BY_DREV,
self::TYPE_DREV_DEPENDED_ON_BY_DREV => self::TYPE_DREV_DEPENDS_ON_DREV,
self::TYPE_BLOG_HAS_POST => self::TYPE_POST_HAS_BLOG,
self::TYPE_POST_HAS_BLOG => self::TYPE_BLOG_HAS_POST,
self::TYPE_BLOG_HAS_BLOGGER => self::TYPE_BLOGGER_HAS_BLOG,
self::TYPE_BLOGGER_HAS_BLOG => self::TYPE_BLOG_HAS_BLOGGER,
self::TYPE_PROJ_MEMBER => self::TYPE_MEMBER_OF_PROJ,
self::TYPE_MEMBER_OF_PROJ => self::TYPE_PROJ_MEMBER,
- self::TYPE_COMMIT_HAS_PROJECT => self::TYPE_PROJECT_HAS_COMMIT,
- self::TYPE_PROJECT_HAS_COMMIT => self::TYPE_COMMIT_HAS_PROJECT,
-
self::TYPE_QUESTION_HAS_VOTING_USER =>
self::TYPE_VOTING_USER_HAS_QUESTION,
self::TYPE_VOTING_USER_HAS_QUESTION =>
self::TYPE_QUESTION_HAS_VOTING_USER,
self::TYPE_ANSWER_HAS_VOTING_USER => self::TYPE_VOTING_USER_HAS_ANSWER,
self::TYPE_VOTING_USER_HAS_ANSWER => self::TYPE_ANSWER_HAS_VOTING_USER,
self::TYPE_OBJECT_HAS_SUBSCRIBER => self::TYPE_SUBSCRIBED_TO_OBJECT,
self::TYPE_SUBSCRIBED_TO_OBJECT => self::TYPE_OBJECT_HAS_SUBSCRIBER,
self::TYPE_OBJECT_HAS_UNSUBSCRIBER => self::TYPE_UNSUBSCRIBED_FROM_OBJECT,
self::TYPE_UNSUBSCRIBED_FROM_OBJECT => self::TYPE_OBJECT_HAS_UNSUBSCRIBER,
self::TYPE_OBJECT_HAS_FILE => self::TYPE_FILE_HAS_OBJECT,
self::TYPE_FILE_HAS_OBJECT => self::TYPE_OBJECT_HAS_FILE,
self::TYPE_DREV_HAS_COMMIT => self::TYPE_COMMIT_HAS_DREV,
self::TYPE_COMMIT_HAS_DREV => self::TYPE_DREV_HAS_COMMIT,
self::TYPE_OBJECT_HAS_CONTRIBUTOR => self::TYPE_CONTRIBUTED_TO_OBJECT,
self::TYPE_CONTRIBUTED_TO_OBJECT => self::TYPE_OBJECT_HAS_CONTRIBUTOR,
self::TYPE_TASK_HAS_MOCK => self::TYPE_MOCK_HAS_TASK,
self::TYPE_MOCK_HAS_TASK => self::TYPE_TASK_HAS_MOCK,
self::TYPE_PHOB_HAS_ASANATASK => self::TYPE_ASANATASK_HAS_PHOB,
self::TYPE_ASANATASK_HAS_PHOB => self::TYPE_PHOB_HAS_ASANATASK,
self::TYPE_PHOB_HAS_ASANASUBTASK => self::TYPE_ASANASUBTASK_HAS_PHOB,
self::TYPE_ASANASUBTASK_HAS_PHOB => self::TYPE_PHOB_HAS_ASANASUBTASK,
self::TYPE_DREV_HAS_REVIEWER => self::TYPE_REVIEWER_FOR_DREV,
self::TYPE_REVIEWER_FOR_DREV => self::TYPE_DREV_HAS_REVIEWER,
self::TYPE_PHOB_HAS_JIRAISSUE => self::TYPE_JIRAISSUE_HAS_PHOB,
self::TYPE_JIRAISSUE_HAS_PHOB => self::TYPE_PHOB_HAS_JIRAISSUE,
self::TYPE_OBJECT_USES_CREDENTIAL => self::TYPE_CREDENTIAL_USED_BY_OBJECT,
self::TYPE_CREDENTIAL_USED_BY_OBJECT => self::TYPE_OBJECT_USES_CREDENTIAL,
self::TYPE_PANEL_HAS_DASHBOARD => self::TYPE_DASHBOARD_HAS_PANEL,
self::TYPE_DASHBOARD_HAS_PANEL => self::TYPE_PANEL_HAS_DASHBOARD,
self::TYPE_OBJECT_HAS_WATCHER => self::TYPE_WATCHER_HAS_OBJECT,
self::TYPE_WATCHER_HAS_OBJECT => self::TYPE_OBJECT_HAS_WATCHER,
self::TYPE_OBJECT_NEEDS_SIGNATURE =>
self::TYPE_SIGNATURE_NEEDED_BY_OBJECT,
self::TYPE_SIGNATURE_NEEDED_BY_OBJECT =>
self::TYPE_OBJECT_NEEDS_SIGNATURE,
);
return idx($map, $edge_type);
}
private static function shouldPreventCycles($edge_type) {
static $map = array(
self::TYPE_TEST_NO_CYCLE => true,
self::TYPE_TASK_DEPENDS_ON_TASK => true,
self::TYPE_DREV_DEPENDS_ON_DREV => true,
);
return isset($map[$edge_type]);
}
public static function establishConnection($phid_type, $conn_type) {
$map = PhabricatorPHIDType::getAllTypes();
if (isset($map[$phid_type])) {
$type = $map[$phid_type];
$object = $type->newObject();
if ($object) {
return $object->establishConnection($conn_type);
}
}
static $class_map = array(
PhabricatorPHIDConstants::PHID_TYPE_TOBJ => 'HarbormasterObject',
PhabricatorPHIDConstants::PHID_TYPE_XOBJ => 'DoorkeeperExternalObject',
);
$class = idx($class_map, $phid_type);
if (!$class) {
throw new Exception(
"Edges are not available for objects of type '{$phid_type}'!");
}
return newv($class, array())->establishConnection($conn_type);
}
public static function getEditStringForEdgeType($type) {
switch ($type) {
- case self::TYPE_PROJECT_HAS_COMMIT:
case self::TYPE_DREV_HAS_COMMIT:
return '%s edited commit(s), added %d: %s; removed %d: %s.';
case self::TYPE_TASK_DEPENDS_ON_TASK:
case self::TYPE_TASK_DEPENDED_ON_BY_TASK:
case self::TYPE_MOCK_HAS_TASK:
return '%s edited task(s), added %d: %s; removed %d: %s.';
case self::TYPE_DREV_DEPENDS_ON_DREV:
case self::TYPE_DREV_DEPENDED_ON_BY_DREV:
case self::TYPE_COMMIT_HAS_DREV:
case self::TYPE_REVIEWER_FOR_DREV:
return '%s edited revision(s), added %d: %s; removed %d: %s.';
case self::TYPE_BLOG_HAS_POST:
return '%s edited post(s), added %d: %s; removed %d: %s.';
case self::TYPE_POST_HAS_BLOG:
case self::TYPE_BLOGGER_HAS_BLOG:
return '%s edited blog(s), added %d: %s; removed %d: %s.';
case self::TYPE_BLOG_HAS_BLOGGER:
return '%s edited blogger(s), added %d: %s; removed %d: %s.';
case self::TYPE_PROJ_MEMBER:
return '%s edited member(s), added %d: %s; removed %d: %s.';
case self::TYPE_MEMBER_OF_PROJ:
- case self::TYPE_COMMIT_HAS_PROJECT:
return '%s edited project(s), added %d: %s; removed %d: %s.';
case self::TYPE_QUESTION_HAS_VOTING_USER:
case self::TYPE_ANSWER_HAS_VOTING_USER:
return '%s edited voting user(s), added %d: %s; removed %d: %s.';
case self::TYPE_VOTING_USER_HAS_QUESTION:
return '%s edited question(s), added %d: %s; removed %d: %s.';
case self::TYPE_VOTING_USER_HAS_ANSWER:
return '%s edited answer(s), added %d: %s; removed %d: %s.';
case self::TYPE_OBJECT_HAS_SUBSCRIBER:
return '%s edited subscriber(s), added %d: %s; removed %d: %s.';
case self::TYPE_SUBSCRIBED_TO_OBJECT:
case self::TYPE_UNSUBSCRIBED_FROM_OBJECT:
case self::TYPE_FILE_HAS_OBJECT:
case self::TYPE_CONTRIBUTED_TO_OBJECT:
return '%s edited object(s), added %d: %s; removed %d: %s.';
case self::TYPE_OBJECT_HAS_UNSUBSCRIBER:
return '%s edited unsubcriber(s), added %d: %s; removed %d: %s.';
case self::TYPE_OBJECT_HAS_FILE:
return '%s edited file(s), added %d: %s; removed %d: %s.';
case self::TYPE_PURCAHSE_HAS_CHARGE:
return '%s edited charge(s), added %d: %s; removed %d: %s.';
case self::TYPE_CHARGE_HAS_PURCHASE:
return '%s edited purchase(s), added %d: %s; removed %d: %s.';
case self::TYPE_OBJECT_HAS_CONTRIBUTOR:
return '%s edited contributor(s), added %d: %s; removed %d: %s.';
case self::TYPE_DREV_HAS_REVIEWER:
return '%s edited reviewer(s), added %d: %s; removed %d: %s.';
case self::TYPE_TASK_HAS_MOCK:
return '%s edited mock(s), added %d: %s; removed %d: %s.';
case self::TYPE_DASHBOARD_HAS_PANEL:
return '%s edited panel(s), added %d: %s; removed %d: %s.';
case self::TYPE_PANEL_HAS_DASHBOARD:
return '%s edited dashboard(s), added %d: %s; removed %d: %s.';
case self::TYPE_SUBSCRIBED_TO_OBJECT:
case self::TYPE_UNSUBSCRIBED_FROM_OBJECT:
case self::TYPE_FILE_HAS_OBJECT:
case self::TYPE_CONTRIBUTED_TO_OBJECT:
default:
return '%s edited object(s), added %d: %s; removed %d: %s.';
}
}
public static function getAddStringForEdgeType($type) {
switch ($type) {
- case self::TYPE_PROJECT_HAS_COMMIT:
case self::TYPE_DREV_HAS_COMMIT:
return '%s added %d commit(s): %s.';
case self::TYPE_TASK_DEPENDS_ON_TASK:
return '%s added %d blocking task(s): %s.';
case self::TYPE_DREV_DEPENDS_ON_DREV:
return '%s added %d dependencie(s): %s.';
case self::TYPE_TASK_DEPENDED_ON_BY_TASK:
return '%s added %d blocked task(s): %s.';
case self::TYPE_MOCK_HAS_TASK:
return '%s added %d task(s): %s.';
case self::TYPE_DREV_DEPENDED_ON_BY_DREV:
case self::TYPE_COMMIT_HAS_DREV:
case self::TYPE_REVIEWER_FOR_DREV:
return '%s added %d revision(s): %s.';
case self::TYPE_BLOG_HAS_POST:
return '%s added %d post(s): %s.';
case self::TYPE_POST_HAS_BLOG:
case self::TYPE_BLOGGER_HAS_BLOG:
return '%s added %d blog(s): %s.';
case self::TYPE_BLOG_HAS_BLOGGER:
return '%s added %d blogger(s): %s.';
case self::TYPE_PROJ_MEMBER:
return '%s added %d member(s): %s.';
case self::TYPE_MEMBER_OF_PROJ:
- case self::TYPE_COMMIT_HAS_PROJECT:
return '%s added %d project(s): %s.';
case self::TYPE_QUESTION_HAS_VOTING_USER:
case self::TYPE_ANSWER_HAS_VOTING_USER:
return '%s added %d voting user(s): %s.';
case self::TYPE_VOTING_USER_HAS_QUESTION:
return '%s added %d question(s): %s.';
case self::TYPE_VOTING_USER_HAS_ANSWER:
return '%s added %d answer(s): %s.';
case self::TYPE_OBJECT_HAS_SUBSCRIBER:
return '%s added %d subscriber(s): %s.';
case self::TYPE_OBJECT_HAS_UNSUBSCRIBER:
return '%s added %d unsubcriber(s): %s.';
case self::TYPE_OBJECT_HAS_FILE:
return '%s added %d file(s): %s.';
case self::TYPE_PURCAHSE_HAS_CHARGE:
return '%s added %d charge(s): %s.';
case self::TYPE_CHARGE_HAS_PURCHASE:
return '%s added %d purchase(s): %s.';
case self::TYPE_OBJECT_HAS_CONTRIBUTOR:
return '%s added %d contributor(s): %s.';
case self::TYPE_DREV_HAS_REVIEWER:
return '%s added %d reviewer(s): %s.';
case self::TYPE_TASK_HAS_MOCK:
return '%s added %d mock(s): %s.';
case self::TYPE_DASHBOARD_HAS_PANEL:
return '%s added %d panel(s): %s.';
case self::TYPE_PANEL_HAS_DASHBOARD:
return '%s added %d dashboard(s): %s.';
case self::TYPE_OBJECT_HAS_WATCHER:
return '%s added %d watcher(s): %s.';
case self::TYPE_OBJECT_NEEDS_SIGNATURE:
return '%s added %d required legal document(s): %s.';
case self::TYPE_SUBSCRIBED_TO_OBJECT:
case self::TYPE_UNSUBSCRIBED_FROM_OBJECT:
case self::TYPE_FILE_HAS_OBJECT:
case self::TYPE_CONTRIBUTED_TO_OBJECT:
default:
return '%s added %d object(s): %s.';
}
}
public static function getRemoveStringForEdgeType($type) {
switch ($type) {
- case self::TYPE_PROJECT_HAS_COMMIT:
case self::TYPE_DREV_HAS_COMMIT:
return '%s removed %d commit(s): %s.';
case self::TYPE_TASK_DEPENDS_ON_TASK:
return '%s removed %d blocking task(s): %s.';
case self::TYPE_TASK_DEPENDED_ON_BY_TASK:
return '%s removed %d blocked task(s): %s.';
case self::TYPE_MOCK_HAS_TASK:
return '%s removed %d task(s): %s.';
case self::TYPE_DREV_DEPENDS_ON_DREV:
case self::TYPE_DREV_DEPENDED_ON_BY_DREV:
case self::TYPE_COMMIT_HAS_DREV:
case self::TYPE_REVIEWER_FOR_DREV:
return '%s removed %d revision(s): %s.';
case self::TYPE_BLOG_HAS_POST:
return '%s removed %d post(s): %s.';
case self::TYPE_POST_HAS_BLOG:
case self::TYPE_BLOGGER_HAS_BLOG:
return '%s removed %d blog(s): %s.';
case self::TYPE_BLOG_HAS_BLOGGER:
return '%s removed %d blogger(s): %s.';
case self::TYPE_PROJ_MEMBER:
return '%s removed %d member(s): %s.';
case self::TYPE_MEMBER_OF_PROJ:
- case self::TYPE_COMMIT_HAS_PROJECT:
return '%s removed %d project(s): %s.';
case self::TYPE_QUESTION_HAS_VOTING_USER:
case self::TYPE_ANSWER_HAS_VOTING_USER:
return '%s removed %d voting user(s): %s.';
case self::TYPE_VOTING_USER_HAS_QUESTION:
return '%s removed %d question(s): %s.';
case self::TYPE_VOTING_USER_HAS_ANSWER:
return '%s removed %d answer(s): %s.';
case self::TYPE_OBJECT_HAS_SUBSCRIBER:
return '%s removed %d subscriber(s): %s.';
case self::TYPE_OBJECT_HAS_UNSUBSCRIBER:
return '%s removed %d unsubcriber(s): %s.';
case self::TYPE_OBJECT_HAS_FILE:
return '%s removed %d file(s): %s.';
case self::TYPE_PURCAHSE_HAS_CHARGE:
return '%s removed %d charge(s): %s.';
case self::TYPE_CHARGE_HAS_PURCHASE:
return '%s removed %d purchase(s): %s.';
case self::TYPE_OBJECT_HAS_CONTRIBUTOR:
return '%s removed %d contributor(s): %s.';
case self::TYPE_DREV_HAS_REVIEWER:
return '%s removed %d reviewer(s): %s.';
case self::TYPE_TASK_HAS_MOCK:
return '%s removed %d mock(s): %s.';
case self::TYPE_DASHBOARD_HAS_PANEL:
return '%s removed %d panel(s): %s.';
case self::TYPE_PANEL_HAS_DASHBOARD:
return '%s removed %d dashboard(s): %s.';
case self::TYPE_OBJECT_HAS_WATCHER:
return '%s removed %d watcher(s): %s.';
case self::TYPE_SUBSCRIBED_TO_OBJECT:
case self::TYPE_UNSUBSCRIBED_FROM_OBJECT:
case self::TYPE_FILE_HAS_OBJECT:
case self::TYPE_CONTRIBUTED_TO_OBJECT:
default:
return '%s removed %d object(s): %s.';
}
}
public static function getFeedStringForEdgeType($type) {
switch ($type) {
- case self::TYPE_PROJECT_HAS_COMMIT:
case self::TYPE_DREV_HAS_COMMIT:
return '%s updated commits of %s.';
case self::TYPE_TASK_DEPENDS_ON_TASK:
case self::TYPE_TASK_DEPENDED_ON_BY_TASK:
case self::TYPE_MOCK_HAS_TASK:
return '%s updated tasks of %s.';
case self::TYPE_DREV_DEPENDS_ON_DREV:
case self::TYPE_DREV_DEPENDED_ON_BY_DREV:
case self::TYPE_COMMIT_HAS_DREV:
case self::TYPE_REVIEWER_FOR_DREV:
return '%s updated revisions of %s.';
case self::TYPE_BLOG_HAS_POST:
return '%s updated posts of %s.';
case self::TYPE_POST_HAS_BLOG:
case self::TYPE_BLOGGER_HAS_BLOG:
return '%s updated blogs of %s.';
case self::TYPE_BLOG_HAS_BLOGGER:
return '%s updated bloggers of %s.';
case self::TYPE_PROJ_MEMBER:
return '%s updated members of %s.';
case self::TYPE_MEMBER_OF_PROJ:
- case self::TYPE_COMMIT_HAS_PROJECT:
return '%s updated projects of %s.';
case self::TYPE_QUESTION_HAS_VOTING_USER:
case self::TYPE_ANSWER_HAS_VOTING_USER:
return '%s updated voting users of %s.';
case self::TYPE_VOTING_USER_HAS_QUESTION:
return '%s updated questions of %s.';
case self::TYPE_VOTING_USER_HAS_ANSWER:
return '%s updated answers of %s.';
case self::TYPE_OBJECT_HAS_SUBSCRIBER:
return '%s updated subscribers of %s.';
case self::TYPE_OBJECT_HAS_UNSUBSCRIBER:
return '%s updated unsubcribers of %s.';
case self::TYPE_OBJECT_HAS_FILE:
return '%s updated files of %s.';
case self::TYPE_PURCAHSE_HAS_CHARGE:
return '%s updated charges of %s.';
case self::TYPE_CHARGE_HAS_PURCHASE:
return '%s updated purchases of %s.';
case self::TYPE_OBJECT_HAS_CONTRIBUTOR:
return '%s updated contributors of %s.';
case self::TYPE_DREV_HAS_REVIEWER:
return '%s updated reviewers of %s.';
case self::TYPE_TASK_HAS_MOCK:
return '%s updated mocks of %s.';
case self::TYPE_PANEL_HAS_DASHBOARD:
return '%s updated panels for %s.';
case self::TYPE_PANEL_HAS_DASHBOARD:
return '%s updated dashboards for %s.';
case self::TYPE_OBJECT_HAS_WATCHER:
return '%s updated watchers for %s.';
case self::TYPE_SUBSCRIBED_TO_OBJECT:
case self::TYPE_UNSUBSCRIBED_FROM_OBJECT:
case self::TYPE_FILE_HAS_OBJECT:
case self::TYPE_CONTRIBUTED_TO_OBJECT:
default:
return '%s updated objects of %s.';
}
}
}