1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-28 09:42:41 +01:00
phorge-phorge/src/applications/differential/engine/DifferentialDiffExtractionEngine.php
epriestley d1fb2f7fb9 Make diffusion.filecontentquery return file PHIDs instead of raw content
Summary:
Fixes T9319. Proxied requests (e.g., in the cluster) for binary files (like images) currently fail because we can not return binary data over Conduit in JSON.

Although Conduit will eventually support binary-safe encodings, a cleaner approach to this is just to return a `filePHID` instead of the raw content. This is generally faster and more flexible, and gives us more opportunities to add caching later.

After making the call, the client pulls the file data separately.

We also no longer need to return a complex data structure because we don't do blame over this call any longer.

Test Plan:
  - Viewed images in Diffusion.
  - Viewed READMEs in Diffusion.
  - Used `bin/differential attach-commit rX Dy` to hit attach pathway.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9319

Differential Revision: https://secure.phabricator.com/D14970
2016-01-08 09:29:16 -08:00

276 lines
7.9 KiB
PHP

<?php
final class DifferentialDiffExtractionEngine extends Phobject {
private $viewer;
private $authorPHID;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setAuthorPHID($author_phid) {
$this->authorPHID = $author_phid;
return $this;
}
public function getAuthorPHID() {
return $this->authorPHID;
}
public function newDiffFromCommit(PhabricatorRepositoryCommit $commit) {
$viewer = $this->getViewer();
$repository = $commit->getRepository();
$identifier = $commit->getCommitIdentifier();
$monogram = $commit->getMonogram();
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => $viewer,
'repository' => $repository,
));
$raw_diff = DiffusionQuery::callConduitWithDiffusionRequest(
$viewer,
$drequest,
'diffusion.rawdiffquery',
array(
'commit' => $identifier,
));
// TODO: Support adds, deletes and moves under SVN.
if (strlen($raw_diff)) {
$changes = id(new ArcanistDiffParser())->parseDiff($raw_diff);
} else {
// This is an empty diff, maybe made with `git commit --allow-empty`.
// NOTE: These diffs have the same tree hash as their ancestors, so
// they may attach to revisions in an unexpected way. Just let this
// happen for now, although it might make sense to special case it
// eventually.
$changes = array();
}
$diff = DifferentialDiff::newFromRawChanges($viewer, $changes)
->setRepositoryPHID($repository->getPHID())
->setCreationMethod('commit')
->setSourceControlSystem($repository->getVersionControlSystem())
->setLintStatus(DifferentialLintStatus::LINT_AUTO_SKIP)
->setUnitStatus(DifferentialUnitStatus::UNIT_AUTO_SKIP)
->setDateCreated($commit->getEpoch())
->setDescription($monogram);
$author_phid = $this->getAuthorPHID();
if ($author_phid !== null) {
$diff->setAuthorPHID($author_phid);
}
$parents = DiffusionQuery::callConduitWithDiffusionRequest(
$viewer,
$drequest,
'diffusion.commitparentsquery',
array(
'commit' => $identifier,
));
if ($parents) {
$diff->setSourceControlBaseRevision(head($parents));
}
// TODO: Attach binary files.
return $diff->save();
}
public function isDiffChangedBeforeCommit(
PhabricatorRepositoryCommit $commit,
DifferentialDiff $old,
DifferentialDiff $new) {
$viewer = $this->getViewer();
$repository = $commit->getRepository();
$identifier = $commit->getCommitIdentifier();
$vs_changesets = array();
foreach ($old->getChangesets() as $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $old);
$path = ltrim($path, '/');
$vs_changesets[$path] = $changeset;
}
$changesets = array();
foreach ($new->getChangesets() as $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $new);
$path = ltrim($path, '/');
$changesets[$path] = $changeset;
}
if (array_fill_keys(array_keys($changesets), true) !=
array_fill_keys(array_keys($vs_changesets), true)) {
return true;
}
$file_phids = array();
foreach ($vs_changesets as $changeset) {
$metadata = $changeset->getMetadata();
$file_phid = idx($metadata, 'new:binary-phid');
if ($file_phid) {
$file_phids[$file_phid] = $file_phid;
}
}
$files = array();
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
}
foreach ($changesets as $path => $changeset) {
$vs_changeset = $vs_changesets[$path];
$file_phid = idx($vs_changeset->getMetadata(), 'new:binary-phid');
if ($file_phid) {
if (!isset($files[$file_phid])) {
return true;
}
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => $viewer,
'repository' => $repository,
));
$response = DiffusionQuery::callConduitWithDiffusionRequest(
$viewer,
$drequest,
'diffusion.filecontentquery',
array(
'commit' => $identifier,
'path' => $path,
));
$new_file_phid = $response['filePHID'];
if (!$new_file_phid) {
return true;
}
$new_file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($new_file_phid))
->executeOne();
if (!$new_file) {
return true;
}
if ($files[$file_phid]->loadFileData() != $new_file->loadFileData()) {
return true;
}
} else {
$context = implode("\n", $changeset->makeChangesWithContext());
$vs_context = implode("\n", $vs_changeset->makeChangesWithContext());
// We couldn't just compare $context and $vs_context because following
// diffs will be considered different:
//
// -(empty line)
// -echo 'test';
// (empty line)
//
// (empty line)
// -echo "test";
// -(empty line)
$hunk = id(new DifferentialModernHunk())->setChanges($context);
$vs_hunk = id(new DifferentialModernHunk())->setChanges($vs_context);
if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() ||
$hunk->makeNewFile() != $vs_hunk->makeNewFile()) {
return true;
}
}
}
return false;
}
public function updateRevisionWithCommit(
DifferentialRevision $revision,
PhabricatorRepositoryCommit $commit,
array $more_xactions,
PhabricatorContentSource $content_source) {
$viewer = $this->getViewer();
$result_data = array();
$new_diff = $this->newDiffFromCommit($commit);
$old_diff = $revision->getActiveDiff();
$changed_uri = null;
if ($old_diff) {
$old_diff = id(new DifferentialDiffQuery())
->setViewer($viewer)
->withIDs(array($old_diff->getID()))
->needChangesets(true)
->executeOne();
if ($old_diff) {
$has_changed = $this->isDiffChangedBeforeCommit(
$commit,
$old_diff,
$new_diff);
if ($has_changed) {
$result_data['vsDiff'] = $old_diff->getID();
$revision_monogram = $revision->getMonogram();
$old_id = $old_diff->getID();
$new_id = $new_diff->getID();
$changed_uri = "/{$revision_monogram}?vs={$old_id}&id={$new_id}#toc";
$changed_uri = PhabricatorEnv::getProductionURI($changed_uri);
}
}
}
$xactions = array();
$xactions[] = id(new DifferentialTransaction())
->setTransactionType(DifferentialTransaction::TYPE_UPDATE)
->setIgnoreOnNoEffect(true)
->setNewValue($new_diff->getPHID())
->setMetadataValue('isCommitUpdate', true);
foreach ($more_xactions as $more_xaction) {
$xactions[] = $more_xaction;
}
$editor = id(new DifferentialTransactionEditor())
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContentSource($content_source)
->setChangedPriorToCommitURI($changed_uri)
->setIsCloseByCommit(true);
$author_phid = $this->getAuthorPHID();
if ($author_phid !== null) {
$editor->setActingAsPHID($author_phid);
}
try {
$editor->applyTransactions($revision, $xactions);
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
// NOTE: We've marked transactions other than the CLOSE transaction
// as ignored when they don't have an effect, so this means that we
// lost a race to close the revision. That's perfectly fine, we can
// just continue normally.
}
return $result_data;
}
}