Page MenuHomec4science

DifferentialDiffExtractionEngine.php
No OneTemporary

File Metadata

Created
Mon, May 6, 09:04

DifferentialDiffExtractionEngine.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();
// If we already have an unattached diff for this commit, just reuse it.
// This stops us from repeatedly generating diffs if something goes wrong
// later in the process. See T10968 for context.
$existing_diffs = id(new DifferentialDiffQuery())
->setViewer($viewer)
->withCommitPHIDs(array($commit->getPHID()))
->withHasRevision(false)
->needChangesets(true)
->execute();
if ($existing_diffs) {
return head($existing_diffs);
}
$repository = $commit->getRepository();
$identifier = $commit->getCommitIdentifier();
$monogram = $commit->getMonogram();
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => $viewer,
'repository' => $repository,
));
$diff_info = DiffusionQuery::callConduitWithDiffusionRequest(
$viewer,
$drequest,
'diffusion.rawdiffquery',
array(
'commit' => $identifier,
));
$file_phid = $diff_info['filePHID'];
$diff_file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->executeOne();
if (!$diff_file) {
throw new Exception(
pht(
'Failed to load file ("%s") returned by "%s".',
$file_phid,
'diffusion.rawdiffquery'));
}
$raw_diff = $diff_file->loadFileData();
// 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())
->setCommitPHID($commit->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,
));
try {
$response = DiffusionQuery::callConduitWithDiffusionRequest(
$viewer,
$drequest,
'diffusion.filecontentquery',
array(
'commit' => $identifier,
'path' => $path,
));
} catch (Exception $ex) {
// TODO: See PHI1044. This call may fail if the diff deleted the
// file. If the call fails, just detect a change for now. This should
// generally be made cleaner in the future.
return true;
}
$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 DifferentialHunk())->setChanges($context);
$vs_hunk = id(new DifferentialHunk())->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();
// If the revision isn't closed or "Accepted", write a warning into the
// transaction log. This makes it more clear when users bend the rules.
if (!$revision->isClosed() && !$revision->isAccepted()) {
$wrong_type = DifferentialRevisionWrongStateTransaction::TRANSACTIONTYPE;
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($wrong_type)
->setNewValue($revision->getModernRevisionStatus());
}
$type_update = DifferentialRevisionUpdateTransaction::TRANSACTIONTYPE;
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($type_update)
->setIgnoreOnNoEffect(true)
->setNewValue($new_diff->getPHID())
->setMetadataValue('isCommitUpdate', true)
->setMetadataValue('commitPHIDs', array($commit->getPHID()));
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;
}
}

Event Timeline