Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/differential/storage/DifferentialHunk.php
12256 views
1
<?php
2
3
final class DifferentialHunk
4
extends DifferentialDAO
5
implements
6
PhabricatorPolicyInterface,
7
PhabricatorDestructibleInterface {
8
9
protected $changesetID;
10
protected $oldOffset;
11
protected $oldLen;
12
protected $newOffset;
13
protected $newLen;
14
protected $dataType;
15
protected $dataEncoding;
16
protected $dataFormat;
17
protected $data;
18
19
private $changeset;
20
private $splitLines;
21
private $structuredLines;
22
private $structuredFiles = array();
23
24
private $rawData;
25
private $forcedEncoding;
26
private $fileData;
27
28
const FLAG_LINES_ADDED = 1;
29
const FLAG_LINES_REMOVED = 2;
30
const FLAG_LINES_STABLE = 4;
31
32
const DATATYPE_TEXT = 'text';
33
const DATATYPE_FILE = 'file';
34
35
const DATAFORMAT_RAW = 'byte';
36
const DATAFORMAT_DEFLATED = 'gzde';
37
38
protected function getConfiguration() {
39
return array(
40
self::CONFIG_BINARY => array(
41
'data' => true,
42
),
43
self::CONFIG_COLUMN_SCHEMA => array(
44
'dataType' => 'bytes4',
45
'dataEncoding' => 'text16?',
46
'dataFormat' => 'bytes4',
47
'oldOffset' => 'uint32',
48
'oldLen' => 'uint32',
49
'newOffset' => 'uint32',
50
'newLen' => 'uint32',
51
),
52
self::CONFIG_KEY_SCHEMA => array(
53
'key_changeset' => array(
54
'columns' => array('changesetID'),
55
),
56
'key_created' => array(
57
'columns' => array('dateCreated'),
58
),
59
),
60
) + parent::getConfiguration();
61
}
62
63
public function getAddedLines() {
64
return $this->makeContent($include = '+');
65
}
66
67
public function getRemovedLines() {
68
return $this->makeContent($include = '-');
69
}
70
71
public function makeNewFile() {
72
return implode('', $this->makeContent($include = ' +'));
73
}
74
75
public function makeOldFile() {
76
return implode('', $this->makeContent($include = ' -'));
77
}
78
79
public function makeChanges() {
80
return implode('', $this->makeContent($include = '-+'));
81
}
82
83
public function getStructuredOldFile() {
84
return $this->getStructuredFile('-');
85
}
86
87
public function getStructuredNewFile() {
88
return $this->getStructuredFile('+');
89
}
90
91
private function getStructuredFile($kind) {
92
if ($kind !== '+' && $kind !== '-') {
93
throw new Exception(
94
pht(
95
'Structured file kind should be "+" or "-", got "%s".',
96
$kind));
97
}
98
99
if (!isset($this->structuredFiles[$kind])) {
100
if ($kind == '+') {
101
$number = $this->newOffset;
102
} else {
103
$number = $this->oldOffset;
104
}
105
106
$lines = $this->getStructuredLines();
107
108
// NOTE: We keep the "\ No newline at end of file" line if it appears
109
// after a line which is not excluded. For example, if we're constructing
110
// the "+" side of the diff, we want to ignore this one since it's
111
// relevant only to the "-" side of the diff:
112
//
113
// - x
114
// \ No newline at end of file
115
// + x
116
//
117
// ...but we want to keep this one:
118
//
119
// - x
120
// + x
121
// \ No newline at end of file
122
123
$file = array();
124
$keep = true;
125
foreach ($lines as $line) {
126
switch ($line['type']) {
127
case ' ':
128
case $kind:
129
$file[$number++] = $line;
130
$keep = true;
131
break;
132
case '\\':
133
if ($keep) {
134
// Strip the actual newline off the line's text.
135
$text = $file[$number - 1]['text'];
136
$text = rtrim($text, "\r\n");
137
$file[$number - 1]['text'] = $text;
138
139
$file[$number++] = $line;
140
$keep = false;
141
}
142
break;
143
default:
144
$keep = false;
145
break;
146
}
147
}
148
149
$this->structuredFiles[$kind] = $file;
150
}
151
152
return $this->structuredFiles[$kind];
153
}
154
155
public function getSplitLines() {
156
if ($this->splitLines === null) {
157
$this->splitLines = phutil_split_lines($this->getChanges());
158
}
159
return $this->splitLines;
160
}
161
162
public function getStructuredLines() {
163
if ($this->structuredLines === null) {
164
$lines = $this->getSplitLines();
165
166
$structured = array();
167
foreach ($lines as $line) {
168
if (empty($line[0])) {
169
// TODO: Can we just get rid of this?
170
continue;
171
}
172
173
$structured[] = array(
174
'type' => $line[0],
175
'text' => substr($line, 1),
176
);
177
}
178
179
$this->structuredLines = $structured;
180
}
181
182
return $this->structuredLines;
183
}
184
185
186
public function getContentWithMask($mask) {
187
$include = array();
188
189
if (($mask & self::FLAG_LINES_ADDED)) {
190
$include[] = '+';
191
}
192
193
if (($mask & self::FLAG_LINES_REMOVED)) {
194
$include[] = '-';
195
}
196
197
if (($mask & self::FLAG_LINES_STABLE)) {
198
$include[] = ' ';
199
}
200
201
$include = implode('', $include);
202
203
return implode('', $this->makeContent($include));
204
}
205
206
private function makeContent($include) {
207
$lines = $this->getSplitLines();
208
$results = array();
209
210
$include_map = array();
211
for ($ii = 0; $ii < strlen($include); $ii++) {
212
$include_map[$include[$ii]] = true;
213
}
214
215
if (isset($include_map['+'])) {
216
$n = $this->newOffset;
217
} else {
218
$n = $this->oldOffset;
219
}
220
221
$use_next_newline = false;
222
foreach ($lines as $line) {
223
if (!isset($line[0])) {
224
continue;
225
}
226
227
if ($line[0] == '\\') {
228
if ($use_next_newline) {
229
$results[last_key($results)] = rtrim(end($results), "\n");
230
}
231
} else if (empty($include_map[$line[0]])) {
232
$use_next_newline = false;
233
} else {
234
$use_next_newline = true;
235
$results[$n] = substr($line, 1);
236
}
237
238
if ($line[0] == ' ' || isset($include_map[$line[0]])) {
239
$n++;
240
}
241
}
242
243
return $results;
244
}
245
246
public function getChangeset() {
247
return $this->assertAttached($this->changeset);
248
}
249
250
public function attachChangeset(DifferentialChangeset $changeset) {
251
$this->changeset = $changeset;
252
return $this;
253
}
254
255
256
/* -( Storage )------------------------------------------------------------ */
257
258
259
public function setChanges($text) {
260
$this->rawData = $text;
261
262
$this->dataEncoding = $this->detectEncodingForStorage($text);
263
$this->dataType = self::DATATYPE_TEXT;
264
265
list($format, $data) = $this->formatDataForStorage($text);
266
267
$this->dataFormat = $format;
268
$this->data = $data;
269
270
return $this;
271
}
272
273
public function getChanges() {
274
return $this->getUTF8StringFromStorage(
275
$this->getRawData(),
276
nonempty($this->forcedEncoding, $this->getDataEncoding()));
277
}
278
279
public function forceEncoding($encoding) {
280
$this->forcedEncoding = $encoding;
281
return $this;
282
}
283
284
private function formatDataForStorage($data) {
285
$deflated = PhabricatorCaches::maybeDeflateData($data);
286
if ($deflated !== null) {
287
return array(self::DATAFORMAT_DEFLATED, $deflated);
288
}
289
290
return array(self::DATAFORMAT_RAW, $data);
291
}
292
293
public function getAutomaticDataFormat() {
294
// If the hunk is already stored deflated, just keep it deflated. This is
295
// mostly a performance improvement for "bin/differential migrate-hunk" so
296
// that we don't have to recompress all the stored hunks when looking for
297
// stray uncompressed hunks.
298
if ($this->dataFormat === self::DATAFORMAT_DEFLATED) {
299
return self::DATAFORMAT_DEFLATED;
300
}
301
302
list($format) = $this->formatDataForStorage($this->getRawData());
303
304
return $format;
305
}
306
307
public function saveAsText() {
308
$old_type = $this->getDataType();
309
$old_data = $this->getData();
310
311
$raw_data = $this->getRawData();
312
313
$this->setDataType(self::DATATYPE_TEXT);
314
315
list($format, $data) = $this->formatDataForStorage($raw_data);
316
$this->setDataFormat($format);
317
$this->setData($data);
318
319
$result = $this->save();
320
321
$this->destroyData($old_type, $old_data);
322
323
return $result;
324
}
325
326
public function saveAsFile() {
327
$old_type = $this->getDataType();
328
$old_data = $this->getData();
329
330
$raw_data = $this->getRawData();
331
332
list($format, $data) = $this->formatDataForStorage($raw_data);
333
$this->setDataFormat($format);
334
335
$file = PhabricatorFile::newFromFileData(
336
$data,
337
array(
338
'name' => 'differential-hunk',
339
'mime-type' => 'application/octet-stream',
340
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
341
));
342
343
$this->setDataType(self::DATATYPE_FILE);
344
$this->setData($file->getPHID());
345
346
// NOTE: Because hunks don't have a PHID and we just load hunk data with
347
// the omnipotent viewer, we do not need to attach the file to anything.
348
349
$result = $this->save();
350
351
$this->destroyData($old_type, $old_data);
352
353
return $result;
354
}
355
356
private function getRawData() {
357
if ($this->rawData === null) {
358
$type = $this->getDataType();
359
$data = $this->getData();
360
361
switch ($type) {
362
case self::DATATYPE_TEXT:
363
// In this storage type, the changes are stored on the object.
364
$data = $data;
365
break;
366
case self::DATATYPE_FILE:
367
$data = $this->loadFileData();
368
break;
369
default:
370
throw new Exception(
371
pht('Hunk has unsupported data type "%s"!', $type));
372
}
373
374
$format = $this->getDataFormat();
375
switch ($format) {
376
case self::DATAFORMAT_RAW:
377
// In this format, the changes are stored as-is.
378
$data = $data;
379
break;
380
case self::DATAFORMAT_DEFLATED:
381
$data = PhabricatorCaches::inflateData($data);
382
break;
383
default:
384
throw new Exception(
385
pht('Hunk has unsupported data encoding "%s"!', $type));
386
}
387
388
$this->rawData = $data;
389
}
390
391
return $this->rawData;
392
}
393
394
private function loadFileData() {
395
if ($this->fileData === null) {
396
$type = $this->getDataType();
397
if ($type !== self::DATATYPE_FILE) {
398
throw new Exception(
399
pht(
400
'Unable to load file data for hunk with wrong data type ("%s").',
401
$type));
402
}
403
404
$file_phid = $this->getData();
405
406
$file = $this->loadRawFile($file_phid);
407
$data = $file->loadFileData();
408
409
$this->fileData = $data;
410
}
411
412
return $this->fileData;
413
}
414
415
private function loadRawFile($file_phid) {
416
$viewer = PhabricatorUser::getOmnipotentUser();
417
418
419
$files = id(new PhabricatorFileQuery())
420
->setViewer($viewer)
421
->withPHIDs(array($file_phid))
422
->execute();
423
if (!$files) {
424
throw new Exception(
425
pht(
426
'Failed to load file ("%s") with hunk data.',
427
$file_phid));
428
}
429
430
$file = head($files);
431
432
return $file;
433
}
434
435
private function destroyData(
436
$type,
437
$data,
438
PhabricatorDestructionEngine $engine = null) {
439
440
if (!$engine) {
441
$engine = new PhabricatorDestructionEngine();
442
}
443
444
switch ($type) {
445
case self::DATATYPE_FILE:
446
$file = $this->loadRawFile($data);
447
$engine->destroyObject($file);
448
break;
449
}
450
}
451
452
453
/* -( PhabricatorPolicyInterface )----------------------------------------- */
454
455
456
public function getCapabilities() {
457
return array(
458
PhabricatorPolicyCapability::CAN_VIEW,
459
);
460
}
461
462
public function getPolicy($capability) {
463
return $this->getChangeset()->getPolicy($capability);
464
}
465
466
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
467
return $this->getChangeset()->hasAutomaticCapability($capability, $viewer);
468
}
469
470
471
/* -( PhabricatorDestructibleInterface )----------------------------------- */
472
473
474
public function destroyObjectPermanently(
475
PhabricatorDestructionEngine $engine) {
476
477
$type = $this->getDataType();
478
$data = $this->getData();
479
480
$this->destroyData($type, $data, $engine);
481
482
$this->delete();
483
}
484
485
}
486
487