Page MenuHomec4science

DifferentialChangesetEngine.php
No OneTemporary

File Metadata

Created
Fri, Feb 14, 02:47

DifferentialChangesetEngine.php

<?php
final class DifferentialChangesetEngine extends Phobject {
public function rebuildChangesets(array $changesets) {
assert_instances_of($changesets, 'DifferentialChangeset');
foreach ($changesets as $changeset) {
$this->detectGeneratedCode($changeset);
$this->computeHashes($changeset);
}
$this->detectCopiedCode($changesets);
}
/* -( Generated Code )----------------------------------------------------- */
private function detectGeneratedCode(DifferentialChangeset $changeset) {
$is_generated_trusted = $this->isTrustedGeneratedCode($changeset);
if ($is_generated_trusted) {
$changeset->setTrustedChangesetAttribute(
DifferentialChangeset::ATTRIBUTE_GENERATED,
$is_generated_trusted);
}
$is_generated_untrusted = $this->isUntrustedGeneratedCode($changeset);
if ($is_generated_untrusted) {
$changeset->setUntrustedChangesetAttribute(
DifferentialChangeset::ATTRIBUTE_GENERATED,
$is_generated_untrusted);
}
}
private function isTrustedGeneratedCode(DifferentialChangeset $changeset) {
$filename = $changeset->getFilename();
$paths = PhabricatorEnv::getEnvConfig('differential.generated-paths');
foreach ($paths as $regexp) {
if (preg_match($regexp, $filename)) {
return true;
}
}
return false;
}
private function isUntrustedGeneratedCode(DifferentialChangeset $changeset) {
if ($changeset->getHunks()) {
$new_data = $changeset->makeNewFile();
if (strpos($new_data, '@'.'generated') !== false) {
return true;
}
// See PHI1112. This is the official pattern for marking Go code as
// generated.
if (preg_match('(^// Code generated .* DO NOT EDIT\.$)m', $new_data)) {
return true;
}
}
return false;
}
/* -( Content Hashes )----------------------------------------------------- */
private function computeHashes(DifferentialChangeset $changeset) {
$effect_key = DifferentialChangeset::METADATA_EFFECT_HASH;
$effect_hash = $this->newEffectHash($changeset);
if ($effect_hash !== null) {
$changeset->setChangesetMetadata($effect_key, $effect_hash);
}
}
private function newEffectHash(DifferentialChangeset $changeset) {
if ($changeset->getHunks()) {
$new_data = $changeset->makeNewFile();
return PhabricatorHash::digestForIndex($new_data);
}
return null;
}
/* -( Copied Code )-------------------------------------------------------- */
private function detectCopiedCode(array $changesets) {
// See PHI944. If the total number of changed lines is excessively large,
// don't bother with copied code detection. This can take a lot of time and
// memory and it's not generally of any use for very large changes.
$max_size = 65535;
$total_size = 0;
foreach ($changesets as $changeset) {
$total_size += ($changeset->getAddLines() + $changeset->getDelLines());
}
if ($total_size > $max_size) {
return;
}
$min_width = 30;
$min_lines = 3;
$map = array();
$files = array();
$types = array();
foreach ($changesets as $changeset) {
$file = $changeset->getFilename();
foreach ($changeset->getHunks() as $hunk) {
$lines = $hunk->getStructuredOldFile();
foreach ($lines as $line => $info) {
$type = $info['type'];
if ($type == '\\') {
continue;
}
$types[$file][$line] = $type;
$text = $info['text'];
$text = trim($text);
$files[$file][$line] = $text;
if (strlen($text) >= $min_width) {
$map[$text][] = array($file, $line);
}
}
}
}
foreach ($changesets as $changeset) {
$copies = array();
foreach ($changeset->getHunks() as $hunk) {
$added = $hunk->getStructuredNewFile();
$atype = array();
foreach ($added as $line => $info) {
$atype[$line] = $info['type'];
$added[$line] = trim($info['text']);
}
$skip_lines = 0;
foreach ($added as $line => $code) {
if ($skip_lines) {
// We're skipping lines that we already processed because we
// extended a block above them downward to include them.
$skip_lines--;
continue;
}
if ($atype[$line] !== '+') {
// This line hasn't been changed in the new file, so don't try
// to figure out where it came from.
continue;
}
if (empty($map[$code])) {
// This line was too short to trigger copy/move detection.
continue;
}
if (count($map[$code]) > 16) {
// If there are a large number of identical lines in this diff,
// don't try to figure out where this block came from: the analysis
// is O(N^2), since we need to compare every line against every
// other line. Even if we arrive at a result, it is unlikely to be
// meaningful. See T5041.
continue;
}
$best_length = 0;
// Explore all candidates.
foreach ($map[$code] as $val) {
list($file, $orig_line) = $val;
$length = 1;
// Search backward and forward to find all of the adjacent lines
// which match.
foreach (array(-1, 1) as $direction) {
$offset = $direction;
while (true) {
if (isset($copies[$line + $offset])) {
// If we run into a block above us which we've already
// attributed to a move or copy from elsewhere, stop
// looking.
break;
}
if (!isset($added[$line + $offset])) {
// If we've run off the beginning or end of the new file,
// stop looking.
break;
}
if (!isset($files[$file][$orig_line + $offset])) {
// If we've run off the beginning or end of the original
// file, we also stop looking.
break;
}
$old = $files[$file][$orig_line + $offset];
$new = $added[$line + $offset];
if ($old !== $new) {
// If the old line doesn't match the new line, stop
// looking.
break;
}
$length++;
$offset += $direction;
}
}
if ($length < $best_length) {
// If we already know of a better source (more matching lines)
// for this move/copy, stick with that one. We prefer long
// copies/moves which match a lot of context over short ones.
continue;
}
if ($length == $best_length) {
if (idx($types[$file], $orig_line) != '-') {
// If we already know of an equally good source (same number
// of matching lines) and this isn't a move, stick with the
// other one. We prefer moves over copies.
continue;
}
}
$best_length = $length;
// ($offset - 1) contains number of forward matching lines.
$best_offset = $offset - 1;
$best_file = $file;
$best_line = $orig_line;
}
$file = ($best_file == $changeset->getFilename() ? '' : $best_file);
for ($i = $best_length; $i--; ) {
$type = idx($types[$best_file], $best_line + $best_offset - $i);
$copies[$line + $best_offset - $i] = ($best_length < $min_lines
? array() // Ignore short blocks.
: array($file, $best_line + $best_offset - $i, $type));
}
$skip_lines = $best_offset;
}
}
$copies = array_filter($copies);
if ($copies) {
$metadata = $changeset->getMetadata();
$metadata['copy:lines'] = $copies;
$changeset->setMetadata($metadata);
}
}
}
}

Event Timeline