Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/diff/PhabricatorDifferenceEngine.php
12241 views
1
<?php
2
3
/**
4
* Utility class which encapsulates some shared behavior between different
5
* applications which render diffs.
6
*
7
* @task config Configuring the Engine
8
* @task diff Generating Diffs
9
*/
10
final class PhabricatorDifferenceEngine extends Phobject {
11
12
13
private $oldName;
14
private $newName;
15
private $normalize;
16
17
18
/* -( Configuring the Engine )--------------------------------------------- */
19
20
21
/**
22
* Set the name to identify the old file with. Primarily cosmetic.
23
*
24
* @param string Old file name.
25
* @return this
26
* @task config
27
*/
28
public function setOldName($old_name) {
29
$this->oldName = $old_name;
30
return $this;
31
}
32
33
34
/**
35
* Set the name to identify the new file with. Primarily cosmetic.
36
*
37
* @param string New file name.
38
* @return this
39
* @task config
40
*/
41
public function setNewName($new_name) {
42
$this->newName = $new_name;
43
return $this;
44
}
45
46
47
public function setNormalize($normalize) {
48
$this->normalize = $normalize;
49
return $this;
50
}
51
52
public function getNormalize() {
53
return $this->normalize;
54
}
55
56
57
/* -( Generating Diffs )--------------------------------------------------- */
58
59
60
/**
61
* Generate a raw diff from two raw files. This is a lower-level API than
62
* @{method:generateChangesetFromFileContent}, but may be useful if you need
63
* to use a custom parser configuration, as with Diffusion.
64
*
65
* @param string Entire previous file content.
66
* @param string Entire current file content.
67
* @return string Raw diff between the two files.
68
* @task diff
69
*/
70
public function generateRawDiffFromFileContent($old, $new) {
71
72
$options = array();
73
74
// Generate diffs with full context.
75
$options[] = '-U65535';
76
77
$old_name = nonempty($this->oldName, '/dev/universe').' 9999-99-99';
78
$new_name = nonempty($this->newName, '/dev/universe').' 9999-99-99';
79
80
$options[] = '-L';
81
$options[] = $old_name;
82
$options[] = '-L';
83
$options[] = $new_name;
84
85
$normalize = $this->getNormalize();
86
if ($normalize) {
87
$old = $this->normalizeFile($old);
88
$new = $this->normalizeFile($new);
89
}
90
91
$old_tmp = new TempFile();
92
$new_tmp = new TempFile();
93
94
Filesystem::writeFile($old_tmp, $old);
95
Filesystem::writeFile($new_tmp, $new);
96
list($err, $diff) = exec_manual(
97
'diff %Ls %s %s',
98
$options,
99
$old_tmp,
100
$new_tmp);
101
102
if (!$err) {
103
// This indicates that the two files are the same. Build a synthetic,
104
// changeless diff so that we can still render the raw, unchanged file
105
// instead of being forced to just say "this file didn't change" since we
106
// don't have the content.
107
108
$entire_file = explode("\n", $old);
109
foreach ($entire_file as $k => $line) {
110
$entire_file[$k] = ' '.$line;
111
}
112
113
$len = count($entire_file);
114
$entire_file = implode("\n", $entire_file);
115
116
// TODO: If both files were identical but missing newlines, we probably
117
// get this wrong. Unclear if it ever matters.
118
119
// This is a bit hacky but the diff parser can handle it.
120
$diff = "--- {$old_name}\n".
121
"+++ {$new_name}\n".
122
"@@ -1,{$len} +1,{$len} @@\n".
123
$entire_file."\n";
124
}
125
126
return $diff;
127
}
128
129
130
/**
131
* Generate an @{class:DifferentialChangeset} from two raw files. This is
132
* principally useful because you can feed the output to
133
* @{class:DifferentialChangesetParser} in order to render it.
134
*
135
* @param string Entire previous file content.
136
* @param string Entire current file content.
137
* @return @{class:DifferentialChangeset} Synthetic changeset.
138
* @task diff
139
*/
140
public function generateChangesetFromFileContent($old, $new) {
141
$diff = $this->generateRawDiffFromFileContent($old, $new);
142
143
$changes = id(new ArcanistDiffParser())->parseDiff($diff);
144
$diff = DifferentialDiff::newEphemeralFromRawChanges(
145
$changes);
146
return head($diff->getChangesets());
147
}
148
149
private function normalizeFile($corpus) {
150
// We can freely apply any other transformations we want to here: we have
151
// no constraints on what we need to preserve. If we normalize every line
152
// to "cat", the diff will still work, the alignment of the "-" / "+"
153
// lines will just be very hard to read.
154
155
// In general, we'll make the diff better if we normalize two lines that
156
// humans think are the same.
157
158
// We'll make the diff worse if we normalize two lines that humans think
159
// are different.
160
161
162
// Strip all whitespace present anywhere in the diff, since humans never
163
// consider whitespace changes to alter the line into a "different line"
164
// even when they're semantic (e.g., in a string constant). This covers
165
// indentation changes, trailing whitepspace, and formatting changes
166
// like "+/-".
167
$corpus = preg_replace('/[ \t]/', '', $corpus);
168
169
return $corpus;
170
}
171
172
public static function applyIntralineDiff($str, $intra_stack) {
173
$buf = '';
174
$p = $s = $e = 0; // position, start, end
175
$highlight = $tag = $ent = false;
176
$highlight_o = '<span class="bright">';
177
$highlight_c = '</span>';
178
179
$depth_in = '<span class="depth-in">';
180
$depth_out = '<span class="depth-out">';
181
182
$is_html = false;
183
if ($str instanceof PhutilSafeHTML) {
184
$is_html = true;
185
$str = $str->getHTMLContent();
186
}
187
188
$n = strlen($str);
189
for ($i = 0; $i < $n; $i++) {
190
191
if ($p == $e) {
192
do {
193
if (empty($intra_stack)) {
194
$buf .= substr($str, $i);
195
break 2;
196
}
197
$stack = array_shift($intra_stack);
198
$s = $e;
199
$e += $stack[1];
200
} while ($stack[0] === 0);
201
202
switch ($stack[0]) {
203
case '>':
204
$open_tag = $depth_in;
205
break;
206
case '<':
207
$open_tag = $depth_out;
208
break;
209
default:
210
$open_tag = $highlight_o;
211
break;
212
}
213
}
214
215
if (!$highlight && !$tag && !$ent && $p == $s) {
216
$buf .= $open_tag;
217
$highlight = true;
218
}
219
220
if ($str[$i] == '<') {
221
$tag = true;
222
if ($highlight) {
223
$buf .= $highlight_c;
224
}
225
}
226
227
if (!$tag) {
228
if ($str[$i] == '&') {
229
$ent = true;
230
}
231
if ($ent && $str[$i] == ';') {
232
$ent = false;
233
}
234
if (!$ent) {
235
$p++;
236
}
237
}
238
239
$buf .= $str[$i];
240
241
if ($tag && $str[$i] == '>') {
242
$tag = false;
243
if ($highlight) {
244
$buf .= $open_tag;
245
}
246
}
247
248
if ($highlight && ($p == $e || $i == $n - 1)) {
249
$buf .= $highlight_c;
250
$highlight = false;
251
}
252
}
253
254
if ($is_html) {
255
return phutil_safe_html($buf);
256
}
257
258
return $buf;
259
}
260
261
}
262
263