Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2890250
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
25 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/differential/controller/DifferentialRevisionOperationController.php b/src/applications/differential/controller/DifferentialRevisionOperationController.php
index 71eeeba6f1..99067f9f2e 100644
--- a/src/applications/differential/controller/DifferentialRevisionOperationController.php
+++ b/src/applications/differential/controller/DifferentialRevisionOperationController.php
@@ -1,152 +1,162 @@
<?php
final class DifferentialRevisionOperationController
extends DifferentialController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($id))
->setViewer($viewer)
->needActiveDiffs(true)
->executeOne();
if (!$revision) {
return new Aphront404Response();
}
$detail_uri = "/D{$id}";
$op = new DrydockLandRepositoryOperation();
$barrier = $op->getBarrierToLanding($viewer, $revision);
if ($barrier) {
return $this->newDialog()
->setTitle($barrier['title'])
->appendParagraph($barrier['body'])
->addCancelButton($detail_uri);
}
$diff = $revision->getActiveDiff();
$repository = $revision->getRepository();
- $default_ref = $this->loadDefaultRef($repository);
+ $default_ref = $this->loadDefaultRef($repository, $diff);
if ($default_ref) {
$v_ref = array($default_ref->getPHID());
} else {
$v_ref = array();
}
$e_ref = true;
$errors = array();
if ($request->isFormPost()) {
$v_ref = $request->getArr('refPHIDs');
$ref_phid = head($v_ref);
if (!strlen($ref_phid)) {
$e_ref = pht('Required');
$errors[] = pht(
'You must select a branch to land this revision onto.');
} else {
$ref = $this->newRefQuery($repository)
->withPHIDs(array($ref_phid))
->executeOne();
if (!$ref) {
$e_ref = pht('Invalid');
$errors[] = pht(
'You must select a branch from this repository to land this '.
'revision onto.');
}
}
if (!$errors) {
// NOTE: The operation is locked to the current active diff, so if the
// revision is updated before the operation applies nothing sneaky
// occurs.
$target = 'branch:'.$ref->getRefName();
$operation = DrydockRepositoryOperation::initializeNewOperation($op)
->setAuthorPHID($viewer->getPHID())
->setObjectPHID($revision->getPHID())
->setRepositoryPHID($repository->getPHID())
->setRepositoryTarget($target)
->setProperty('differential.diffPHID', $diff->getPHID());
$operation->save();
$operation->scheduleUpdate();
return id(new AphrontRedirectResponse())
->setURI($detail_uri);
}
}
$ref_datasource = id(new DiffusionRefDatasource())
->setParameters(
array(
'repositoryPHIDs' => array($repository->getPHID()),
'refTypes' => $this->getTargetableRefTypes(),
));
$form = id(new AphrontFormView())
->setUser($viewer)
->appendRemarkupInstructions(
pht(
'In theory, this will do approximately what `arc land` would do. '.
'In practice, you will have a riveting adventure instead.'))
->appendControl(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Onto Branch'))
->setName('refPHIDs')
->setLimit(1)
->setError($e_ref)
->setValue($v_ref)
->setDatasource($ref_datasource))
->appendRemarkupInstructions(
pht(
'(WARNING) THIS FEATURE IS EXPERIMENTAL AND DANGEROUS! USE IT AT '.
'YOUR OWN RISK!'));
return $this->newDialog()
->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle(pht('Land Revision'))
->setErrors($errors)
->appendForm($form)
->addCancelButton($detail_uri)
->addSubmitButton(pht('Mutate Repository Unpredictably'));
}
private function newRefQuery(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
return id(new PhabricatorRepositoryRefCursorQuery())
->setViewer($viewer)
->withRepositoryPHIDs(array($repository->getPHID()))
->withRefTypes($this->getTargetableRefTypes());
}
private function getTargetableRefTypes() {
return array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
);
}
- private function loadDefaultRef(PhabricatorRepository $repository) {
- $default_name = $this->getDefaultRefName($repository);
+ private function loadDefaultRef(
+ PhabricatorRepository $repository,
+ DifferentialDiff $diff) {
+ $default_name = $this->getDefaultRefName($repository, $diff);
if (!strlen($default_name)) {
return null;
}
return $this->newRefQuery($repository)
->withRefNames(array($default_name))
->executeOne();
}
- private function getDefaultRefName(PhabricatorRepository $repository) {
+ private function getDefaultRefName(
+ PhabricatorRepository $repository,
+ DifferentialDiff $diff) {
+
+ $onto = $diff->loadTargetBranch();
+ if ($onto !== null) {
+ return $onto;
+ }
+
return $repository->getDefaultBranch();
}
}
diff --git a/src/applications/differential/customfield/DifferentialBranchField.php b/src/applications/differential/customfield/DifferentialBranchField.php
index fc50943f1d..62f47a5e1a 100644
--- a/src/applications/differential/customfield/DifferentialBranchField.php
+++ b/src/applications/differential/customfield/DifferentialBranchField.php
@@ -1,88 +1,96 @@
<?php
final class DifferentialBranchField
extends DifferentialCustomField {
public function getFieldKey() {
return 'differential:branch';
}
public function getFieldName() {
return pht('Branch');
}
public function getFieldDescription() {
return pht('Shows the branch a diff came from.');
}
public function shouldAppearInPropertyView() {
return true;
}
public function renderPropertyViewValue(array $handles) {
return null;
}
public function shouldAppearInDiffPropertyView() {
return true;
}
public function renderDiffPropertyViewLabel(DifferentialDiff $diff) {
return $this->getFieldName();
}
public function renderDiffPropertyViewValue(DifferentialDiff $diff) {
return $this->getBranchDescription($diff);
}
private function getBranchDescription(DifferentialDiff $diff) {
$branch = $diff->getBranch();
$bookmark = $diff->getBookmark();
if (strlen($branch) && strlen($bookmark)) {
return pht('%s (bookmark) on %s (branch)', $bookmark, $branch);
} else if (strlen($bookmark)) {
return pht('%s (bookmark)', $bookmark);
} else if (strlen($branch)) {
- return $branch;
+ $onto = $diff->loadTargetBranch();
+ if (strlen($onto)) {
+ return pht(
+ '%s (branched from %s)',
+ $branch,
+ $onto);
+ } else {
+ return $branch;
+ }
} else {
return null;
}
}
public function getProTips() {
return array(
pht(
'In Git and Mercurial, use a branch like "%s" to automatically '.
'associate changes with the corresponding task.',
'T123'),
);
}
public function shouldAppearInTransactionMail() {
return true;
}
public function updateTransactionMailBody(
PhabricatorMetaMTAMailBody $body,
PhabricatorApplicationTransactionEditor $editor,
array $xactions) {
$status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED;
// Show the "BRANCH" section only if there's a new diff or the revision
// is "Accepted".
if ((!$editor->getDiffUpdateTransaction($xactions)) &&
($this->getObject()->getStatus() != $status_accepted)) {
return;
}
$branch = $this->getBranchDescription($this->getObject()->getActiveDiff());
if ($branch === null) {
return;
}
$body->addTextSection(pht('BRANCH'), $branch);
}
}
diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php
index 42bee674ba..d8491a6776 100644
--- a/src/applications/differential/storage/DifferentialDiff.php
+++ b/src/applications/differential/storage/DifferentialDiff.php
@@ -1,539 +1,582 @@
<?php
final class DifferentialDiff
extends DifferentialDAO
implements
PhabricatorPolicyInterface,
HarbormasterBuildableInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorDestructibleInterface {
protected $revisionID;
protected $authorPHID;
protected $repositoryPHID;
protected $sourceMachine;
protected $sourcePath;
protected $sourceControlSystem;
protected $sourceControlBaseRevision;
protected $sourceControlPath;
protected $lintStatus;
protected $unitStatus;
protected $lineCount;
protected $branch;
protected $bookmark;
protected $creationMethod;
protected $repositoryUUID;
protected $description;
protected $viewPolicy;
private $unsavedChangesets = array();
private $changesets = self::ATTACHABLE;
private $revision = self::ATTACHABLE;
private $properties = array();
private $buildable = self::ATTACHABLE;
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
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?',
// 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?',
),
self::CONFIG_KEY_SCHEMA => array(
'revisionID' => array(
'columns' => array('revisionID'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
DifferentialDiffPHIDType::TYPECONST);
}
public function addUnsavedChangeset(DifferentialChangeset $changeset) {
if ($this->changesets === null) {
$this->changesets = array();
}
$this->unsavedChangesets[] = $changeset;
$this->changesets[] = $changeset;
return $this;
}
public function attachChangesets(array $changesets) {
assert_instances_of($changesets, 'DifferentialChangeset');
$this->changesets = $changesets;
return $this;
}
public function getChangesets() {
return $this->assertAttached($this->changesets);
}
public function loadChangesets() {
if (!$this->getID()) {
return array();
}
$changesets = id(new DifferentialChangeset())->loadAllWhere(
'diffID = %d',
$this->getID());
foreach ($changesets as $changeset) {
$changeset->attachDiff($this);
}
return $changesets;
}
public function save() {
$this->openTransaction();
$ret = parent::save();
foreach ($this->unsavedChangesets as $changeset) {
$changeset->setDiffID($this->getID());
$changeset->save();
}
$this->saveTransaction();
return $ret;
}
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) {
assert_instances_of($changes, 'ArcanistDiffChange');
$diff = self::initializeNewDiff($actor);
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) {
// There may not be any changes; initialize the changesets list so that
// we don't throw later when accessing it.
$diff->attachChangesets(array());
$lines = 0;
foreach ($changes as $change) {
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;
}
$changeset = new DifferentialChangeset();
$add_lines = 0;
$del_lines = 0;
$first_line = PHP_INT_MAX;
$hunks = $change->getHunks();
if ($hunks) {
foreach ($hunks as $hunk) {
$dhunk = new DifferentialModernHunk();
$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();
$added_lines = $hunk->getChangedLines('new');
if ($added_lines) {
$first_line = min($first_line, head_key($added_lines));
}
}
$lines += $add_lines + $del_lines;
} else {
// This happens when you add empty files.
$changeset->attachHunks(array());
}
$metadata = $change->getAllMetadata();
if ($first_line != PHP_INT_MAX) {
$metadata['line:first'] = $first_line;
}
$changeset->setOldFile($change->getOldPath());
$changeset->setFilename($change->getCurrentPath());
$changeset->setChangeType($change->getType());
$changeset->setFileType($change->getFileType());
$changeset->setMetadata($metadata);
$changeset->setOldProperties($change->getOldProperties());
$changeset->setNewProperties($change->getNewProperties());
$changeset->setAwayPaths($change->getAwayPaths());
$changeset->setAddLines($add_lines);
$changeset->setDelLines($del_lines);
$diff->addUnsavedChangeset($changeset);
}
$diff->setLineCount($lines);
$parser = new DifferentialChangesetParser();
$changesets = $parser->detectCopiedCode(
$diff->getChangesets(),
$min_width = 30,
$min_lines = 3);
$diff->attachChangesets($changesets);
return $diff;
}
public function getDiffDict() {
$dict = array(
'id' => $this->getID(),
'revisionID' => $this->getRevisionID(),
'dateCreated' => $this->getDateCreated(),
'dateModified' => $this->getDateModified(),
'sourceControlBaseRevision' => $this->getSourceControlBaseRevision(),
'sourceControlPath' => $this->getSourceControlPath(),
'sourceControlSystem' => $this->getSourceControlSystem(),
'branch' => $this->getBranch(),
'bookmark' => $this->getBookmark(),
'creationMethod' => $this->getCreationMethod(),
'description' => $this->getDescription(),
'unitStatus' => $this->getUnitStatus(),
'lintStatus' => $this->getLintStatus(),
'changes' => array(),
'properties' => array(),
);
$dict['changes'] = $this->buildChangesList();
return $dict + $this->getDiffAuthorshipDict();
}
public function getDiffAuthorshipDict() {
$dict = array();
$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();
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(
'id' => $changeset->getID(),
'metadata' => $changeset->getMetadata(),
'oldPath' => $changeset->getOldFile(),
'currentPath' => $changeset->getFilename(),
'awayPaths' => $changeset->getAwayPaths(),
'oldProperties' => $changeset->getOldProperties(),
'newProperties' => $changeset->getNewProperties(),
'type' => $changeset->getChangeType(),
'fileType' => $changeset->getFileType(),
'commitHash' => null,
'addLines' => $changeset->getAddLines(),
'delLines' => $changeset->getDelLines(),
'hunks' => $hunks,
);
$changes[] = $change;
}
return $changes;
}
public function hasRevision() {
return $this->revision !== self::ATTACHABLE;
}
public function getRevision() {
return $this->assertAttached($this->revision);
}
public function attachRevision(DifferentialRevision $revision = null) {
$this->revision = $revision;
return $this;
}
public function attachProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function getProperty($key) {
return $this->assertAttachedKey($this->properties, $key);
}
public function attachBuildable(HarbormasterBuildable $buildable = null) {
$this->buildable = $buildable;
return $this;
}
public function getBuildable() {
return $this->assertAttached($this->buildable);
}
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;
}
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;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
if ($this->hasRevision()) {
return $this->getRevision()->getPolicy($capability);
}
return $this->viewPolicy;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->hasRevision()) {
return $this->getRevision()->hasAutomaticCapability($capability, $viewer);
}
return ($this->getAuthorPHID() == $viewer->getPhid());
}
public function describeAutomaticCapability($capability) {
if ($this->hasRevision()) {
return pht(
'This diff is attached to a revision, and inherits its policies.');
}
return pht('The author of a diff can see it.');
}
/* -( HarbormasterBuildableInterface )------------------------------------- */
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;
}
public function getBuildVariables() {
$results = array();
$results['buildable.diff'] = $this->getID();
if ($this->revisionID) {
$revision = $this->getRevision();
$results['buildable.revision'] = $revision->getID();
$repo = $revision->getRepository();
if ($repo) {
$results['repository.callsign'] = $repo->getCallsign();
$results['repository.phid'] = $repo->getPHID();
$results['repository.vcs'] = $repo->getVersionControlSystem();
$results['repository.uri'] = $repo->getPublicCloneURI();
$results['repository.staging.uri'] = $repo->getStagingURI();
$results['repository.staging.ref'] = $this->getStagingRef();
}
}
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.'),
'repository.phid' =>
pht('The PHID 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.'),
'repository.staging.uri' =>
pht('The URI of the staging repository.'),
'repository.staging.ref' =>
pht('The ref name for this change in the staging repository.'),
);
}
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();
}
+ 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');
+ }
+
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new DifferentialDiffEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new DifferentialDiffTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
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();
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 13:16 (3 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1124980
Default Alt Text
(25 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment