Path: blob/master/src/infrastructure/diff/PhabricatorDifferenceEngine.php
12241 views
<?php12/**3* Utility class which encapsulates some shared behavior between different4* applications which render diffs.5*6* @task config Configuring the Engine7* @task diff Generating Diffs8*/9final class PhabricatorDifferenceEngine extends Phobject {101112private $oldName;13private $newName;14private $normalize;151617/* -( Configuring the Engine )--------------------------------------------- */181920/**21* Set the name to identify the old file with. Primarily cosmetic.22*23* @param string Old file name.24* @return this25* @task config26*/27public function setOldName($old_name) {28$this->oldName = $old_name;29return $this;30}313233/**34* Set the name to identify the new file with. Primarily cosmetic.35*36* @param string New file name.37* @return this38* @task config39*/40public function setNewName($new_name) {41$this->newName = $new_name;42return $this;43}444546public function setNormalize($normalize) {47$this->normalize = $normalize;48return $this;49}5051public function getNormalize() {52return $this->normalize;53}545556/* -( Generating Diffs )--------------------------------------------------- */575859/**60* Generate a raw diff from two raw files. This is a lower-level API than61* @{method:generateChangesetFromFileContent}, but may be useful if you need62* to use a custom parser configuration, as with Diffusion.63*64* @param string Entire previous file content.65* @param string Entire current file content.66* @return string Raw diff between the two files.67* @task diff68*/69public function generateRawDiffFromFileContent($old, $new) {7071$options = array();7273// Generate diffs with full context.74$options[] = '-U65535';7576$old_name = nonempty($this->oldName, '/dev/universe').' 9999-99-99';77$new_name = nonempty($this->newName, '/dev/universe').' 9999-99-99';7879$options[] = '-L';80$options[] = $old_name;81$options[] = '-L';82$options[] = $new_name;8384$normalize = $this->getNormalize();85if ($normalize) {86$old = $this->normalizeFile($old);87$new = $this->normalizeFile($new);88}8990$old_tmp = new TempFile();91$new_tmp = new TempFile();9293Filesystem::writeFile($old_tmp, $old);94Filesystem::writeFile($new_tmp, $new);95list($err, $diff) = exec_manual(96'diff %Ls %s %s',97$options,98$old_tmp,99$new_tmp);100101if (!$err) {102// This indicates that the two files are the same. Build a synthetic,103// changeless diff so that we can still render the raw, unchanged file104// instead of being forced to just say "this file didn't change" since we105// don't have the content.106107$entire_file = explode("\n", $old);108foreach ($entire_file as $k => $line) {109$entire_file[$k] = ' '.$line;110}111112$len = count($entire_file);113$entire_file = implode("\n", $entire_file);114115// TODO: If both files were identical but missing newlines, we probably116// get this wrong. Unclear if it ever matters.117118// This is a bit hacky but the diff parser can handle it.119$diff = "--- {$old_name}\n".120"+++ {$new_name}\n".121"@@ -1,{$len} +1,{$len} @@\n".122$entire_file."\n";123}124125return $diff;126}127128129/**130* Generate an @{class:DifferentialChangeset} from two raw files. This is131* principally useful because you can feed the output to132* @{class:DifferentialChangesetParser} in order to render it.133*134* @param string Entire previous file content.135* @param string Entire current file content.136* @return @{class:DifferentialChangeset} Synthetic changeset.137* @task diff138*/139public function generateChangesetFromFileContent($old, $new) {140$diff = $this->generateRawDiffFromFileContent($old, $new);141142$changes = id(new ArcanistDiffParser())->parseDiff($diff);143$diff = DifferentialDiff::newEphemeralFromRawChanges(144$changes);145return head($diff->getChangesets());146}147148private function normalizeFile($corpus) {149// We can freely apply any other transformations we want to here: we have150// no constraints on what we need to preserve. If we normalize every line151// to "cat", the diff will still work, the alignment of the "-" / "+"152// lines will just be very hard to read.153154// In general, we'll make the diff better if we normalize two lines that155// humans think are the same.156157// We'll make the diff worse if we normalize two lines that humans think158// are different.159160161// Strip all whitespace present anywhere in the diff, since humans never162// consider whitespace changes to alter the line into a "different line"163// even when they're semantic (e.g., in a string constant). This covers164// indentation changes, trailing whitepspace, and formatting changes165// like "+/-".166$corpus = preg_replace('/[ \t]/', '', $corpus);167168return $corpus;169}170171public static function applyIntralineDiff($str, $intra_stack) {172$buf = '';173$p = $s = $e = 0; // position, start, end174$highlight = $tag = $ent = false;175$highlight_o = '<span class="bright">';176$highlight_c = '</span>';177178$depth_in = '<span class="depth-in">';179$depth_out = '<span class="depth-out">';180181$is_html = false;182if ($str instanceof PhutilSafeHTML) {183$is_html = true;184$str = $str->getHTMLContent();185}186187$n = strlen($str);188for ($i = 0; $i < $n; $i++) {189190if ($p == $e) {191do {192if (empty($intra_stack)) {193$buf .= substr($str, $i);194break 2;195}196$stack = array_shift($intra_stack);197$s = $e;198$e += $stack[1];199} while ($stack[0] === 0);200201switch ($stack[0]) {202case '>':203$open_tag = $depth_in;204break;205case '<':206$open_tag = $depth_out;207break;208default:209$open_tag = $highlight_o;210break;211}212}213214if (!$highlight && !$tag && !$ent && $p == $s) {215$buf .= $open_tag;216$highlight = true;217}218219if ($str[$i] == '<') {220$tag = true;221if ($highlight) {222$buf .= $highlight_c;223}224}225226if (!$tag) {227if ($str[$i] == '&') {228$ent = true;229}230if ($ent && $str[$i] == ';') {231$ent = false;232}233if (!$ent) {234$p++;235}236}237238$buf .= $str[$i];239240if ($tag && $str[$i] == '>') {241$tag = false;242if ($highlight) {243$buf .= $open_tag;244}245}246247if ($highlight && ($p == $e || $i == $n - 1)) {248$buf .= $highlight_c;249$highlight = false;250}251}252253if ($is_html) {254return phutil_safe_html($buf);255}256257return $buf;258}259260}261262263