2011-01-24 20:01:53 +01:00
|
|
|
<?php
|
|
|
|
|
2013-07-01 21:38:42 +02:00
|
|
|
final class DifferentialDiff
|
|
|
|
extends DifferentialDAO
|
2013-12-26 19:40:52 +01:00
|
|
|
implements
|
|
|
|
PhabricatorPolicyInterface,
|
2017-01-01 17:36:38 +01:00
|
|
|
PhabricatorExtendedPolicyInterface,
|
2014-04-18 01:03:24 +02:00
|
|
|
HarbormasterBuildableInterface,
|
2015-10-15 05:01:22 +02:00
|
|
|
HarbormasterCircleCIBuildableInterface,
|
2014-05-02 03:25:30 +02:00
|
|
|
PhabricatorApplicationTransactionInterface,
|
2014-07-21 15:59:22 +02:00
|
|
|
PhabricatorDestructibleInterface {
|
2011-01-24 20:01:53 +01:00
|
|
|
|
|
|
|
protected $revisionID;
|
2011-01-30 19:37:36 +01:00
|
|
|
protected $authorPHID;
|
2014-01-27 00:29:22 +01:00
|
|
|
protected $repositoryPHID;
|
2011-01-24 20:01:53 +01:00
|
|
|
|
|
|
|
protected $sourceMachine;
|
|
|
|
protected $sourcePath;
|
|
|
|
|
|
|
|
protected $sourceControlSystem;
|
|
|
|
protected $sourceControlBaseRevision;
|
|
|
|
protected $sourceControlPath;
|
|
|
|
|
|
|
|
protected $lintStatus;
|
|
|
|
protected $unitStatus;
|
|
|
|
|
|
|
|
protected $lineCount;
|
|
|
|
|
|
|
|
protected $branch;
|
2012-06-30 23:45:30 +02:00
|
|
|
protected $bookmark;
|
2011-01-24 20:01:53 +01:00
|
|
|
|
|
|
|
protected $creationMethod;
|
2011-04-06 05:49:31 +02:00
|
|
|
protected $repositoryUUID;
|
2011-01-24 20:01:53 +01:00
|
|
|
|
2011-02-05 02:53:14 +01:00
|
|
|
protected $description;
|
|
|
|
|
2014-11-19 21:16:07 +01:00
|
|
|
protected $viewPolicy;
|
|
|
|
|
2011-01-24 20:36:53 +01:00
|
|
|
private $unsavedChangesets = array();
|
2013-09-03 15:02:14 +02:00
|
|
|
private $changesets = self::ATTACHABLE;
|
2013-09-27 03:45:04 +02:00
|
|
|
private $revision = self::ATTACHABLE;
|
2014-02-27 20:06:37 +01:00
|
|
|
private $properties = array();
|
2015-06-23 16:09:23 +02:00
|
|
|
private $buildable = self::ATTACHABLE;
|
2011-01-24 20:36:53 +01:00
|
|
|
|
2016-02-29 18:53:46 +01:00
|
|
|
private $unitMessages = self::ATTACHABLE;
|
|
|
|
|
2015-01-13 20:47:05 +01:00
|
|
|
protected function getConfiguration() {
|
2013-11-06 22:59:06 +01:00
|
|
|
return array(
|
|
|
|
self::CONFIG_AUX_PHID => true,
|
2014-09-29 00:12:58 +02:00
|
|
|
self::CONFIG_COLUMN_SCHEMA => array(
|
|
|
|
'revisionID' => 'id?',
|
|
|
|
'authorPHID' => 'phid?',
|
|
|
|
'repositoryPHID' => 'phid?',
|
|
|
|
'sourceMachine' => 'text255?',
|
|
|
|
'sourcePath' => 'text255?',
|
|
|
|
'sourceControlSystem' => 'text64?',
|
|
|
|
'sourceControlBaseRevision' => 'text255?',
|
|
|
|
'sourceControlPath' => 'text255?',
|
|
|
|
'lintStatus' => 'uint32',
|
|
|
|
'unitStatus' => 'uint32',
|
|
|
|
'lineCount' => 'uint32',
|
|
|
|
'branch' => 'text255?',
|
|
|
|
'bookmark' => 'text255?',
|
|
|
|
'repositoryUUID' => 'text64?',
|
2014-10-01 16:59:44 +02:00
|
|
|
|
|
|
|
// T6203/NULLABILITY
|
|
|
|
// These should be non-null; all diffs should have a creation method
|
|
|
|
// and the description should just be empty.
|
|
|
|
'creationMethod' => 'text255?',
|
|
|
|
'description' => 'text255?',
|
2014-09-29 00:12:58 +02:00
|
|
|
),
|
|
|
|
self::CONFIG_KEY_SCHEMA => array(
|
|
|
|
'revisionID' => array(
|
|
|
|
'columns' => array('revisionID'),
|
|
|
|
),
|
|
|
|
),
|
2013-11-06 22:59:06 +01:00
|
|
|
) + parent::getConfiguration();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function generatePHID() {
|
|
|
|
return PhabricatorPHID::generateNewPHID(
|
2014-07-24 00:05:46 +02:00
|
|
|
DifferentialDiffPHIDType::TYPECONST);
|
2013-11-06 22:59:06 +01:00
|
|
|
}
|
|
|
|
|
2011-01-24 20:36:53 +01:00
|
|
|
public function addUnsavedChangeset(DifferentialChangeset $changeset) {
|
2011-01-25 00:52:35 +01:00
|
|
|
if ($this->changesets === null) {
|
|
|
|
$this->changesets = array();
|
|
|
|
}
|
2011-01-24 20:36:53 +01:00
|
|
|
$this->unsavedChangesets[] = $changeset;
|
2011-01-25 00:52:35 +01:00
|
|
|
$this->changesets[] = $changeset;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function attachChangesets(array $changesets) {
|
2012-04-04 22:13:08 +02:00
|
|
|
assert_instances_of($changesets, 'DifferentialChangeset');
|
2011-01-25 00:52:35 +01:00
|
|
|
$this->changesets = $changesets;
|
2011-01-24 20:36:53 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-01-25 00:52:35 +01:00
|
|
|
public function getChangesets() {
|
2013-09-03 15:02:14 +02:00
|
|
|
return $this->assertAttached($this->changesets);
|
2011-01-25 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2011-01-24 20:01:53 +01:00
|
|
|
public function loadChangesets() {
|
|
|
|
if (!$this->getID()) {
|
|
|
|
return array();
|
|
|
|
}
|
2015-08-17 19:14:22 +02:00
|
|
|
$changesets = id(new DifferentialChangeset())->loadAllWhere(
|
2011-01-24 20:01:53 +01:00
|
|
|
'diffID = %d',
|
|
|
|
$this->getID());
|
2015-08-17 19:14:22 +02:00
|
|
|
|
|
|
|
foreach ($changesets as $changeset) {
|
|
|
|
$changeset->attachDiff($this);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $changesets;
|
2011-01-24 20:01:53 +01:00
|
|
|
}
|
|
|
|
|
2011-01-24 20:36:53 +01:00
|
|
|
public function save() {
|
2012-06-19 20:52:50 +02:00
|
|
|
$this->openTransaction();
|
2011-01-24 20:36:53 +01:00
|
|
|
$ret = parent::save();
|
|
|
|
foreach ($this->unsavedChangesets as $changeset) {
|
2011-01-24 21:07:34 +01:00
|
|
|
$changeset->setDiffID($this->getID());
|
|
|
|
$changeset->save();
|
2011-01-24 20:36:53 +01:00
|
|
|
}
|
2012-06-19 20:52:50 +02:00
|
|
|
$this->saveTransaction();
|
2011-01-24 20:36:53 +01:00
|
|
|
return $ret;
|
|
|
|
}
|
|
|
|
|
2014-11-19 21:16:07 +01:00
|
|
|
public static function initializeNewDiff(PhabricatorUser $actor) {
|
|
|
|
$app = id(new PhabricatorApplicationQuery())
|
|
|
|
->setViewer($actor)
|
|
|
|
->withClasses(array('PhabricatorDifferentialApplication'))
|
|
|
|
->executeOne();
|
|
|
|
$view_policy = $app->getPolicy(
|
|
|
|
DifferentialDefaultViewCapability::CAPABILITY);
|
|
|
|
|
|
|
|
$diff = id(new DifferentialDiff())
|
|
|
|
->setViewPolicy($view_policy);
|
|
|
|
|
|
|
|
return $diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function newFromRawChanges(
|
|
|
|
PhabricatorUser $actor,
|
|
|
|
array $changes) {
|
|
|
|
|
2012-04-04 22:13:08 +02:00
|
|
|
assert_instances_of($changes, 'ArcanistDiffChange');
|
2011-01-24 20:01:53 +01:00
|
|
|
|
2014-11-19 21:16:07 +01:00
|
|
|
$diff = self::initializeNewDiff($actor);
|
2014-12-19 23:54:15 +01:00
|
|
|
return self::buildChangesetsFromRawChanges($diff, $changes);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function newEphemeralFromRawChanges(array $changes) {
|
|
|
|
assert_instances_of($changes, 'ArcanistDiffChange');
|
|
|
|
|
|
|
|
$diff = id(new DifferentialDiff())->makeEphemeral();
|
|
|
|
return self::buildChangesetsFromRawChanges($diff, $changes);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static function buildChangesetsFromRawChanges(
|
|
|
|
DifferentialDiff $diff,
|
|
|
|
array $changes) {
|
|
|
|
|
2013-07-01 18:02:55 +02:00
|
|
|
// There may not be any changes; initialize the changesets list so that
|
|
|
|
// we don't throw later when accessing it.
|
|
|
|
$diff->attachChangesets(array());
|
|
|
|
|
2011-01-24 20:01:53 +01:00
|
|
|
$lines = 0;
|
|
|
|
foreach ($changes as $change) {
|
2012-10-19 19:29:19 +02:00
|
|
|
if ($change->getType() == ArcanistDiffChangeType::TYPE_MESSAGE) {
|
|
|
|
// If a user pastes a diff into Differential which includes a commit
|
|
|
|
// message (e.g., they ran `git show` to generate it), discard that
|
|
|
|
// change when constructing a DifferentialDiff.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-01-24 20:01:53 +01:00
|
|
|
$changeset = new DifferentialChangeset();
|
|
|
|
$add_lines = 0;
|
|
|
|
$del_lines = 0;
|
2012-09-21 20:57:45 +02:00
|
|
|
$first_line = PHP_INT_MAX;
|
Allow DifferentialDiff to construct proper DifferentialChangeset objects from
diffs which add empty files
Summary:
See T507 and some others. We now parse empty git diffs correctly, but the logic
to build DifferentialDiffs out of them leaves the objects with 'null' for
$changesets, when it should be array().
Further layers later throw, believing we have not loaded the changesets, when we
actually have, there just aren't any.
Test Plan: Viewed rJX05d493e17fbbb29f29e4880be6834d1d7415374e in Diffusion,
which adds an empty README file. No exception thrown.
Reviewers: jungejason, nh, tuomaspelkonen, aran
Reviewed By: nh
CC: aran, nh
Differential Revision: 1038
2011-10-23 23:33:53 +02:00
|
|
|
$hunks = $change->getHunks();
|
|
|
|
if ($hunks) {
|
|
|
|
foreach ($hunks as $hunk) {
|
2015-01-22 21:17:04 +01:00
|
|
|
$dhunk = new DifferentialModernHunk();
|
Allow DifferentialDiff to construct proper DifferentialChangeset objects from
diffs which add empty files
Summary:
See T507 and some others. We now parse empty git diffs correctly, but the logic
to build DifferentialDiffs out of them leaves the objects with 'null' for
$changesets, when it should be array().
Further layers later throw, believing we have not loaded the changesets, when we
actually have, there just aren't any.
Test Plan: Viewed rJX05d493e17fbbb29f29e4880be6834d1d7415374e in Diffusion,
which adds an empty README file. No exception thrown.
Reviewers: jungejason, nh, tuomaspelkonen, aran
Reviewed By: nh
CC: aran, nh
Differential Revision: 1038
2011-10-23 23:33:53 +02:00
|
|
|
$dhunk->setOldOffset($hunk->getOldOffset());
|
|
|
|
$dhunk->setOldLen($hunk->getOldLength());
|
|
|
|
$dhunk->setNewOffset($hunk->getNewOffset());
|
|
|
|
$dhunk->setNewLen($hunk->getNewLength());
|
|
|
|
$dhunk->setChanges($hunk->getCorpus());
|
|
|
|
$changeset->addUnsavedHunk($dhunk);
|
|
|
|
$add_lines += $hunk->getAddLines();
|
|
|
|
$del_lines += $hunk->getDelLines();
|
2012-09-21 20:57:45 +02:00
|
|
|
$added_lines = $hunk->getChangedLines('new');
|
|
|
|
if ($added_lines) {
|
|
|
|
$first_line = min($first_line, head_key($added_lines));
|
|
|
|
}
|
Allow DifferentialDiff to construct proper DifferentialChangeset objects from
diffs which add empty files
Summary:
See T507 and some others. We now parse empty git diffs correctly, but the logic
to build DifferentialDiffs out of them leaves the objects with 'null' for
$changesets, when it should be array().
Further layers later throw, believing we have not loaded the changesets, when we
actually have, there just aren't any.
Test Plan: Viewed rJX05d493e17fbbb29f29e4880be6834d1d7415374e in Diffusion,
which adds an empty README file. No exception thrown.
Reviewers: jungejason, nh, tuomaspelkonen, aran
Reviewed By: nh
CC: aran, nh
Differential Revision: 1038
2011-10-23 23:33:53 +02:00
|
|
|
}
|
2012-06-27 23:45:37 +02:00
|
|
|
$lines += $add_lines + $del_lines;
|
Allow DifferentialDiff to construct proper DifferentialChangeset objects from
diffs which add empty files
Summary:
See T507 and some others. We now parse empty git diffs correctly, but the logic
to build DifferentialDiffs out of them leaves the objects with 'null' for
$changesets, when it should be array().
Further layers later throw, believing we have not loaded the changesets, when we
actually have, there just aren't any.
Test Plan: Viewed rJX05d493e17fbbb29f29e4880be6834d1d7415374e in Diffusion,
which adds an empty README file. No exception thrown.
Reviewers: jungejason, nh, tuomaspelkonen, aran
Reviewed By: nh
CC: aran, nh
Differential Revision: 1038
2011-10-23 23:33:53 +02:00
|
|
|
} else {
|
|
|
|
// This happens when you add empty files.
|
|
|
|
$changeset->attachHunks(array());
|
2011-01-24 20:01:53 +01:00
|
|
|
}
|
|
|
|
|
2012-09-21 20:57:45 +02:00
|
|
|
$metadata = $change->getAllMetadata();
|
|
|
|
if ($first_line != PHP_INT_MAX) {
|
|
|
|
$metadata['line:first'] = $first_line;
|
|
|
|
}
|
|
|
|
|
2011-01-24 20:01:53 +01:00
|
|
|
$changeset->setOldFile($change->getOldPath());
|
|
|
|
$changeset->setFilename($change->getCurrentPath());
|
|
|
|
$changeset->setChangeType($change->getType());
|
|
|
|
|
|
|
|
$changeset->setFileType($change->getFileType());
|
2012-09-21 20:57:45 +02:00
|
|
|
$changeset->setMetadata($metadata);
|
2011-01-24 20:01:53 +01:00
|
|
|
$changeset->setOldProperties($change->getOldProperties());
|
|
|
|
$changeset->setNewProperties($change->getNewProperties());
|
|
|
|
$changeset->setAwayPaths($change->getAwayPaths());
|
|
|
|
$changeset->setAddLines($add_lines);
|
|
|
|
$changeset->setDelLines($del_lines);
|
|
|
|
|
2011-01-24 20:36:53 +01:00
|
|
|
$diff->addUnsavedChangeset($changeset);
|
2011-01-24 20:01:53 +01:00
|
|
|
}
|
|
|
|
$diff->setLineCount($lines);
|
|
|
|
|
2013-01-09 22:11:17 +01:00
|
|
|
$parser = new DifferentialChangesetParser();
|
|
|
|
$changesets = $parser->detectCopiedCode(
|
|
|
|
$diff->getChangesets(),
|
|
|
|
$min_width = 30,
|
|
|
|
$min_lines = 3);
|
|
|
|
$diff->attachChangesets($changesets);
|
2012-04-28 08:00:30 +02:00
|
|
|
|
2011-01-24 20:01:53 +01:00
|
|
|
return $diff;
|
|
|
|
}
|
|
|
|
|
2012-04-28 08:00:30 +02:00
|
|
|
|
2011-10-14 21:08:31 +02:00
|
|
|
public function getDiffDict() {
|
|
|
|
$dict = array(
|
|
|
|
'id' => $this->getID(),
|
|
|
|
'revisionID' => $this->getRevisionID(),
|
2013-01-31 05:25:07 +01:00
|
|
|
'dateCreated' => $this->getDateCreated(),
|
|
|
|
'dateModified' => $this->getDateModified(),
|
2011-10-14 21:08:31 +02:00
|
|
|
'sourceControlBaseRevision' => $this->getSourceControlBaseRevision(),
|
|
|
|
'sourceControlPath' => $this->getSourceControlPath(),
|
2012-01-09 20:42:37 +01:00
|
|
|
'sourceControlSystem' => $this->getSourceControlSystem(),
|
|
|
|
'branch' => $this->getBranch(),
|
2012-06-30 23:45:30 +02:00
|
|
|
'bookmark' => $this->getBookmark(),
|
2012-06-16 00:09:42 +02:00
|
|
|
'creationMethod' => $this->getCreationMethod(),
|
2012-06-16 01:16:03 +02:00
|
|
|
'description' => $this->getDescription(),
|
2011-10-14 21:08:31 +02:00
|
|
|
'unitStatus' => $this->getUnitStatus(),
|
|
|
|
'lintStatus' => $this->getLintStatus(),
|
|
|
|
'changes' => array(),
|
|
|
|
);
|
|
|
|
|
2013-09-17 22:55:41 +02:00
|
|
|
$dict['changes'] = $this->buildChangesList();
|
|
|
|
|
2015-10-14 19:50:53 +02:00
|
|
|
return $dict + $this->getDiffAuthorshipDict();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getDiffAuthorshipDict() {
|
2016-04-14 16:45:39 +02:00
|
|
|
$dict = array('properties' => array());
|
2015-10-14 19:50:53 +02:00
|
|
|
|
2013-09-17 22:55:41 +02:00
|
|
|
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
|
|
|
|
'diffID = %d',
|
|
|
|
$this->getID());
|
|
|
|
foreach ($properties as $property) {
|
|
|
|
$dict['properties'][$property->getName()] = $property->getData();
|
|
|
|
|
|
|
|
if ($property->getName() == 'local:commits') {
|
|
|
|
foreach ($property->getData() as $commit) {
|
|
|
|
$dict['authorName'] = $commit['author'];
|
|
|
|
$dict['authorEmail'] = idx($commit, 'authorEmail');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $dict;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function buildChangesList() {
|
|
|
|
$changes = array();
|
2011-10-14 21:08:31 +02:00
|
|
|
foreach ($this->getChangesets() as $changeset) {
|
|
|
|
$hunks = array();
|
|
|
|
foreach ($changeset->getHunks() as $hunk) {
|
|
|
|
$hunks[] = array(
|
|
|
|
'oldOffset' => $hunk->getOldOffset(),
|
|
|
|
'newOffset' => $hunk->getNewOffset(),
|
|
|
|
'oldLength' => $hunk->getOldLen(),
|
|
|
|
'newLength' => $hunk->getNewLen(),
|
|
|
|
'addLines' => null,
|
|
|
|
'delLines' => null,
|
|
|
|
'isMissingOldNewline' => null,
|
|
|
|
'isMissingNewNewline' => null,
|
|
|
|
'corpus' => $hunk->getChanges(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$change = array(
|
2013-05-03 17:12:43 +02:00
|
|
|
'id' => $changeset->getID(),
|
2011-10-14 21:08:31 +02:00
|
|
|
'metadata' => $changeset->getMetadata(),
|
|
|
|
'oldPath' => $changeset->getOldFile(),
|
2012-01-17 08:05:44 +01:00
|
|
|
'currentPath' => $changeset->getFilename(),
|
2011-10-14 21:08:31 +02:00
|
|
|
'awayPaths' => $changeset->getAwayPaths(),
|
|
|
|
'oldProperties' => $changeset->getOldProperties(),
|
|
|
|
'newProperties' => $changeset->getNewProperties(),
|
|
|
|
'type' => $changeset->getChangeType(),
|
|
|
|
'fileType' => $changeset->getFileType(),
|
|
|
|
'commitHash' => null,
|
2011-12-14 20:52:28 +01:00
|
|
|
'addLines' => $changeset->getAddLines(),
|
|
|
|
'delLines' => $changeset->getDelLines(),
|
2011-10-14 21:08:31 +02:00
|
|
|
'hunks' => $hunks,
|
|
|
|
);
|
2013-09-17 22:55:41 +02:00
|
|
|
$changes[] = $change;
|
2011-10-14 21:08:31 +02:00
|
|
|
}
|
2013-09-17 22:55:41 +02:00
|
|
|
return $changes;
|
2012-11-09 22:33:58 +01:00
|
|
|
}
|
|
|
|
|
2014-11-19 21:16:07 +01:00
|
|
|
public function hasRevision() {
|
|
|
|
return $this->revision !== self::ATTACHABLE;
|
|
|
|
}
|
|
|
|
|
2013-09-27 03:45:04 +02:00
|
|
|
public function getRevision() {
|
|
|
|
return $this->assertAttached($this->revision);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function attachRevision(DifferentialRevision $revision = null) {
|
|
|
|
$this->revision = $revision;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2014-02-27 20:06:37 +01:00
|
|
|
public function attachProperty($key, $value) {
|
|
|
|
$this->properties[$key] = $value;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getProperty($key) {
|
|
|
|
return $this->assertAttachedKey($this->properties, $key);
|
|
|
|
}
|
|
|
|
|
2016-02-29 18:53:46 +01:00
|
|
|
public function hasDiffProperty($key) {
|
|
|
|
$properties = $this->getDiffProperties();
|
|
|
|
return array_key_exists($key, $properties);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function attachDiffProperties(array $properties) {
|
|
|
|
$this->properties = $properties;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getDiffProperties() {
|
|
|
|
return $this->assertAttached($this->properties);
|
|
|
|
}
|
|
|
|
|
2015-06-23 16:09:23 +02:00
|
|
|
public function attachBuildable(HarbormasterBuildable $buildable = null) {
|
|
|
|
$this->buildable = $buildable;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getBuildable() {
|
|
|
|
return $this->assertAttached($this->buildable);
|
|
|
|
}
|
|
|
|
|
2015-08-10 23:16:36 +02:00
|
|
|
public function getBuildTargetPHIDs() {
|
|
|
|
$buildable = $this->getBuildable();
|
|
|
|
|
|
|
|
if (!$buildable) {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$target_phids = array();
|
|
|
|
foreach ($buildable->getBuilds() as $build) {
|
|
|
|
foreach ($build->getBuildTargets() as $target) {
|
|
|
|
$target_phids[] = $target->getPHID();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $target_phids;
|
|
|
|
}
|
2013-07-01 21:38:42 +02:00
|
|
|
|
2015-08-11 00:24:15 +02:00
|
|
|
public function loadCoverageMap(PhabricatorUser $viewer) {
|
|
|
|
$target_phids = $this->getBuildTargetPHIDs();
|
|
|
|
if (!$target_phids) {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$unit = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
|
|
|
|
'buildTargetPHID IN (%Ls)',
|
|
|
|
$target_phids);
|
|
|
|
|
|
|
|
$map = array();
|
|
|
|
foreach ($unit as $message) {
|
|
|
|
$coverage = $message->getProperty('coverage', array());
|
|
|
|
foreach ($coverage as $path => $coverage_data) {
|
|
|
|
$map[$path][] = $coverage_data;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($map as $path => $coverage_items) {
|
|
|
|
$map[$path] = ArcanistUnitTestResult::mergeCoverage($coverage_items);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $map;
|
|
|
|
}
|
|
|
|
|
2016-01-08 13:35:17 +01:00
|
|
|
public function getURI() {
|
|
|
|
$id = $this->getID();
|
|
|
|
return "/differential/diff/{$id}/";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-02-29 18:53:46 +01:00
|
|
|
public function attachUnitMessages(array $unit_messages) {
|
|
|
|
$this->unitMessages = $unit_messages;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function getUnitMessages() {
|
|
|
|
return $this->assertAttached($this->unitMessages);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-01 21:38:42 +02:00
|
|
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getCapabilities() {
|
|
|
|
return array(
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getPolicy($capability) {
|
2014-11-19 21:16:07 +01:00
|
|
|
if ($this->hasRevision()) {
|
2017-01-01 17:36:38 +01:00
|
|
|
return PhabricatorPolicies::getMostOpenPolicy();
|
2013-09-27 03:45:04 +02:00
|
|
|
}
|
|
|
|
|
2014-11-19 21:16:07 +01:00
|
|
|
return $this->viewPolicy;
|
2013-07-01 21:38:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
2014-11-19 21:16:07 +01:00
|
|
|
if ($this->hasRevision()) {
|
2013-09-27 03:45:04 +02:00
|
|
|
return $this->getRevision()->hasAutomaticCapability($capability, $viewer);
|
|
|
|
}
|
|
|
|
|
2017-01-01 17:36:38 +01:00
|
|
|
return ($this->getAuthorPHID() == $viewer->getPHID());
|
2013-07-01 21:38:42 +02:00
|
|
|
}
|
|
|
|
|
2013-09-27 17:43:41 +02:00
|
|
|
public function describeAutomaticCapability($capability) {
|
2014-11-19 21:16:07 +01:00
|
|
|
if ($this->hasRevision()) {
|
2013-09-27 17:43:41 +02:00
|
|
|
return pht(
|
|
|
|
'This diff is attached to a revision, and inherits its policies.');
|
|
|
|
}
|
2017-01-01 17:36:38 +01:00
|
|
|
|
2014-11-19 21:16:07 +01:00
|
|
|
return pht('The author of a diff can see it.');
|
2013-09-27 17:43:41 +02:00
|
|
|
}
|
|
|
|
|
2013-12-26 19:40:52 +01:00
|
|
|
|
2017-01-01 17:36:38 +01:00
|
|
|
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
|
|
|
|
$extended = array();
|
|
|
|
|
|
|
|
switch ($capability) {
|
|
|
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
|
|
|
if ($this->hasRevision()) {
|
|
|
|
$extended[] = array(
|
|
|
|
$this->getRevision(),
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $extended;
|
|
|
|
}
|
|
|
|
|
2013-12-26 19:40:52 +01:00
|
|
|
|
|
|
|
/* -( HarbormasterBuildableInterface )------------------------------------- */
|
|
|
|
|
|
|
|
|
2016-02-26 21:20:47 +01:00
|
|
|
public function getHarbormasterBuildableDisplayPHID() {
|
|
|
|
$container_phid = $this->getHarbormasterContainerPHID();
|
|
|
|
if ($container_phid) {
|
|
|
|
return $container_phid;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->getHarbormasterBuildablePHID();
|
|
|
|
}
|
|
|
|
|
2013-12-26 19:40:52 +01:00
|
|
|
public function getHarbormasterBuildablePHID() {
|
|
|
|
return $this->getPHID();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getHarbormasterContainerPHID() {
|
|
|
|
if ($this->getRevisionID()) {
|
|
|
|
$revision = id(new DifferentialRevision())->load($this->getRevisionID());
|
|
|
|
if ($revision) {
|
|
|
|
return $revision->getPHID();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-01-04 20:31:44 +01:00
|
|
|
public function getHarbormasterPublishablePHID() {
|
|
|
|
return $this->getHarbormasterContainerPHID();
|
|
|
|
}
|
|
|
|
|
2014-06-20 04:58:23 +02:00
|
|
|
public function getBuildVariables() {
|
|
|
|
$results = array();
|
|
|
|
|
|
|
|
$results['buildable.diff'] = $this->getID();
|
Add "Autoplans" to Harbormaster
Summary:
Ref T8095. Two general problems:
- I want Harbormaster to own all lint and unit test results.
- I don't want users to have to configure anything for `arc` to keep working automatically.
These are in conflict because generic lint/unit test ownership in Harbormaster requires that build targets exist which we can attach build results to. However, we can't currently create build targets on demand: Harbormaster assumes it is responsible for creating targets, then running code or making third-party service calls to actually run the builds.
I considered two broad approaches to let `arc` push results into Harbormaster without requiring administrators to configure some kind of "arc results" build plan:
# Add magic target PHIDs like `PHID-MAGIC-this-is-really-arc-unit`.
# Add new code to build real targets with real PHIDs.
(1) is probably a bit less work to get off the ground, but I think it's worse overall and very likely to create more problems in the long run. I particularly worry that it will lead to a small amount of special casing in a very large number of places, which seems more fragile.
(2) is more work upfront but I think does a better job of putting all the special casing in one place that we can, e.g., more reasonably unit test, and letting the rest of the code rarely/never care about this case since it's just dealing with normal plans/steps/targets as far as it can tell.
This diff introduces "autoplans", which are source templates for plans/steps. This let us "push" these targets into Harbormaster. Hypthetically, any process "like" arc can use autoplans to upload test/lint/etc results. In practice, probably only `arc` will ever use this, but I think it's still quite a bit cleaner than the alternative despite all the generality.
Workflow is basically:
- `arc` creates a diff.
- `arc` calls `harbormaster.queryautotargets`, passing the diff PHID and saying "I have some lint and unit results I want to stick on this thing".
- Harbormaster builds the plan, steps, and targets (if any of them don't already exist), and hands back the target PHIDs so `arc` has a completely standard-looking place to put results.
- `arc` uploads the test results to the right targets, as though Harbormaster had asked it to run unit/lint in the first place.
(This doesn't actually do any of that yet, just sets things up.)
I'll maybe doc turn that ^^^^^^ into a doc for posterity since I think it's hard to guess what an "autotarget" is, but I'm going to grab some lunch first.
Test Plan:
- Added unit tests to make sure we can build these things properly.
- Used `harbormaster.queryautotargets` to build autotargets for a bunch of diffs.
- Verified targets come up in "waiting for message" state.
- Verified plans and steps are not editable.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: hach-que, epriestley
Maniphest Tasks: T8095
Differential Revision: https://secure.phabricator.com/D13345
2015-06-21 18:04:21 +02:00
|
|
|
if ($this->revisionID) {
|
|
|
|
$revision = $this->getRevision();
|
|
|
|
$results['buildable.revision'] = $revision->getID();
|
|
|
|
$repo = $revision->getRepository();
|
|
|
|
|
|
|
|
if ($repo) {
|
|
|
|
$results['repository.callsign'] = $repo->getCallsign();
|
2015-09-25 02:29:47 +02:00
|
|
|
$results['repository.phid'] = $repo->getPHID();
|
Add "Autoplans" to Harbormaster
Summary:
Ref T8095. Two general problems:
- I want Harbormaster to own all lint and unit test results.
- I don't want users to have to configure anything for `arc` to keep working automatically.
These are in conflict because generic lint/unit test ownership in Harbormaster requires that build targets exist which we can attach build results to. However, we can't currently create build targets on demand: Harbormaster assumes it is responsible for creating targets, then running code or making third-party service calls to actually run the builds.
I considered two broad approaches to let `arc` push results into Harbormaster without requiring administrators to configure some kind of "arc results" build plan:
# Add magic target PHIDs like `PHID-MAGIC-this-is-really-arc-unit`.
# Add new code to build real targets with real PHIDs.
(1) is probably a bit less work to get off the ground, but I think it's worse overall and very likely to create more problems in the long run. I particularly worry that it will lead to a small amount of special casing in a very large number of places, which seems more fragile.
(2) is more work upfront but I think does a better job of putting all the special casing in one place that we can, e.g., more reasonably unit test, and letting the rest of the code rarely/never care about this case since it's just dealing with normal plans/steps/targets as far as it can tell.
This diff introduces "autoplans", which are source templates for plans/steps. This let us "push" these targets into Harbormaster. Hypthetically, any process "like" arc can use autoplans to upload test/lint/etc results. In practice, probably only `arc` will ever use this, but I think it's still quite a bit cleaner than the alternative despite all the generality.
Workflow is basically:
- `arc` creates a diff.
- `arc` calls `harbormaster.queryautotargets`, passing the diff PHID and saying "I have some lint and unit results I want to stick on this thing".
- Harbormaster builds the plan, steps, and targets (if any of them don't already exist), and hands back the target PHIDs so `arc` has a completely standard-looking place to put results.
- `arc` uploads the test results to the right targets, as though Harbormaster had asked it to run unit/lint in the first place.
(This doesn't actually do any of that yet, just sets things up.)
I'll maybe doc turn that ^^^^^^ into a doc for posterity since I think it's hard to guess what an "autotarget" is, but I'm going to grab some lunch first.
Test Plan:
- Added unit tests to make sure we can build these things properly.
- Used `harbormaster.queryautotargets` to build autotargets for a bunch of diffs.
- Verified targets come up in "waiting for message" state.
- Verified plans and steps are not editable.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: hach-que, epriestley
Maniphest Tasks: T8095
Differential Revision: https://secure.phabricator.com/D13345
2015-06-21 18:04:21 +02:00
|
|
|
$results['repository.vcs'] = $repo->getVersionControlSystem();
|
|
|
|
$results['repository.uri'] = $repo->getPublicCloneURI();
|
2015-10-02 01:55:01 +02:00
|
|
|
|
|
|
|
$results['repository.staging.uri'] = $repo->getStagingURI();
|
2015-10-14 00:46:30 +02:00
|
|
|
$results['repository.staging.ref'] = $this->getStagingRef();
|
Add "Autoplans" to Harbormaster
Summary:
Ref T8095. Two general problems:
- I want Harbormaster to own all lint and unit test results.
- I don't want users to have to configure anything for `arc` to keep working automatically.
These are in conflict because generic lint/unit test ownership in Harbormaster requires that build targets exist which we can attach build results to. However, we can't currently create build targets on demand: Harbormaster assumes it is responsible for creating targets, then running code or making third-party service calls to actually run the builds.
I considered two broad approaches to let `arc` push results into Harbormaster without requiring administrators to configure some kind of "arc results" build plan:
# Add magic target PHIDs like `PHID-MAGIC-this-is-really-arc-unit`.
# Add new code to build real targets with real PHIDs.
(1) is probably a bit less work to get off the ground, but I think it's worse overall and very likely to create more problems in the long run. I particularly worry that it will lead to a small amount of special casing in a very large number of places, which seems more fragile.
(2) is more work upfront but I think does a better job of putting all the special casing in one place that we can, e.g., more reasonably unit test, and letting the rest of the code rarely/never care about this case since it's just dealing with normal plans/steps/targets as far as it can tell.
This diff introduces "autoplans", which are source templates for plans/steps. This let us "push" these targets into Harbormaster. Hypthetically, any process "like" arc can use autoplans to upload test/lint/etc results. In practice, probably only `arc` will ever use this, but I think it's still quite a bit cleaner than the alternative despite all the generality.
Workflow is basically:
- `arc` creates a diff.
- `arc` calls `harbormaster.queryautotargets`, passing the diff PHID and saying "I have some lint and unit results I want to stick on this thing".
- Harbormaster builds the plan, steps, and targets (if any of them don't already exist), and hands back the target PHIDs so `arc` has a completely standard-looking place to put results.
- `arc` uploads the test results to the right targets, as though Harbormaster had asked it to run unit/lint in the first place.
(This doesn't actually do any of that yet, just sets things up.)
I'll maybe doc turn that ^^^^^^ into a doc for posterity since I think it's hard to guess what an "autotarget" is, but I'm going to grab some lunch first.
Test Plan:
- Added unit tests to make sure we can build these things properly.
- Used `harbormaster.queryautotargets` to build autotargets for a bunch of diffs.
- Verified targets come up in "waiting for message" state.
- Verified plans and steps are not editable.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: hach-que, epriestley
Maniphest Tasks: T8095
Differential Revision: https://secure.phabricator.com/D13345
2015-06-21 18:04:21 +02:00
|
|
|
}
|
2014-06-20 04:58:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getAvailableBuildVariables() {
|
|
|
|
return array(
|
|
|
|
'buildable.diff' =>
|
|
|
|
pht('The differential diff ID, if applicable.'),
|
|
|
|
'buildable.revision' =>
|
|
|
|
pht('The differential revision ID, if applicable.'),
|
|
|
|
'repository.callsign' =>
|
|
|
|
pht('The callsign of the repository in Phabricator.'),
|
2015-09-25 02:29:47 +02:00
|
|
|
'repository.phid' =>
|
|
|
|
pht('The PHID of the repository in Phabricator.'),
|
2014-06-20 04:58:23 +02:00
|
|
|
'repository.vcs' =>
|
|
|
|
pht('The version control system, either "svn", "hg" or "git".'),
|
|
|
|
'repository.uri' =>
|
|
|
|
pht('The URI to clone or checkout the repository from.'),
|
2015-10-02 01:55:01 +02:00
|
|
|
'repository.staging.uri' =>
|
|
|
|
pht('The URI of the staging repository.'),
|
|
|
|
'repository.staging.ref' =>
|
|
|
|
pht('The ref name for this change in the staging repository.'),
|
2014-06-20 04:58:23 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2015-10-15 05:01:22 +02:00
|
|
|
|
|
|
|
/* -( HarbormasterCircleCIBuildableInterface )----------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getCircleCIGitHubRepositoryURI() {
|
|
|
|
$diff_phid = $this->getPHID();
|
|
|
|
$repository_phid = $this->getRepositoryPHID();
|
|
|
|
if (!$repository_phid) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'This diff ("%s") is not associated with a repository. A diff '.
|
|
|
|
'must belong to a tracked repository to be built by CircleCI.',
|
|
|
|
$diff_phid));
|
|
|
|
}
|
|
|
|
|
|
|
|
$repository = id(new PhabricatorRepositoryQuery())
|
|
|
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
|
|
|
->withPHIDs(array($repository_phid))
|
|
|
|
->executeOne();
|
|
|
|
if (!$repository) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'This diff ("%s") is associated with a repository ("%s") which '.
|
|
|
|
'could not be loaded.',
|
|
|
|
$diff_phid,
|
|
|
|
$repository_phid));
|
|
|
|
}
|
|
|
|
|
|
|
|
$staging_uri = $repository->getStagingURI();
|
|
|
|
if (!$staging_uri) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'This diff ("%s") is associated with a repository ("%s") that '.
|
|
|
|
'does not have a Staging Area configured. You must configure a '.
|
|
|
|
'Staging Area to use CircleCI integration.',
|
|
|
|
$diff_phid,
|
|
|
|
$repository_phid));
|
|
|
|
}
|
|
|
|
|
|
|
|
$path = HarbormasterCircleCIBuildStepImplementation::getGitHubPath(
|
|
|
|
$staging_uri);
|
|
|
|
if (!$path) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'This diff ("%s") is associated with a repository ("%s") that '.
|
|
|
|
'does not have a Staging Area ("%s") that is hosted on GitHub. '.
|
|
|
|
'CircleCI can only build from GitHub, so the Staging Area for '.
|
|
|
|
'the repository must be hosted there.',
|
|
|
|
$diff_phid,
|
|
|
|
$repository_phid,
|
|
|
|
$staging_uri));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $staging_uri;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCircleCIBuildIdentifierType() {
|
|
|
|
return 'tag';
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCircleCIBuildIdentifier() {
|
|
|
|
$ref = $this->getStagingRef();
|
|
|
|
$ref = preg_replace('(^refs/tags/)', '', $ref);
|
|
|
|
return $ref;
|
|
|
|
}
|
|
|
|
|
2015-10-14 00:46:30 +02:00
|
|
|
public function getStagingRef() {
|
|
|
|
// TODO: We're just hoping to get lucky. Instead, `arc` should store
|
|
|
|
// where it sent changes and we should only provide staging details
|
|
|
|
// if we reasonably believe they are accurate.
|
|
|
|
return 'refs/tags/phabricator/diff/'.$this->getID();
|
|
|
|
}
|
|
|
|
|
2015-12-10 23:50:58 +01:00
|
|
|
public function loadTargetBranch() {
|
|
|
|
// TODO: This is sketchy, but just eat the query cost until this can get
|
|
|
|
// cleaned up.
|
|
|
|
|
|
|
|
// For now, we're only returning a target if there's exactly one and it's
|
|
|
|
// a branch, since we don't support landing to more esoteric targets like
|
|
|
|
// tags yet.
|
|
|
|
|
|
|
|
$property = id(new DifferentialDiffProperty())->loadOneWhere(
|
|
|
|
'diffID = %d AND name = %s',
|
|
|
|
$this->getID(),
|
|
|
|
'arc:onto');
|
|
|
|
if (!$property) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = $property->getData();
|
|
|
|
|
|
|
|
if (!$data) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!is_array($data)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count($data) != 1) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$onto = head($data);
|
|
|
|
if (!is_array($onto)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$type = idx($onto, 'type');
|
|
|
|
if ($type != 'branch') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return idx($onto, 'name');
|
|
|
|
}
|
|
|
|
|
2014-04-18 01:03:24 +02:00
|
|
|
|
|
|
|
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getApplicationTransactionEditor() {
|
2014-12-01 23:46:25 +01:00
|
|
|
return new DifferentialDiffEditor();
|
2014-04-18 01:03:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getApplicationTransactionObject() {
|
2014-12-01 23:46:25 +01:00
|
|
|
return $this;
|
2014-04-18 01:03:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getApplicationTransactionTemplate() {
|
2014-12-01 23:46:25 +01:00
|
|
|
return new DifferentialDiffTransaction();
|
2014-04-18 01:03:24 +02:00
|
|
|
}
|
|
|
|
|
2014-12-04 22:58:52 +01:00
|
|
|
public function willRenderTimeline(
|
|
|
|
PhabricatorApplicationTransactionView $timeline,
|
|
|
|
AphrontRequest $request) {
|
|
|
|
|
|
|
|
return $timeline;
|
|
|
|
}
|
|
|
|
|
2014-05-02 03:25:30 +02:00
|
|
|
|
2014-07-21 15:59:22 +02:00
|
|
|
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
2014-05-02 03:25:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
public function destroyObjectPermanently(
|
|
|
|
PhabricatorDestructionEngine $engine) {
|
|
|
|
|
|
|
|
$this->openTransaction();
|
|
|
|
$this->delete();
|
|
|
|
|
|
|
|
foreach ($this->loadChangesets() as $changeset) {
|
|
|
|
$changeset->delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
|
|
|
|
'diffID = %d',
|
|
|
|
$this->getID());
|
|
|
|
foreach ($properties as $prop) {
|
|
|
|
$prop->delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->saveTransaction();
|
|
|
|
}
|
|
|
|
|
2011-01-24 20:01:53 +01:00
|
|
|
}
|