2011-01-26 02:17:19 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle major edit operations to DifferentialRevision -- adding and removing
|
|
|
|
* reviewers, diffs, and CCs. Unlike simple edits, these changes trigger
|
|
|
|
* complicated email workflows.
|
|
|
|
*/
|
2012-10-10 19:18:23 +02:00
|
|
|
final class DifferentialRevisionEditor extends PhabricatorEditor {
|
2011-01-26 02:17:19 +01:00
|
|
|
|
|
|
|
protected $revision;
|
|
|
|
|
|
|
|
protected $cc = null;
|
|
|
|
protected $reviewers = null;
|
|
|
|
protected $diff;
|
|
|
|
protected $comments;
|
|
|
|
protected $silentUpdate;
|
|
|
|
|
2011-08-10 22:46:01 +02:00
|
|
|
private $auxiliaryFields = array();
|
Track content sources (email, web, conduit, mobile) for replies
Summary:
When an object is updated, record the content source for the update. This mostly
isn't terribly useful but one concrete thing I want to do with it is let admins
audit via-email replies more easily since there are a bunch of options which let
you do hyjinx if you intentionally configure them insecurely. I think having a
little more auditability around this feature is generally good. At some point
I'm going to turn this into a link admins can click to see details.
It also allows us to see how frequently different mechanisms are used, and lets
you see if someone is at their desk or on a mobile or whatever, at least
indirectly.
The "tablet" and "mobile" sources are currently unused but I figured I'd throw
them in anyway. SMS support should definitely happen at some point.
Not 100% sure about the design for this, I might change it to plain text at some
point.
Test Plan: Updated objects and saw update sources rendered.
Reviewers: jungejason, tuomaspelkonen, aran
Reviewed By: jungejason
CC: aran, epriestley, jungejason
Differential Revision: 844
2011-08-22 19:25:45 +02:00
|
|
|
private $contentSource;
|
2011-08-10 22:46:01 +02:00
|
|
|
|
2012-10-10 19:18:23 +02:00
|
|
|
public function __construct(DifferentialRevision $revision) {
|
2011-01-26 02:17:19 +01:00
|
|
|
$this->revision = $revision;
|
|
|
|
}
|
|
|
|
|
2011-02-06 22:49:23 +01:00
|
|
|
public static function newRevisionFromConduitWithDiff(
|
|
|
|
array $fields,
|
|
|
|
DifferentialDiff $diff,
|
2012-10-10 19:18:23 +02:00
|
|
|
PhabricatorUser $actor) {
|
2011-02-06 22:49:23 +01:00
|
|
|
|
|
|
|
$revision = new DifferentialRevision();
|
|
|
|
$revision->setPHID($revision->generatePHID());
|
2012-10-10 19:18:23 +02:00
|
|
|
$revision->setAuthorPHID($actor->getPHID());
|
2012-01-10 20:39:11 +01:00
|
|
|
$revision->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
|
2011-02-06 22:49:23 +01:00
|
|
|
|
2012-10-10 19:18:23 +02:00
|
|
|
$editor = new DifferentialRevisionEditor($revision);
|
|
|
|
$editor->setActor($actor);
|
2013-05-31 02:33:17 +02:00
|
|
|
$editor->addDiff($diff, null);
|
2011-02-06 22:49:23 +01:00
|
|
|
$editor->copyFieldsFromConduit($fields);
|
|
|
|
|
|
|
|
$editor->save();
|
|
|
|
|
|
|
|
return $revision;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function copyFieldsFromConduit(array $fields) {
|
|
|
|
|
2012-10-10 19:18:23 +02:00
|
|
|
$actor = $this->getActor();
|
2011-02-06 22:49:23 +01:00
|
|
|
$revision = $this->revision;
|
Drive commit message rendering from field specifications
Summary:
When rendering commit messages, drive all the logic through field specification
classes instead of the hard-coded DifferentialCommitMessageData class. This
removes DifferentialCommitMessageData and support classes.
Note that this effectively reverts D546, and will cause a minor break for
Facebook (Task IDs will no longer render in commit messages generated by "arc
amend", and will not be editable via "arc diff --edit"). This can be resolved by
implementing the feature as a custom field. While I've been able to preserve the
task ID functionality elsewhere, I felt this implementation was too complex to
reasonably leave hooks for, and the break is pretty minor.
Test Plan:
- Made numerous calls to differential.getcommitmessage across many diffs in
various states, with and without 'edit' and with and without various field
overrides.
- General behavior seems correct (messages look accurate, and have the
expected information). Special fields like "Reviewed By" and "git-svn-id" seem
to work correctly.
- Edit behavior seems correct (edit mode shows all editable fields, hides
fields like "Reviewed By").
- Field overwrite behavior seems correct (overwritable fields show the correct
values when overwritten, ignore provided values otherwise).
Reviewed By: jungejason
Reviewers: jungejason, tuomaspelkonen, aran
CC: aran, jungejason
Differential Revision: 814
2011-08-16 06:06:58 +02:00
|
|
|
$revision->loadRelationships();
|
2011-02-06 22:49:23 +01:00
|
|
|
|
2013-05-31 02:33:17 +02:00
|
|
|
$all_fields = DifferentialFieldSelector::newSelector()
|
2011-08-15 03:52:09 +02:00
|
|
|
->getFieldSpecifications();
|
|
|
|
|
2013-05-31 02:33:17 +02:00
|
|
|
$aux_fields = array();
|
|
|
|
foreach ($all_fields as $aux_field) {
|
2011-08-15 03:52:09 +02:00
|
|
|
$aux_field->setRevision($revision);
|
2013-05-31 02:33:17 +02:00
|
|
|
$aux_field->setDiff($this->diff);
|
2012-10-10 19:18:23 +02:00
|
|
|
$aux_field->setUser($actor);
|
2013-05-31 02:33:17 +02:00
|
|
|
if ($aux_field->shouldAppearOnCommitMessage()) {
|
|
|
|
$aux_fields[$aux_field->getCommitMessageKey()] = $aux_field;
|
2011-08-15 03:52:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($fields as $field => $value) {
|
|
|
|
if (empty($aux_fields[$field])) {
|
|
|
|
throw new Exception(
|
|
|
|
"Parsed commit message contains unrecognized field '{$field}'.");
|
|
|
|
}
|
|
|
|
$aux_fields[$field]->setValueFromParsedCommitMessage($value);
|
|
|
|
}
|
|
|
|
|
2011-12-02 00:43:25 +01:00
|
|
|
foreach ($aux_fields as $aux_field) {
|
|
|
|
$aux_field->validateField();
|
|
|
|
}
|
|
|
|
|
2013-05-31 02:33:17 +02:00
|
|
|
$this->setAuxiliaryFields($all_fields);
|
2011-02-06 22:49:23 +01:00
|
|
|
}
|
|
|
|
|
2011-08-10 22:46:01 +02:00
|
|
|
public function setAuxiliaryFields(array $auxiliary_fields) {
|
2012-04-04 01:35:35 +02:00
|
|
|
assert_instances_of($auxiliary_fields, 'DifferentialFieldSpecification');
|
2011-08-10 22:46:01 +02:00
|
|
|
$this->auxiliaryFields = $auxiliary_fields;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
public function getRevision() {
|
|
|
|
return $this->revision;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setReviewers(array $reviewers) {
|
|
|
|
$this->reviewers = $reviewers;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setCCPHIDs(array $cc) {
|
|
|
|
$this->cc = $cc;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
Track content sources (email, web, conduit, mobile) for replies
Summary:
When an object is updated, record the content source for the update. This mostly
isn't terribly useful but one concrete thing I want to do with it is let admins
audit via-email replies more easily since there are a bunch of options which let
you do hyjinx if you intentionally configure them insecurely. I think having a
little more auditability around this feature is generally good. At some point
I'm going to turn this into a link admins can click to see details.
It also allows us to see how frequently different mechanisms are used, and lets
you see if someone is at their desk or on a mobile or whatever, at least
indirectly.
The "tablet" and "mobile" sources are currently unused but I figured I'd throw
them in anyway. SMS support should definitely happen at some point.
Not 100% sure about the design for this, I might change it to plain text at some
point.
Test Plan: Updated objects and saw update sources rendered.
Reviewers: jungejason, tuomaspelkonen, aran
Reviewed By: jungejason
CC: aran, epriestley, jungejason
Differential Revision: 844
2011-08-22 19:25:45 +02:00
|
|
|
public function setContentSource(PhabricatorContentSource $content_source) {
|
|
|
|
$this->contentSource = $content_source;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
public function addDiff(DifferentialDiff $diff, $comments) {
|
|
|
|
if ($diff->getRevisionID() &&
|
|
|
|
$diff->getRevisionID() != $this->getRevision()->getID()) {
|
|
|
|
$diff_id = (int)$diff->getID();
|
|
|
|
$targ_id = (int)$this->getRevision()->getID();
|
|
|
|
$real_id = (int)$diff->getRevisionID();
|
|
|
|
throw new Exception(
|
|
|
|
"Can not attach diff #{$diff_id} to Revision D{$targ_id}, it is ".
|
|
|
|
"already attached to D{$real_id}.");
|
|
|
|
}
|
|
|
|
$this->diff = $diff;
|
|
|
|
$this->comments = $comments;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getDiff() {
|
|
|
|
return $this->diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getComments() {
|
|
|
|
return $this->comments;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getActorPHID() {
|
2012-10-11 23:34:16 +02:00
|
|
|
return $this->getActor()->getPHID();
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function isNewRevision() {
|
|
|
|
return !$this->getRevision()->getID();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A silent update does not trigger Herald rules or send emails. This is used
|
|
|
|
* for auto-amends at commit time.
|
|
|
|
*/
|
|
|
|
public function setSilentUpdate($silent) {
|
|
|
|
$this->silentUpdate = $silent;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function save() {
|
|
|
|
$revision = $this->getRevision();
|
|
|
|
|
|
|
|
$is_new = $this->isNewRevision();
|
|
|
|
if ($is_new) {
|
2012-02-03 01:03:44 +01:00
|
|
|
$this->initializeNewRevision($revision);
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$revision->loadRelationships();
|
|
|
|
|
2011-08-14 22:55:30 +02:00
|
|
|
$this->willWriteRevision();
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
if ($this->reviewers === null) {
|
|
|
|
$this->reviewers = $revision->getReviewers();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->cc === null) {
|
|
|
|
$this->cc = $revision->getCCPHIDs();
|
|
|
|
}
|
|
|
|
|
2012-11-29 08:01:07 +01:00
|
|
|
if ($is_new) {
|
|
|
|
$content_blocks = array();
|
|
|
|
foreach ($this->auxiliaryFields as $field) {
|
|
|
|
if ($field->shouldExtractMentions()) {
|
|
|
|
$content_blocks[] = $field->renderValueForCommitMessage(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$phids = PhabricatorMarkupEngine::extractPHIDsFromMentions(
|
|
|
|
$content_blocks);
|
|
|
|
$this->cc = array_unique(array_merge($this->cc, $phids));
|
|
|
|
}
|
|
|
|
|
2012-02-03 01:03:44 +01:00
|
|
|
$diff = $this->getDiff();
|
|
|
|
if ($diff) {
|
|
|
|
$revision->setLineCount($diff->getLineCount());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save the revision, to generate its ID and PHID if it is new. We need
|
|
|
|
// the ID/PHID in order to record them in Herald transcripts, but don't
|
|
|
|
// want to hold a transaction open while running Herald because it is
|
|
|
|
// potentially somewhat slow. The downside is that we may end up with a
|
|
|
|
// saved revision/diff pair without appropriate CCs. We could be better
|
|
|
|
// about this -- for example:
|
|
|
|
//
|
|
|
|
// - Herald can't affect reviewers, so we could compute them before
|
|
|
|
// opening the transaction and then save them in the transaction.
|
|
|
|
// - Herald doesn't *really* need PHIDs to compute its effects, we could
|
|
|
|
// run it before saving these objects and then hand over the PHIDs later.
|
|
|
|
//
|
|
|
|
// But this should address the problem of orphaned revisions, which is
|
|
|
|
// currently the only problem we experience in practice.
|
|
|
|
|
|
|
|
$revision->openTransaction();
|
|
|
|
|
2012-04-10 21:51:34 +02:00
|
|
|
if ($diff) {
|
|
|
|
$revision->setBranchName($diff->getBranch());
|
|
|
|
$revision->setArcanistProjectPHID($diff->getArcanistProjectPHID());
|
|
|
|
}
|
|
|
|
|
2012-02-03 01:03:44 +01:00
|
|
|
$revision->save();
|
2012-04-10 21:51:34 +02:00
|
|
|
|
2012-02-03 01:03:44 +01:00
|
|
|
if ($diff) {
|
|
|
|
$diff->setRevisionID($revision->getID());
|
|
|
|
$diff->save();
|
|
|
|
}
|
|
|
|
|
|
|
|
$revision->saveTransaction();
|
|
|
|
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
// We're going to build up three dictionaries: $add, $rem, and $stable. The
|
|
|
|
// $add dictionary has added reviewers/CCs. The $rem dictionary has
|
|
|
|
// reviewers/CCs who have been removed, and the $stable array is
|
|
|
|
// reviewers/CCs who haven't changed. We're going to send new reviewers/CCs
|
|
|
|
// a different ("welcome") email than we send stable reviewers/CCs.
|
|
|
|
|
|
|
|
$old = array(
|
|
|
|
'rev' => array_fill_keys($revision->getReviewers(), true),
|
|
|
|
'ccs' => array_fill_keys($revision->getCCPHIDs(), true),
|
|
|
|
);
|
|
|
|
|
|
|
|
$xscript_header = null;
|
|
|
|
$xscript_uri = null;
|
|
|
|
|
|
|
|
$new = array(
|
|
|
|
'rev' => array_fill_keys($this->reviewers, true),
|
|
|
|
'ccs' => array_fill_keys($this->cc, true),
|
|
|
|
);
|
|
|
|
|
|
|
|
$rem_ccs = array();
|
2012-03-26 18:55:38 +02:00
|
|
|
$xscript_phid = null;
|
2011-01-26 02:17:19 +01:00
|
|
|
if ($diff) {
|
2011-04-08 03:55:43 +02:00
|
|
|
$adapter = new HeraldDifferentialRevisionAdapter(
|
|
|
|
$revision,
|
|
|
|
$diff);
|
|
|
|
$adapter->setExplicitCCs($new['ccs']);
|
|
|
|
$adapter->setExplicitReviewers($new['rev']);
|
2013-02-02 01:48:10 +01:00
|
|
|
$adapter->setForbiddenCCs($revision->loadUnsubscribedPHIDs());
|
2011-04-08 03:55:43 +02:00
|
|
|
|
|
|
|
$xscript = HeraldEngine::loadAndApplyRules($adapter);
|
2012-07-17 04:01:43 +02:00
|
|
|
$xscript_uri = '/herald/transcript/'.$xscript->getID().'/';
|
2011-01-26 02:17:19 +01:00
|
|
|
$xscript_phid = $xscript->getPHID();
|
|
|
|
$xscript_header = $xscript->getXHeraldRulesHeader();
|
|
|
|
|
2011-08-16 22:05:12 +02:00
|
|
|
$xscript_header = HeraldTranscript::saveXHeraldRulesHeader(
|
2011-05-03 06:04:06 +02:00
|
|
|
$revision->getPHID(),
|
|
|
|
$xscript_header);
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
$sub = array(
|
|
|
|
'rev' => array(),
|
2011-04-08 03:55:43 +02:00
|
|
|
'ccs' => $adapter->getCCsAddedByHerald(),
|
2011-01-26 02:17:19 +01:00
|
|
|
);
|
2011-04-08 03:55:43 +02:00
|
|
|
$rem_ccs = $adapter->getCCsRemovedByHerald();
|
2011-01-26 02:17:19 +01:00
|
|
|
} else {
|
|
|
|
$sub = array(
|
|
|
|
'rev' => array(),
|
|
|
|
'ccs' => array(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove any CCs which are prevented by Herald rules.
|
|
|
|
$sub['ccs'] = array_diff_key($sub['ccs'], $rem_ccs);
|
|
|
|
$new['ccs'] = array_diff_key($new['ccs'], $rem_ccs);
|
|
|
|
|
|
|
|
$add = array();
|
|
|
|
$rem = array();
|
|
|
|
$stable = array();
|
|
|
|
foreach (array('rev', 'ccs') as $key) {
|
|
|
|
$add[$key] = array();
|
|
|
|
if ($new[$key] !== null) {
|
|
|
|
$add[$key] += array_diff_key($new[$key], $old[$key]);
|
|
|
|
}
|
|
|
|
$add[$key] += array_diff_key($sub[$key], $old[$key]);
|
|
|
|
|
|
|
|
$combined = $sub[$key];
|
|
|
|
if ($new[$key] !== null) {
|
|
|
|
$combined += $new[$key];
|
|
|
|
}
|
|
|
|
$rem[$key] = array_diff_key($old[$key], $combined);
|
|
|
|
|
|
|
|
$stable[$key] = array_diff_key($old[$key], $add[$key] + $rem[$key]);
|
|
|
|
}
|
|
|
|
|
2013-07-10 22:45:14 +02:00
|
|
|
self::updateReviewers(
|
2011-01-26 02:17:19 +01:00
|
|
|
$revision,
|
2013-07-10 22:45:14 +02:00
|
|
|
$this->getActor(),
|
2011-01-26 02:17:19 +01:00
|
|
|
array_keys($add['rev']),
|
2013-07-10 22:45:14 +02:00
|
|
|
array_keys($rem['rev']),
|
2012-10-11 23:34:16 +02:00
|
|
|
$this->getActorPHID());
|
2011-01-26 02:17:19 +01:00
|
|
|
|
2012-03-26 18:55:38 +02:00
|
|
|
// We want to attribute new CCs to a "reasonPHID", representing the reason
|
|
|
|
// they were added. This is either a user (if some user explicitly CCs
|
|
|
|
// them, or uses "Add CCs...") or a Herald transcript PHID, indicating that
|
|
|
|
// they were added by a Herald rule.
|
2011-04-08 03:55:43 +02:00
|
|
|
|
2011-02-05 03:16:02 +01:00
|
|
|
if ($add['ccs'] || $rem['ccs']) {
|
2012-03-26 18:55:38 +02:00
|
|
|
$reasons = array();
|
|
|
|
foreach ($add['ccs'] as $phid => $ignored) {
|
|
|
|
if (empty($new['ccs'][$phid])) {
|
|
|
|
$reasons[$phid] = $xscript_phid;
|
2011-02-05 03:16:02 +01:00
|
|
|
} else {
|
2012-10-11 23:34:16 +02:00
|
|
|
$reasons[$phid] = $this->getActorPHID();
|
2011-02-05 03:16:02 +01:00
|
|
|
}
|
|
|
|
}
|
2012-03-26 18:55:38 +02:00
|
|
|
foreach ($rem['ccs'] as $phid => $ignored) {
|
|
|
|
if (empty($new['ccs'][$phid])) {
|
2012-10-11 23:34:16 +02:00
|
|
|
$reasons[$phid] = $this->getActorPHID();
|
2011-02-05 03:16:02 +01:00
|
|
|
} else {
|
2012-03-26 18:55:38 +02:00
|
|
|
$reasons[$phid] = $xscript_phid;
|
2011-02-05 03:16:02 +01:00
|
|
|
}
|
|
|
|
}
|
2012-03-26 18:55:38 +02:00
|
|
|
} else {
|
2012-10-11 23:34:16 +02:00
|
|
|
$reasons = $this->getActorPHID();
|
2011-02-05 03:16:02 +01:00
|
|
|
}
|
2012-03-26 18:55:38 +02:00
|
|
|
|
2011-02-05 03:16:02 +01:00
|
|
|
self::alterCCs(
|
|
|
|
$revision,
|
|
|
|
$this->cc,
|
|
|
|
array_keys($rem['ccs']),
|
|
|
|
array_keys($add['ccs']),
|
2012-03-26 18:55:38 +02:00
|
|
|
$reasons);
|
2011-02-05 03:16:02 +01:00
|
|
|
|
2011-08-10 22:46:01 +02:00
|
|
|
$this->updateAuxiliaryFields();
|
|
|
|
|
2011-05-28 00:52:26 +02:00
|
|
|
// Add the author and users included from Herald rules to the relevant set
|
|
|
|
// of users so they get a copy of the email.
|
2011-01-26 02:17:19 +01:00
|
|
|
if (!$this->silentUpdate) {
|
|
|
|
if ($is_new) {
|
|
|
|
$add['rev'][$this->getActorPHID()] = true;
|
2011-05-28 00:52:26 +02:00
|
|
|
if ($diff) {
|
|
|
|
$add['rev'] += $adapter->getEmailPHIDsAddedByHerald();
|
|
|
|
}
|
2011-01-26 02:17:19 +01:00
|
|
|
} else {
|
|
|
|
$stable['rev'][$this->getActorPHID()] = true;
|
2011-05-28 00:52:26 +02:00
|
|
|
if ($diff) {
|
|
|
|
$stable['rev'] += $adapter->getEmailPHIDsAddedByHerald();
|
|
|
|
}
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$mail = array();
|
|
|
|
|
2011-02-10 07:46:18 +01:00
|
|
|
$phids = array($this->getActorPHID());
|
|
|
|
|
|
|
|
$handles = id(new PhabricatorObjectHandleData($phids))
|
2013-03-01 02:15:09 +01:00
|
|
|
->setViewer($this->getActor())
|
2011-02-10 07:46:18 +01:00
|
|
|
->loadHandles();
|
|
|
|
$actor_handle = $handles[$this->getActorPHID()];
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
$changesets = null;
|
2011-02-05 21:33:53 +01:00
|
|
|
$comment = null;
|
2011-01-26 02:17:19 +01:00
|
|
|
if ($diff) {
|
|
|
|
$changesets = $diff->loadChangesets();
|
2011-02-05 02:53:14 +01:00
|
|
|
// TODO: This should probably be in DifferentialFeedbackEditor?
|
2011-01-26 02:17:19 +01:00
|
|
|
if (!$is_new) {
|
2011-02-05 02:53:14 +01:00
|
|
|
$comment = $this->createComment();
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
2011-02-05 21:33:53 +01:00
|
|
|
if ($comment) {
|
2011-01-26 02:17:19 +01:00
|
|
|
$mail[] = id(new DifferentialNewDiffMail(
|
|
|
|
$revision,
|
2011-02-10 07:46:18 +01:00
|
|
|
$actor_handle,
|
2011-01-26 02:17:19 +01:00
|
|
|
$changesets))
|
2013-03-01 02:15:09 +01:00
|
|
|
->setActor($this->getActor())
|
2011-01-26 02:17:19 +01:00
|
|
|
->setIsFirstMailAboutRevision($is_new)
|
|
|
|
->setIsFirstMailToRecipients($is_new)
|
|
|
|
->setComments($this->getComments())
|
|
|
|
->setToPHIDs(array_keys($stable['rev']))
|
|
|
|
->setCCPHIDs(array_keys($stable['ccs']));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save the changes we made above.
|
|
|
|
|
Use only the first line of update comment in revision history description
Summary:
More complicated updates deserves title and explanation.
This diff uses only the first line from comment in diff's description.
It also removes the truncating at 80 chars which looks as an error.
Test Plan:
var_dump(preg_replace('/\n.*/s', '', "abc"));
var_dump(preg_replace('/\n.*/s', '', "abc\ndef"));
var_dump(preg_replace('/\n.*/s', '', "abc\ndef"));
var_dump(preg_replace('/\n.*/s', '', "abc\ndef\nghi"));
var_dump(preg_replace('/\n.*/s', '', "\nabc")); // empty string
Display revision with 255 chars description in update history - it looks OK.
Reviewers: epriestley
Reviewed By: epriestley
CC: aran, epriestley
Differential Revision: https://secure.phabricator.com/D1812
2012-03-07 20:52:56 +01:00
|
|
|
$diff->setDescription(preg_replace('/\n.*/s', '', $this->getComments()));
|
2011-01-26 02:17:19 +01:00
|
|
|
$diff->save();
|
|
|
|
|
2011-09-14 19:59:52 +02:00
|
|
|
$this->updateAffectedPathTable($revision, $diff, $changesets);
|
2011-09-26 23:20:31 +02:00
|
|
|
$this->updateRevisionHashTable($revision, $diff);
|
2011-09-14 19:59:52 +02:00
|
|
|
|
2012-04-24 02:40:57 +02:00
|
|
|
// An updated diff should require review, as long as it's not closed
|
2011-01-26 02:17:19 +01:00
|
|
|
// or accepted. The "accepted" status is "sticky" to encourage courtesy
|
|
|
|
// re-diffs after someone accepts with minor changes/suggestions.
|
|
|
|
|
|
|
|
$status = $revision->getStatus();
|
2012-04-24 02:40:57 +02:00
|
|
|
if ($status != ArcanistDifferentialRevisionStatus::CLOSED &&
|
2012-01-10 20:39:11 +01:00
|
|
|
$status != ArcanistDifferentialRevisionStatus::ACCEPTED) {
|
|
|
|
$revision->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
2011-01-30 22:20:56 +01:00
|
|
|
$diff = $revision->loadActiveDiff();
|
2011-01-26 02:17:19 +01:00
|
|
|
if ($diff) {
|
2011-01-30 22:20:56 +01:00
|
|
|
$changesets = $diff->loadChangesets();
|
2011-01-26 02:17:19 +01:00
|
|
|
} else {
|
|
|
|
$changesets = array();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$revision->save();
|
|
|
|
|
2011-08-14 22:55:30 +02:00
|
|
|
$this->didWriteRevision();
|
|
|
|
|
2011-06-19 09:27:39 +02:00
|
|
|
$event_data = array(
|
|
|
|
'revision_id' => $revision->getID(),
|
|
|
|
'revision_phid' => $revision->getPHID(),
|
|
|
|
'revision_name' => $revision->getTitle(),
|
|
|
|
'revision_author_phid' => $revision->getAuthorPHID(),
|
|
|
|
'action' => $is_new
|
|
|
|
? DifferentialAction::ACTION_CREATE
|
|
|
|
: DifferentialAction::ACTION_UPDATE,
|
|
|
|
'feedback_content' => $is_new
|
2011-07-12 16:23:04 +02:00
|
|
|
? phutil_utf8_shorten($revision->getSummary(), 140)
|
2011-06-19 09:27:39 +02:00
|
|
|
: $this->getComments(),
|
|
|
|
'actor_phid' => $revision->getAuthorPHID(),
|
|
|
|
);
|
|
|
|
|
2012-10-23 21:03:11 +02:00
|
|
|
$mailed_phids = array();
|
|
|
|
if (!$this->silentUpdate) {
|
|
|
|
$revision->loadRelationships();
|
|
|
|
|
|
|
|
if ($add['rev']) {
|
|
|
|
$message = id(new DifferentialNewDiffMail(
|
|
|
|
$revision,
|
|
|
|
$actor_handle,
|
|
|
|
$changesets))
|
2013-03-01 02:23:23 +01:00
|
|
|
->setActor($this->getActor())
|
2012-10-23 21:03:11 +02:00
|
|
|
->setIsFirstMailAboutRevision($is_new)
|
|
|
|
->setIsFirstMailToRecipients(true)
|
|
|
|
->setToPHIDs(array_keys($add['rev']));
|
|
|
|
|
|
|
|
if ($is_new) {
|
|
|
|
// The first time we send an email about a revision, put the CCs in
|
|
|
|
// the "CC:" field of the same "Review Requested" email that reviewers
|
|
|
|
// get, so you don't get two initial emails if you're on a list that
|
|
|
|
// is CC'd.
|
|
|
|
$message->setCCPHIDs(array_keys($add['ccs']));
|
|
|
|
}
|
|
|
|
|
|
|
|
$mail[] = $message;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we added CCs, we want to send them an email, but only if they were
|
|
|
|
// not already a reviewer and were not added as one (in these cases, they
|
|
|
|
// got a "NewDiff" mail, either in the past or just a moment ago). You can
|
|
|
|
// still get two emails, but only if a revision is updated and you are
|
|
|
|
// added as a reviewer at the same time a list you are on is added as a
|
|
|
|
// CC, which is rare and reasonable.
|
|
|
|
|
|
|
|
$implied_ccs = self::getImpliedCCs($revision);
|
|
|
|
$implied_ccs = array_fill_keys($implied_ccs, true);
|
|
|
|
$add['ccs'] = array_diff_key($add['ccs'], $implied_ccs);
|
|
|
|
|
|
|
|
if (!$is_new && $add['ccs']) {
|
|
|
|
$mail[] = id(new DifferentialCCWelcomeMail(
|
|
|
|
$revision,
|
|
|
|
$actor_handle,
|
|
|
|
$changesets))
|
2013-03-01 02:15:09 +01:00
|
|
|
->setActor($this->getActor())
|
2012-10-23 21:03:11 +02:00
|
|
|
->setIsFirstMailToRecipients(true)
|
|
|
|
->setToPHIDs(array_keys($add['ccs']));
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($mail as $message) {
|
|
|
|
$message->setHeraldTranscriptURI($xscript_uri);
|
|
|
|
$message->setXHeraldRulesHeader($xscript_header);
|
|
|
|
$message->send();
|
|
|
|
|
|
|
|
$mailed_phids[] = $message->getRawMail()->buildRecipientList();
|
|
|
|
}
|
|
|
|
$mailed_phids = array_mergev($mailed_phids);
|
|
|
|
}
|
|
|
|
|
2011-07-10 00:44:49 +02:00
|
|
|
id(new PhabricatorFeedStoryPublisher())
|
2012-10-23 21:03:11 +02:00
|
|
|
->setStoryType('PhabricatorFeedStoryDifferential')
|
2011-07-10 00:44:49 +02:00
|
|
|
->setStoryData($event_data)
|
|
|
|
->setStoryTime(time())
|
|
|
|
->setStoryAuthorPHID($revision->getAuthorPHID())
|
|
|
|
->setRelatedPHIDs(
|
|
|
|
array(
|
|
|
|
$revision->getPHID(),
|
|
|
|
$revision->getAuthorPHID(),
|
|
|
|
))
|
2012-06-09 03:45:26 +02:00
|
|
|
->setPrimaryObjectPHID($revision->getPHID())
|
|
|
|
->setSubscribedPHIDs(
|
|
|
|
array_merge(
|
|
|
|
array($revision->getAuthorPHID()),
|
|
|
|
$revision->getReviewers(),
|
|
|
|
$revision->getCCPHIDs()))
|
2012-10-23 21:03:11 +02:00
|
|
|
->setMailRecipientPHIDs($mailed_phids)
|
2011-07-10 00:44:49 +02:00
|
|
|
->publish();
|
|
|
|
|
Improve Search architecture
Summary:
The search indexing API has several problems right now:
- Always runs in-process.
- It would be nice to push this into the task queue for performance. However, the API currently passses an object all the way through (and some indexers depend on preloaded object attributes), so it can't be dumped into the task queue at any stage since we can't serialize it.
- Being able to use the task queue will also make rebuilding indexes faster.
- Instead, make the API phid-oriented.
- No uniform indexing API.
- Each "Editor" currently calls SomeCustomIndexer::indexThing(). This won't work with AbstractTransactions. The API is also just weird.
- Instead, provide a uniform API.
- No uniform CLI.
- We have `scripts/search/reindex_everything.php`, but it doesn't actually index everything. Each new document type needs to be separately added to it, leading to stuff like D3839. Third-party applications can't provide indexers.
- Instead, let indexers expose documents for indexing.
- Not application-oriented.
- All the indexers live in search/ right now, which isn't the right organization in an application-orietned view of the world.
- Instead, move indexers to applications and load them with SymbolLoader.
Test Plan:
- `bin/search index`
- Indexed one revision, one task.
- Indexed `--type TASK`, `--type DREV`, etc., for all types.
- Indexed `--all`.
- Added the word "saboteur" to a revision, task, wiki page, and question and then searched for it.
- Creating users is a pain; searched for a user after indexing.
- Creating commits is a pain; searched for a commit after indexing.
- Mocks aren't currently loadable in the result view, so their indexing is moot.
Reviewers: btrahan, vrana
Reviewed By: btrahan
CC: 20after4, aran
Maniphest Tasks: T1991, T2104
Differential Revision: https://secure.phabricator.com/D4261
2012-12-21 23:21:31 +01:00
|
|
|
id(new PhabricatorSearchIndexer())
|
|
|
|
->indexDocumentByPHID($revision->getPHID());
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
2011-05-31 23:53:03 +02:00
|
|
|
public static function addCCAndUpdateRevision(
|
|
|
|
$revision,
|
|
|
|
$phid,
|
2013-02-08 03:13:38 +01:00
|
|
|
PhabricatorUser $actor) {
|
2011-05-31 23:53:03 +02:00
|
|
|
|
2013-02-08 03:13:38 +01:00
|
|
|
self::addCC($revision, $phid, $actor->getPHID());
|
2011-05-31 23:53:03 +02:00
|
|
|
|
2013-02-02 01:48:10 +01:00
|
|
|
$type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_UNSUBSCRIBER;
|
|
|
|
id(new PhabricatorEdgeEditor())
|
2013-02-08 03:13:38 +01:00
|
|
|
->setActor($actor)
|
2013-02-02 01:48:10 +01:00
|
|
|
->removeEdge($revision->getPHID(), $type, $phid)
|
|
|
|
->save();
|
2011-05-31 23:53:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static function removeCCAndUpdateRevision(
|
|
|
|
$revision,
|
|
|
|
$phid,
|
2013-02-08 03:13:38 +01:00
|
|
|
PhabricatorUser $actor) {
|
2011-05-31 23:53:03 +02:00
|
|
|
|
2013-02-08 03:13:38 +01:00
|
|
|
self::removeCC($revision, $phid, $actor->getPHID());
|
2011-05-31 23:53:03 +02:00
|
|
|
|
2013-02-02 01:48:10 +01:00
|
|
|
$type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_UNSUBSCRIBER;
|
|
|
|
id(new PhabricatorEdgeEditor())
|
2013-02-08 03:13:38 +01:00
|
|
|
->setActor($actor)
|
2013-02-02 01:48:10 +01:00
|
|
|
->addEdge($revision->getPHID(), $type, $phid)
|
|
|
|
->save();
|
2011-05-31 23:53:03 +02:00
|
|
|
}
|
|
|
|
|
2011-02-19 23:36:13 +01:00
|
|
|
public static function addCC(
|
|
|
|
DifferentialRevision $revision,
|
|
|
|
$phid,
|
|
|
|
$reason) {
|
|
|
|
return self::alterCCs(
|
|
|
|
$revision,
|
|
|
|
$revision->getCCPHIDs(),
|
|
|
|
$rem = array(),
|
|
|
|
$add = array($phid),
|
|
|
|
$reason);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function removeCC(
|
|
|
|
DifferentialRevision $revision,
|
|
|
|
$phid,
|
|
|
|
$reason) {
|
|
|
|
return self::alterCCs(
|
|
|
|
$revision,
|
|
|
|
$revision->getCCPHIDs(),
|
|
|
|
$rem = array($phid),
|
|
|
|
$add = array(),
|
|
|
|
$reason);
|
|
|
|
}
|
|
|
|
|
2011-02-05 03:16:02 +01:00
|
|
|
protected static function alterCCs(
|
2011-01-26 02:17:19 +01:00
|
|
|
DifferentialRevision $revision,
|
2011-02-05 03:16:02 +01:00
|
|
|
array $stable_phids,
|
|
|
|
array $rem_phids,
|
|
|
|
array $add_phids,
|
2011-01-26 02:17:19 +01:00
|
|
|
$reason_phid) {
|
|
|
|
|
2012-04-02 19:41:03 +02:00
|
|
|
$dont_add = self::getImpliedCCs($revision);
|
2012-03-30 15:25:49 +02:00
|
|
|
$add_phids = array_diff($add_phids, $dont_add);
|
|
|
|
|
2011-02-05 03:16:02 +01:00
|
|
|
return self::alterRelationships(
|
|
|
|
$revision,
|
|
|
|
$stable_phids,
|
|
|
|
$rem_phids,
|
|
|
|
$add_phids,
|
|
|
|
$reason_phid,
|
|
|
|
DifferentialRevision::RELATION_SUBSCRIBED);
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
2012-04-02 19:41:03 +02:00
|
|
|
private static function getImpliedCCs(DifferentialRevision $revision) {
|
|
|
|
return array_merge(
|
|
|
|
$revision->getReviewers(),
|
|
|
|
array($revision->getAuthorPHID()));
|
|
|
|
}
|
2011-02-05 03:16:02 +01:00
|
|
|
|
2013-07-10 22:45:14 +02:00
|
|
|
public static function updateReviewers(
|
|
|
|
DifferentialRevision $revision,
|
|
|
|
PhabricatorUser $actor,
|
|
|
|
array $add_phids,
|
|
|
|
array $remove_phids) {
|
|
|
|
|
|
|
|
$reviewers = $revision->getReviewers();
|
|
|
|
|
|
|
|
// This is here until the new way proves stable enough
|
|
|
|
// See https://secure.phabricator.com/T1279
|
|
|
|
self::alterReviewers(
|
|
|
|
$revision,
|
|
|
|
$reviewers,
|
|
|
|
$remove_phids,
|
|
|
|
$add_phids,
|
|
|
|
$actor->getPHID());
|
|
|
|
|
|
|
|
$editor = id(new PhabricatorEdgeEditor())
|
|
|
|
->setActor($actor);
|
|
|
|
|
|
|
|
$options = array(
|
|
|
|
'data' => array(
|
2013-07-11 01:26:38 +02:00
|
|
|
'status' => DifferentialReviewerStatus::STATUS_ADDED
|
2013-07-10 22:45:14 +02:00
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
$reviewer_phids_map = array_fill_keys($reviewers, true);
|
|
|
|
|
|
|
|
foreach ($add_phids as $phid) {
|
|
|
|
|
|
|
|
// Adding an already existing edge again would have cause memory loss
|
|
|
|
// That is, the previous state for that reviewer would be lost
|
|
|
|
if (isset($reviewer_phids_map[$phid])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$editor->addEdge(
|
|
|
|
$revision->getPHID(),
|
|
|
|
PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER,
|
|
|
|
$phid,
|
|
|
|
$options);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($remove_phids as $phid) {
|
|
|
|
$editor->removeEdge(
|
|
|
|
$revision->getPHID(),
|
|
|
|
PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER,
|
|
|
|
$phid);
|
|
|
|
}
|
|
|
|
|
|
|
|
$editor->save();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function updateReviewerStatus(
|
|
|
|
DifferentialRevision $revision,
|
|
|
|
PhabricatorUser $actor,
|
|
|
|
$reviewer_phid,
|
|
|
|
$status) {
|
|
|
|
|
|
|
|
$reviewers = $revision->getReviewers();
|
|
|
|
if (!in_array($reviewer_phid, $reviewers)) {
|
|
|
|
// This is here until the new way proves stable enough
|
|
|
|
// See https://secure.phabricator.com/T1279
|
|
|
|
self::alterReviewers(
|
|
|
|
$revision,
|
|
|
|
$reviewers,
|
|
|
|
array(),
|
|
|
|
array($reviewer_phid),
|
|
|
|
$actor->getPHID());
|
|
|
|
}
|
|
|
|
|
|
|
|
$options = array(
|
|
|
|
'data' => array(
|
|
|
|
'status' => $status
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
$active_diff = $revision->loadActiveDiff();
|
|
|
|
if ($active_diff) {
|
|
|
|
$options['data']['diff'] = $active_diff->getID();
|
|
|
|
}
|
|
|
|
|
|
|
|
id(new PhabricatorEdgeEditor())
|
|
|
|
->setActor($actor)
|
|
|
|
->addEdge(
|
|
|
|
$revision->getPHID(),
|
|
|
|
PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER,
|
|
|
|
$reviewer_phid,
|
|
|
|
$options)
|
|
|
|
->save();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
private static function alterReviewers(
|
2011-01-26 02:17:19 +01:00
|
|
|
DifferentialRevision $revision,
|
2011-02-05 03:16:02 +01:00
|
|
|
array $stable_phids,
|
|
|
|
array $rem_phids,
|
|
|
|
array $add_phids,
|
2011-01-26 02:17:19 +01:00
|
|
|
$reason_phid) {
|
|
|
|
|
2011-02-05 03:16:02 +01:00
|
|
|
return self::alterRelationships(
|
|
|
|
$revision,
|
|
|
|
$stable_phids,
|
|
|
|
$rem_phids,
|
|
|
|
$add_phids,
|
|
|
|
$reason_phid,
|
|
|
|
DifferentialRevision::RELATION_REVIEWER);
|
|
|
|
}
|
2011-01-26 02:17:19 +01:00
|
|
|
|
2011-02-05 03:16:02 +01:00
|
|
|
private static function alterRelationships(
|
2011-01-26 02:17:19 +01:00
|
|
|
DifferentialRevision $revision,
|
2011-01-27 03:46:34 +01:00
|
|
|
array $stable_phids,
|
|
|
|
array $rem_phids,
|
|
|
|
array $add_phids,
|
2011-02-05 03:16:02 +01:00
|
|
|
$reason_phid,
|
|
|
|
$relation_type) {
|
2011-02-03 04:38:43 +01:00
|
|
|
|
2011-01-27 03:46:34 +01:00
|
|
|
$rem_map = array_fill_keys($rem_phids, true);
|
|
|
|
$add_map = array_fill_keys($add_phids, true);
|
|
|
|
|
|
|
|
$seq_map = array_values($stable_phids);
|
|
|
|
$seq_map = array_flip($seq_map);
|
|
|
|
foreach ($rem_map as $phid => $ignored) {
|
|
|
|
if (!isset($seq_map[$phid])) {
|
|
|
|
$seq_map[$phid] = count($seq_map);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
foreach ($add_map as $phid => $ignored) {
|
|
|
|
if (!isset($seq_map[$phid])) {
|
|
|
|
$seq_map[$phid] = count($seq_map);
|
|
|
|
}
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
2011-02-05 03:16:02 +01:00
|
|
|
$raw = $revision->getRawRelations($relation_type);
|
2011-02-03 02:38:03 +01:00
|
|
|
$raw = ipull($raw, null, 'objectPHID');
|
2011-02-03 04:38:43 +01:00
|
|
|
|
2011-01-27 03:46:34 +01:00
|
|
|
$sequence = count($seq_map);
|
2011-02-05 03:16:02 +01:00
|
|
|
foreach ($raw as $phid => $ignored) {
|
2011-01-27 03:46:34 +01:00
|
|
|
if (isset($seq_map[$phid])) {
|
|
|
|
$raw[$phid]['sequence'] = $seq_map[$phid];
|
|
|
|
} else {
|
|
|
|
$raw[$phid]['sequence'] = $sequence++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$raw = isort($raw, 'sequence');
|
|
|
|
|
2011-02-05 03:16:02 +01:00
|
|
|
foreach ($raw as $phid => $ignored) {
|
2011-01-27 03:46:34 +01:00
|
|
|
if (isset($rem_map[$phid])) {
|
2011-02-03 02:38:03 +01:00
|
|
|
unset($raw[$phid]);
|
2011-01-27 03:46:34 +01:00
|
|
|
}
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
2011-01-27 03:46:34 +01:00
|
|
|
foreach ($add_phids as $add) {
|
2012-03-26 18:55:38 +02:00
|
|
|
$reason = is_array($reason_phid)
|
|
|
|
? idx($reason_phid, $add)
|
|
|
|
: $reason_phid;
|
|
|
|
|
2011-02-03 02:38:03 +01:00
|
|
|
$raw[$add] = array(
|
2011-01-27 03:46:34 +01:00
|
|
|
'objectPHID' => $add,
|
|
|
|
'sequence' => idx($seq_map, $add, $sequence++),
|
2012-03-26 18:55:38 +02:00
|
|
|
'reasonPHID' => $reason,
|
2011-01-27 03:46:34 +01:00
|
|
|
);
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
2011-02-03 04:38:43 +01:00
|
|
|
|
2011-01-27 03:46:34 +01:00
|
|
|
$conn_w = $revision->establishConnection('w');
|
|
|
|
|
|
|
|
$sql = array();
|
|
|
|
foreach ($raw as $relation) {
|
|
|
|
$sql[] = qsprintf(
|
|
|
|
$conn_w,
|
2011-02-03 02:38:03 +01:00
|
|
|
'(%d, %s, %s, %d, %s)',
|
2011-01-27 03:46:34 +01:00
|
|
|
$revision->getID(),
|
2011-02-05 03:16:02 +01:00
|
|
|
$relation_type,
|
2011-01-27 03:46:34 +01:00
|
|
|
$relation['objectPHID'],
|
|
|
|
$relation['sequence'],
|
|
|
|
$relation['reasonPHID']);
|
|
|
|
}
|
|
|
|
|
|
|
|
$conn_w->openTransaction();
|
|
|
|
queryfx(
|
|
|
|
$conn_w,
|
|
|
|
'DELETE FROM %T WHERE revisionID = %d AND relation = %s',
|
|
|
|
DifferentialRevision::RELATIONSHIP_TABLE,
|
|
|
|
$revision->getID(),
|
2011-02-05 03:16:02 +01:00
|
|
|
$relation_type);
|
2011-01-27 03:46:34 +01:00
|
|
|
if ($sql) {
|
|
|
|
queryfx(
|
|
|
|
$conn_w,
|
|
|
|
'INSERT INTO %T
|
2011-02-03 02:38:03 +01:00
|
|
|
(revisionID, relation, objectPHID, sequence, reasonPHID)
|
2011-01-27 03:46:34 +01:00
|
|
|
VALUES %Q',
|
|
|
|
DifferentialRevision::RELATIONSHIP_TABLE,
|
|
|
|
implode(', ', $sql));
|
|
|
|
}
|
|
|
|
$conn_w->saveTransaction();
|
2011-06-24 20:50:19 +02:00
|
|
|
|
|
|
|
$revision->loadRelationships();
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
2011-02-05 02:53:14 +01:00
|
|
|
|
|
|
|
private function createComment() {
|
|
|
|
$revision_id = $this->revision->getID();
|
|
|
|
$comment = id(new DifferentialComment())
|
|
|
|
->setAuthorPHID($this->getActorPHID())
|
|
|
|
->setRevisionID($revision_id)
|
2011-01-26 02:17:19 +01:00
|
|
|
->setContent($this->getComments())
|
2012-02-22 17:03:48 +01:00
|
|
|
->setAction(DifferentialAction::ACTION_UPDATE)
|
|
|
|
->setMetadata(
|
|
|
|
array(
|
|
|
|
DifferentialComment::METADATA_DIFF_ID => $this->getDiff()->getID(),
|
|
|
|
));
|
Track content sources (email, web, conduit, mobile) for replies
Summary:
When an object is updated, record the content source for the update. This mostly
isn't terribly useful but one concrete thing I want to do with it is let admins
audit via-email replies more easily since there are a bunch of options which let
you do hyjinx if you intentionally configure them insecurely. I think having a
little more auditability around this feature is generally good. At some point
I'm going to turn this into a link admins can click to see details.
It also allows us to see how frequently different mechanisms are used, and lets
you see if someone is at their desk or on a mobile or whatever, at least
indirectly.
The "tablet" and "mobile" sources are currently unused but I figured I'd throw
them in anyway. SMS support should definitely happen at some point.
Not 100% sure about the design for this, I might change it to plain text at some
point.
Test Plan: Updated objects and saw update sources rendered.
Reviewers: jungejason, tuomaspelkonen, aran
Reviewed By: jungejason
CC: aran, epriestley, jungejason
Differential Revision: 844
2011-08-22 19:25:45 +02:00
|
|
|
|
|
|
|
if ($this->contentSource) {
|
|
|
|
$comment->setContentSource($this->contentSource);
|
|
|
|
}
|
|
|
|
|
2011-02-05 02:53:14 +01:00
|
|
|
$comment->save();
|
2011-01-26 02:17:19 +01:00
|
|
|
|
2011-02-05 02:53:14 +01:00
|
|
|
return $comment;
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
2011-08-10 22:46:01 +02:00
|
|
|
private function updateAuxiliaryFields() {
|
|
|
|
$aux_map = array();
|
|
|
|
foreach ($this->auxiliaryFields as $aux_field) {
|
|
|
|
$key = $aux_field->getStorageKey();
|
2011-08-14 23:28:08 +02:00
|
|
|
if ($key !== null) {
|
|
|
|
$val = $aux_field->getValueForStorage();
|
|
|
|
$aux_map[$key] = $val;
|
|
|
|
}
|
2011-08-10 22:46:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!$aux_map) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$revision = $this->revision;
|
|
|
|
|
|
|
|
$fields = id(new DifferentialAuxiliaryField())->loadAllWhere(
|
|
|
|
'revisionPHID = %s AND name IN (%Ls)',
|
|
|
|
$revision->getPHID(),
|
|
|
|
array_keys($aux_map));
|
|
|
|
$fields = mpull($fields, null, 'getName');
|
|
|
|
|
|
|
|
foreach ($aux_map as $key => $val) {
|
|
|
|
$obj = idx($fields, $key);
|
Save empty fields as no row, not an empty row
Summary: When a user stores the empty string in an auxiliary field, simply don't
store it, and delete it if it already exists.
Test Plan: Edited a revision with an empty "Quack" field, got an empty row in
the DB. Applied patch, edited empty again, row went away. Edited empty again,
still no row. Edited and put something in the field, got a row.
Reviewers: jungejason, tuomaspelkonen, aran
Reviewed By: jungejason
CC: aran, jungejason
Differential Revision: 865
2011-08-26 20:40:42 +02:00
|
|
|
if (!strlen($val)) {
|
|
|
|
// If the new value is empty, just delete the old row if one exists and
|
|
|
|
// don't add a new row if it doesn't.
|
|
|
|
if ($obj) {
|
|
|
|
$obj->delete();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!$obj) {
|
|
|
|
$obj = new DifferentialAuxiliaryField();
|
|
|
|
$obj->setRevisionPHID($revision->getPHID());
|
|
|
|
$obj->setName($key);
|
|
|
|
}
|
2011-08-10 22:46:01 +02:00
|
|
|
|
Save empty fields as no row, not an empty row
Summary: When a user stores the empty string in an auxiliary field, simply don't
store it, and delete it if it already exists.
Test Plan: Edited a revision with an empty "Quack" field, got an empty row in
the DB. Applied patch, edited empty again, row went away. Edited empty again,
still no row. Edited and put something in the field, got a row.
Reviewers: jungejason, tuomaspelkonen, aran
Reviewed By: jungejason
CC: aran, jungejason
Differential Revision: 865
2011-08-26 20:40:42 +02:00
|
|
|
if ($obj->getValue() !== $val) {
|
|
|
|
$obj->setValue($val);
|
|
|
|
$obj->save();
|
|
|
|
}
|
2011-08-10 22:46:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-08-14 22:55:30 +02:00
|
|
|
private function willWriteRevision() {
|
|
|
|
foreach ($this->auxiliaryFields as $aux_field) {
|
|
|
|
$aux_field->willWriteRevision($this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function didWriteRevision() {
|
|
|
|
foreach ($this->auxiliaryFields as $aux_field) {
|
|
|
|
$aux_field->didWriteRevision($this);
|
|
|
|
}
|
|
|
|
}
|
2011-08-10 22:46:01 +02:00
|
|
|
|
2011-09-14 19:59:52 +02:00
|
|
|
/**
|
2011-09-26 23:20:31 +02:00
|
|
|
* Update the table which links Differential revisions to paths they affect,
|
2011-09-14 19:59:52 +02:00
|
|
|
* so Diffusion can efficiently find pending revisions for a given file.
|
|
|
|
*/
|
|
|
|
private function updateAffectedPathTable(
|
|
|
|
DifferentialRevision $revision,
|
|
|
|
DifferentialDiff $diff,
|
|
|
|
array $changesets) {
|
2012-04-04 22:13:08 +02:00
|
|
|
assert_instances_of($changesets, 'DifferentialChangeset');
|
2011-09-14 19:59:52 +02:00
|
|
|
|
|
|
|
$project = $diff->loadArcanistProject();
|
|
|
|
if (!$project) {
|
|
|
|
// Probably an old revision from before projects.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$repository = $project->loadRepository();
|
|
|
|
if (!$repository) {
|
|
|
|
// Probably no project <-> repository link, or the repository where the
|
|
|
|
// project lives is untracked.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$path_prefix = null;
|
|
|
|
|
|
|
|
$local_root = $diff->getSourceControlPath();
|
|
|
|
if ($local_root) {
|
|
|
|
// We're in a working copy which supports subdirectory checkouts (e.g.,
|
|
|
|
// SVN) so we need to figure out what prefix we should add to each path
|
|
|
|
// (e.g., trunk/projects/example/) to get the absolute path from the
|
|
|
|
// root of the repository. DVCS systems like Git and Mercurial are not
|
|
|
|
// affected.
|
|
|
|
|
|
|
|
// Normalize both paths and check if the repository root is a prefix of
|
|
|
|
// the local root. If so, throw it away. Note that this correctly handles
|
|
|
|
// the case where the remote path is "/".
|
|
|
|
$local_root = id(new PhutilURI($local_root))->getPath();
|
|
|
|
$local_root = rtrim($local_root, '/');
|
|
|
|
|
|
|
|
$repo_root = id(new PhutilURI($repository->getRemoteURI()))->getPath();
|
|
|
|
$repo_root = rtrim($repo_root, '/');
|
|
|
|
|
|
|
|
if (!strncmp($repo_root, $local_root, strlen($repo_root))) {
|
|
|
|
$path_prefix = substr($local_root, strlen($repo_root));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$paths = array();
|
|
|
|
foreach ($changesets as $changeset) {
|
2012-01-17 08:05:44 +01:00
|
|
|
$paths[] = $path_prefix.'/'.$changeset->getFilename();
|
2011-09-14 19:59:52 +02:00
|
|
|
}
|
|
|
|
|
2011-10-02 21:52:54 +02:00
|
|
|
// Mark this as also touching all parent paths, so you can see all pending
|
|
|
|
// changes to any file within a directory.
|
|
|
|
$all_paths = array();
|
|
|
|
foreach ($paths as $local) {
|
|
|
|
foreach (DiffusionPathIDQuery::expandPathToRoot($local) as $path) {
|
|
|
|
$all_paths[$path] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$all_paths = array_keys($all_paths);
|
|
|
|
|
2012-11-21 20:08:07 +01:00
|
|
|
$path_ids =
|
|
|
|
PhabricatorRepositoryCommitChangeParserWorker::lookupOrCreatePaths(
|
|
|
|
$all_paths);
|
2011-09-14 19:59:52 +02:00
|
|
|
|
|
|
|
$table = new DifferentialAffectedPath();
|
|
|
|
$conn_w = $table->establishConnection('w');
|
|
|
|
|
|
|
|
$sql = array();
|
2012-11-21 20:08:07 +01:00
|
|
|
foreach ($path_ids as $path_id) {
|
2011-09-14 19:59:52 +02:00
|
|
|
$sql[] = qsprintf(
|
|
|
|
$conn_w,
|
|
|
|
'(%d, %d, %d, %d)',
|
|
|
|
$repository->getID(),
|
|
|
|
$path_id,
|
|
|
|
time(),
|
|
|
|
$revision->getID());
|
|
|
|
}
|
|
|
|
|
|
|
|
queryfx(
|
|
|
|
$conn_w,
|
|
|
|
'DELETE FROM %T WHERE revisionID = %d',
|
|
|
|
$table->getTableName(),
|
|
|
|
$revision->getID());
|
|
|
|
foreach (array_chunk($sql, 256) as $chunk) {
|
|
|
|
queryfx(
|
|
|
|
$conn_w,
|
|
|
|
'INSERT INTO %T (repositoryID, pathID, epoch, revisionID) VALUES %Q',
|
|
|
|
$table->getTableName(),
|
|
|
|
implode(', ', $chunk));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-26 23:20:31 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the table connecting revisions to DVCS local hashes, so we can
|
|
|
|
* identify revisions by commit/tree hashes.
|
|
|
|
*/
|
|
|
|
private function updateRevisionHashTable(
|
|
|
|
DifferentialRevision $revision,
|
|
|
|
DifferentialDiff $diff) {
|
|
|
|
|
|
|
|
$vcs = $diff->getSourceControlSystem();
|
|
|
|
if ($vcs == DifferentialRevisionControlSystem::SVN) {
|
|
|
|
// Subversion has no local commit or tree hash information, so we don't
|
|
|
|
// have to do anything.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$property = id(new DifferentialDiffProperty())->loadOneWhere(
|
|
|
|
'diffID = %d AND name = %s',
|
|
|
|
$diff->getID(),
|
|
|
|
'local:commits');
|
|
|
|
if (!$property) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$hashes = array();
|
|
|
|
|
|
|
|
$data = $property->getData();
|
|
|
|
switch ($vcs) {
|
|
|
|
case DifferentialRevisionControlSystem::GIT:
|
|
|
|
foreach ($data as $commit) {
|
|
|
|
$hashes[] = array(
|
2012-01-10 20:39:11 +01:00
|
|
|
ArcanistDifferentialRevisionHash::HASH_GIT_COMMIT,
|
2011-09-26 23:20:31 +02:00
|
|
|
$commit['commit'],
|
|
|
|
);
|
|
|
|
$hashes[] = array(
|
2012-01-10 20:39:11 +01:00
|
|
|
ArcanistDifferentialRevisionHash::HASH_GIT_TREE,
|
2011-09-26 23:20:31 +02:00
|
|
|
$commit['tree'],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case DifferentialRevisionControlSystem::MERCURIAL:
|
|
|
|
foreach ($data as $commit) {
|
|
|
|
$hashes[] = array(
|
2012-01-10 20:39:11 +01:00
|
|
|
ArcanistDifferentialRevisionHash::HASH_MERCURIAL_COMMIT,
|
2011-09-26 23:20:31 +02:00
|
|
|
$commit['rev'],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
$conn_w = $revision->establishConnection('w');
|
|
|
|
|
|
|
|
$sql = array();
|
|
|
|
foreach ($hashes as $info) {
|
|
|
|
list($type, $hash) = $info;
|
|
|
|
$sql[] = qsprintf(
|
|
|
|
$conn_w,
|
|
|
|
'(%d, %s, %s)',
|
|
|
|
$revision->getID(),
|
|
|
|
$type,
|
|
|
|
$hash);
|
|
|
|
}
|
|
|
|
|
|
|
|
queryfx(
|
|
|
|
$conn_w,
|
|
|
|
'DELETE FROM %T WHERE revisionID = %d',
|
2012-01-10 20:39:11 +01:00
|
|
|
ArcanistDifferentialRevisionHash::TABLE_NAME,
|
2011-09-26 23:20:31 +02:00
|
|
|
$revision->getID());
|
|
|
|
|
|
|
|
if ($sql) {
|
|
|
|
queryfx(
|
|
|
|
$conn_w,
|
|
|
|
'INSERT INTO %T (revisionID, type, hash) VALUES %Q',
|
2012-01-10 20:39:11 +01:00
|
|
|
ArcanistDifferentialRevisionHash::TABLE_NAME,
|
2011-09-26 23:20:31 +02:00
|
|
|
implode(', ', $sql));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-03 01:03:44 +01:00
|
|
|
private function initializeNewRevision(DifferentialRevision $revision) {
|
|
|
|
// These fields aren't nullable; set them to sensible defaults if they
|
|
|
|
// haven't been configured. We're just doing this so we can generate an
|
|
|
|
// ID for the revision if we don't have one already.
|
|
|
|
$revision->setLineCount(0);
|
|
|
|
if ($revision->getStatus() === null) {
|
|
|
|
$revision->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
|
|
|
|
}
|
|
|
|
if ($revision->getTitle() === null) {
|
|
|
|
$revision->setTitle('Untitled Revision');
|
|
|
|
}
|
|
|
|
if ($revision->getAuthorPHID() === null) {
|
|
|
|
$revision->setAuthorPHID($this->getActorPHID());
|
|
|
|
}
|
|
|
|
if ($revision->getSummary() === null) {
|
|
|
|
$revision->setSummary('');
|
|
|
|
}
|
|
|
|
if ($revision->getTestPlan() === null) {
|
|
|
|
$revision->setTestPlan('');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|