@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 483 lines 12 kB view raw
1<?php 2 3final 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 && $results) { 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 break; 365 case self::DATATYPE_FILE: 366 $data = $this->loadFileData(); 367 break; 368 default: 369 throw new Exception( 370 pht('Hunk has unsupported data type "%s"!', $type)); 371 } 372 373 $format = $this->getDataFormat(); 374 switch ($format) { 375 case self::DATAFORMAT_RAW: 376 // In this format, the changes are stored as-is. 377 break; 378 case self::DATAFORMAT_DEFLATED: 379 $data = PhabricatorCaches::inflateData($data); 380 break; 381 default: 382 throw new Exception( 383 pht('Hunk has unsupported data encoding "%s"!', $type)); 384 } 385 386 $this->rawData = $data; 387 } 388 389 return $this->rawData; 390 } 391 392 private function loadFileData() { 393 if ($this->fileData === null) { 394 $type = $this->getDataType(); 395 if ($type !== self::DATATYPE_FILE) { 396 throw new Exception( 397 pht( 398 'Unable to load file data for hunk with wrong data type ("%s").', 399 $type)); 400 } 401 402 $file_phid = $this->getData(); 403 404 $file = $this->loadRawFile($file_phid); 405 $data = $file->loadFileData(); 406 407 $this->fileData = $data; 408 } 409 410 return $this->fileData; 411 } 412 413 private function loadRawFile($file_phid) { 414 $viewer = PhabricatorUser::getOmnipotentUser(); 415 416 417 $files = id(new PhabricatorFileQuery()) 418 ->setViewer($viewer) 419 ->withPHIDs(array($file_phid)) 420 ->execute(); 421 if (!$files) { 422 throw new Exception( 423 pht( 424 'Failed to load file ("%s") with hunk data.', 425 $file_phid)); 426 } 427 428 $file = head($files); 429 430 return $file; 431 } 432 433 private function destroyData( 434 $type, 435 $data, 436 ?PhabricatorDestructionEngine $engine = null) { 437 438 if (!$engine) { 439 $engine = new PhabricatorDestructionEngine(); 440 } 441 442 switch ($type) { 443 case self::DATATYPE_FILE: 444 $file = $this->loadRawFile($data); 445 $engine->destroyObject($file); 446 break; 447 } 448 } 449 450 451/* -( PhabricatorPolicyInterface )----------------------------------------- */ 452 453 454 public function getCapabilities() { 455 return array( 456 PhabricatorPolicyCapability::CAN_VIEW, 457 ); 458 } 459 460 public function getPolicy($capability) { 461 return $this->getChangeset()->getPolicy($capability); 462 } 463 464 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 465 return $this->getChangeset()->hasAutomaticCapability($capability, $viewer); 466 } 467 468 469/* -( PhabricatorDestructibleInterface )----------------------------------- */ 470 471 472 public function destroyObjectPermanently( 473 PhabricatorDestructionEngine $engine) { 474 475 $type = $this->getDataType(); 476 $data = $this->getData(); 477 478 $this->destroyData($type, $data, $engine); 479 480 $this->delete(); 481 } 482 483}