Remove product literal strings in "pht()", part 6
[phabricator.git] / src / applications / differential / storage / DifferentialHunk.php
blobf7decf69ae1f087f712c99ca7923d7aa97c7ba36
1 <?php
3 final class DifferentialHunk
4 extends DifferentialDAO
5 implements
6 PhabricatorPolicyInterface,
7 PhabricatorDestructibleInterface {
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;
19 private $changeset;
20 private $splitLines;
21 private $structuredLines;
22 private $structuredFiles = array();
24 private $rawData;
25 private $forcedEncoding;
26 private $fileData;
28 const FLAG_LINES_ADDED = 1;
29 const FLAG_LINES_REMOVED = 2;
30 const FLAG_LINES_STABLE = 4;
32 const DATATYPE_TEXT = 'text';
33 const DATATYPE_FILE = 'file';
35 const DATAFORMAT_RAW = 'byte';
36 const DATAFORMAT_DEFLATED = 'gzde';
38 protected function getConfiguration() {
39 return array(
40 self::CONFIG_BINARY => array(
41 'data' => true,
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',
52 self::CONFIG_KEY_SCHEMA => array(
53 'key_changeset' => array(
54 'columns' => array('changesetID'),
56 'key_created' => array(
57 'columns' => array('dateCreated'),
60 ) + parent::getConfiguration();
63 public function getAddedLines() {
64 return $this->makeContent($include = '+');
67 public function getRemovedLines() {
68 return $this->makeContent($include = '-');
71 public function makeNewFile() {
72 return implode('', $this->makeContent($include = ' +'));
75 public function makeOldFile() {
76 return implode('', $this->makeContent($include = ' -'));
79 public function makeChanges() {
80 return implode('', $this->makeContent($include = '-+'));
83 public function getStructuredOldFile() {
84 return $this->getStructuredFile('-');
87 public function getStructuredNewFile() {
88 return $this->getStructuredFile('+');
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));
99 if (!isset($this->structuredFiles[$kind])) {
100 if ($kind == '+') {
101 $number = $this->newOffset;
102 } else {
103 $number = $this->oldOffset;
106 $lines = $this->getStructuredLines();
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:
113 // - x
114 // \ No newline at end of file
115 // + x
117 // ...but we want to keep this one:
119 // - x
120 // + x
121 // \ No newline at end of file
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;
139 $file[$number++] = $line;
140 $keep = false;
142 break;
143 default:
144 $keep = false;
145 break;
149 $this->structuredFiles[$kind] = $file;
152 return $this->structuredFiles[$kind];
155 public function getSplitLines() {
156 if ($this->splitLines === null) {
157 $this->splitLines = phutil_split_lines($this->getChanges());
159 return $this->splitLines;
162 public function getStructuredLines() {
163 if ($this->structuredLines === null) {
164 $lines = $this->getSplitLines();
166 $structured = array();
167 foreach ($lines as $line) {
168 if (empty($line[0])) {
169 // TODO: Can we just get rid of this?
170 continue;
173 $structured[] = array(
174 'type' => $line[0],
175 'text' => substr($line, 1),
179 $this->structuredLines = $structured;
182 return $this->structuredLines;
186 public function getContentWithMask($mask) {
187 $include = array();
189 if (($mask & self::FLAG_LINES_ADDED)) {
190 $include[] = '+';
193 if (($mask & self::FLAG_LINES_REMOVED)) {
194 $include[] = '-';
197 if (($mask & self::FLAG_LINES_STABLE)) {
198 $include[] = ' ';
201 $include = implode('', $include);
203 return implode('', $this->makeContent($include));
206 private function makeContent($include) {
207 $lines = $this->getSplitLines();
208 $results = array();
210 $include_map = array();
211 for ($ii = 0; $ii < strlen($include); $ii++) {
212 $include_map[$include[$ii]] = true;
215 if (isset($include_map['+'])) {
216 $n = $this->newOffset;
217 } else {
218 $n = $this->oldOffset;
221 $use_next_newline = false;
222 foreach ($lines as $line) {
223 if (!isset($line[0])) {
224 continue;
227 if ($line[0] == '\\') {
228 if ($use_next_newline) {
229 $results[last_key($results)] = rtrim(end($results), "\n");
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);
238 if ($line[0] == ' ' || isset($include_map[$line[0]])) {
239 $n++;
243 return $results;
246 public function getChangeset() {
247 return $this->assertAttached($this->changeset);
250 public function attachChangeset(DifferentialChangeset $changeset) {
251 $this->changeset = $changeset;
252 return $this;
256 /* -( Storage )------------------------------------------------------------ */
259 public function setChanges($text) {
260 $this->rawData = $text;
262 $this->dataEncoding = $this->detectEncodingForStorage($text);
263 $this->dataType = self::DATATYPE_TEXT;
265 list($format, $data) = $this->formatDataForStorage($text);
267 $this->dataFormat = $format;
268 $this->data = $data;
270 return $this;
273 public function getChanges() {
274 return $this->getUTF8StringFromStorage(
275 $this->getRawData(),
276 nonempty($this->forcedEncoding, $this->getDataEncoding()));
279 public function forceEncoding($encoding) {
280 $this->forcedEncoding = $encoding;
281 return $this;
284 private function formatDataForStorage($data) {
285 $deflated = PhabricatorCaches::maybeDeflateData($data);
286 if ($deflated !== null) {
287 return array(self::DATAFORMAT_DEFLATED, $deflated);
290 return array(self::DATAFORMAT_RAW, $data);
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;
302 list($format) = $this->formatDataForStorage($this->getRawData());
304 return $format;
307 public function saveAsText() {
308 $old_type = $this->getDataType();
309 $old_data = $this->getData();
311 $raw_data = $this->getRawData();
313 $this->setDataType(self::DATATYPE_TEXT);
315 list($format, $data) = $this->formatDataForStorage($raw_data);
316 $this->setDataFormat($format);
317 $this->setData($data);
319 $result = $this->save();
321 $this->destroyData($old_type, $old_data);
323 return $result;
326 public function saveAsFile() {
327 $old_type = $this->getDataType();
328 $old_data = $this->getData();
330 $raw_data = $this->getRawData();
332 list($format, $data) = $this->formatDataForStorage($raw_data);
333 $this->setDataFormat($format);
335 $file = PhabricatorFile::newFromFileData(
336 $data,
337 array(
338 'name' => 'differential-hunk',
339 'mime-type' => 'application/octet-stream',
340 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
343 $this->setDataType(self::DATATYPE_FILE);
344 $this->setData($file->getPHID());
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.
349 $result = $this->save();
351 $this->destroyData($old_type, $old_data);
353 return $result;
356 private function getRawData() {
357 if ($this->rawData === null) {
358 $type = $this->getDataType();
359 $data = $this->getData();
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));
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));
388 $this->rawData = $data;
391 return $this->rawData;
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));
404 $file_phid = $this->getData();
406 $file = $this->loadRawFile($file_phid);
407 $data = $file->loadFileData();
409 $this->fileData = $data;
412 return $this->fileData;
415 private function loadRawFile($file_phid) {
416 $viewer = PhabricatorUser::getOmnipotentUser();
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));
430 $file = head($files);
432 return $file;
435 private function destroyData(
436 $type,
437 $data,
438 PhabricatorDestructionEngine $engine = null) {
440 if (!$engine) {
441 $engine = new PhabricatorDestructionEngine();
444 switch ($type) {
445 case self::DATATYPE_FILE:
446 $file = $this->loadRawFile($data);
447 $engine->destroyObject($file);
448 break;
453 /* -( PhabricatorPolicyInterface )----------------------------------------- */
456 public function getCapabilities() {
457 return array(
458 PhabricatorPolicyCapability::CAN_VIEW,
462 public function getPolicy($capability) {
463 return $this->getChangeset()->getPolicy($capability);
466 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
467 return $this->getChangeset()->hasAutomaticCapability($capability, $viewer);
471 /* -( PhabricatorDestructibleInterface )----------------------------------- */
474 public function destroyObjectPermanently(
475 PhabricatorDestructionEngine $engine) {
477 $type = $this->getDataType();
478 $data = $this->getData();
480 $this->destroyData($type, $data, $engine);
482 $this->delete();