Path: blob/master/src/applications/differential/storage/DifferentialHunk.php
12256 views
<?php12final class DifferentialHunk3extends DifferentialDAO4implements5PhabricatorPolicyInterface,6PhabricatorDestructibleInterface {78protected $changesetID;9protected $oldOffset;10protected $oldLen;11protected $newOffset;12protected $newLen;13protected $dataType;14protected $dataEncoding;15protected $dataFormat;16protected $data;1718private $changeset;19private $splitLines;20private $structuredLines;21private $structuredFiles = array();2223private $rawData;24private $forcedEncoding;25private $fileData;2627const FLAG_LINES_ADDED = 1;28const FLAG_LINES_REMOVED = 2;29const FLAG_LINES_STABLE = 4;3031const DATATYPE_TEXT = 'text';32const DATATYPE_FILE = 'file';3334const DATAFORMAT_RAW = 'byte';35const DATAFORMAT_DEFLATED = 'gzde';3637protected function getConfiguration() {38return array(39self::CONFIG_BINARY => array(40'data' => true,41),42self::CONFIG_COLUMN_SCHEMA => array(43'dataType' => 'bytes4',44'dataEncoding' => 'text16?',45'dataFormat' => 'bytes4',46'oldOffset' => 'uint32',47'oldLen' => 'uint32',48'newOffset' => 'uint32',49'newLen' => 'uint32',50),51self::CONFIG_KEY_SCHEMA => array(52'key_changeset' => array(53'columns' => array('changesetID'),54),55'key_created' => array(56'columns' => array('dateCreated'),57),58),59) + parent::getConfiguration();60}6162public function getAddedLines() {63return $this->makeContent($include = '+');64}6566public function getRemovedLines() {67return $this->makeContent($include = '-');68}6970public function makeNewFile() {71return implode('', $this->makeContent($include = ' +'));72}7374public function makeOldFile() {75return implode('', $this->makeContent($include = ' -'));76}7778public function makeChanges() {79return implode('', $this->makeContent($include = '-+'));80}8182public function getStructuredOldFile() {83return $this->getStructuredFile('-');84}8586public function getStructuredNewFile() {87return $this->getStructuredFile('+');88}8990private function getStructuredFile($kind) {91if ($kind !== '+' && $kind !== '-') {92throw new Exception(93pht(94'Structured file kind should be "+" or "-", got "%s".',95$kind));96}9798if (!isset($this->structuredFiles[$kind])) {99if ($kind == '+') {100$number = $this->newOffset;101} else {102$number = $this->oldOffset;103}104105$lines = $this->getStructuredLines();106107// NOTE: We keep the "\ No newline at end of file" line if it appears108// after a line which is not excluded. For example, if we're constructing109// the "+" side of the diff, we want to ignore this one since it's110// relevant only to the "-" side of the diff:111//112// - x113// \ No newline at end of file114// + x115//116// ...but we want to keep this one:117//118// - x119// + x120// \ No newline at end of file121122$file = array();123$keep = true;124foreach ($lines as $line) {125switch ($line['type']) {126case ' ':127case $kind:128$file[$number++] = $line;129$keep = true;130break;131case '\\':132if ($keep) {133// Strip the actual newline off the line's text.134$text = $file[$number - 1]['text'];135$text = rtrim($text, "\r\n");136$file[$number - 1]['text'] = $text;137138$file[$number++] = $line;139$keep = false;140}141break;142default:143$keep = false;144break;145}146}147148$this->structuredFiles[$kind] = $file;149}150151return $this->structuredFiles[$kind];152}153154public function getSplitLines() {155if ($this->splitLines === null) {156$this->splitLines = phutil_split_lines($this->getChanges());157}158return $this->splitLines;159}160161public function getStructuredLines() {162if ($this->structuredLines === null) {163$lines = $this->getSplitLines();164165$structured = array();166foreach ($lines as $line) {167if (empty($line[0])) {168// TODO: Can we just get rid of this?169continue;170}171172$structured[] = array(173'type' => $line[0],174'text' => substr($line, 1),175);176}177178$this->structuredLines = $structured;179}180181return $this->structuredLines;182}183184185public function getContentWithMask($mask) {186$include = array();187188if (($mask & self::FLAG_LINES_ADDED)) {189$include[] = '+';190}191192if (($mask & self::FLAG_LINES_REMOVED)) {193$include[] = '-';194}195196if (($mask & self::FLAG_LINES_STABLE)) {197$include[] = ' ';198}199200$include = implode('', $include);201202return implode('', $this->makeContent($include));203}204205private function makeContent($include) {206$lines = $this->getSplitLines();207$results = array();208209$include_map = array();210for ($ii = 0; $ii < strlen($include); $ii++) {211$include_map[$include[$ii]] = true;212}213214if (isset($include_map['+'])) {215$n = $this->newOffset;216} else {217$n = $this->oldOffset;218}219220$use_next_newline = false;221foreach ($lines as $line) {222if (!isset($line[0])) {223continue;224}225226if ($line[0] == '\\') {227if ($use_next_newline) {228$results[last_key($results)] = rtrim(end($results), "\n");229}230} else if (empty($include_map[$line[0]])) {231$use_next_newline = false;232} else {233$use_next_newline = true;234$results[$n] = substr($line, 1);235}236237if ($line[0] == ' ' || isset($include_map[$line[0]])) {238$n++;239}240}241242return $results;243}244245public function getChangeset() {246return $this->assertAttached($this->changeset);247}248249public function attachChangeset(DifferentialChangeset $changeset) {250$this->changeset = $changeset;251return $this;252}253254255/* -( Storage )------------------------------------------------------------ */256257258public function setChanges($text) {259$this->rawData = $text;260261$this->dataEncoding = $this->detectEncodingForStorage($text);262$this->dataType = self::DATATYPE_TEXT;263264list($format, $data) = $this->formatDataForStorage($text);265266$this->dataFormat = $format;267$this->data = $data;268269return $this;270}271272public function getChanges() {273return $this->getUTF8StringFromStorage(274$this->getRawData(),275nonempty($this->forcedEncoding, $this->getDataEncoding()));276}277278public function forceEncoding($encoding) {279$this->forcedEncoding = $encoding;280return $this;281}282283private function formatDataForStorage($data) {284$deflated = PhabricatorCaches::maybeDeflateData($data);285if ($deflated !== null) {286return array(self::DATAFORMAT_DEFLATED, $deflated);287}288289return array(self::DATAFORMAT_RAW, $data);290}291292public function getAutomaticDataFormat() {293// If the hunk is already stored deflated, just keep it deflated. This is294// mostly a performance improvement for "bin/differential migrate-hunk" so295// that we don't have to recompress all the stored hunks when looking for296// stray uncompressed hunks.297if ($this->dataFormat === self::DATAFORMAT_DEFLATED) {298return self::DATAFORMAT_DEFLATED;299}300301list($format) = $this->formatDataForStorage($this->getRawData());302303return $format;304}305306public function saveAsText() {307$old_type = $this->getDataType();308$old_data = $this->getData();309310$raw_data = $this->getRawData();311312$this->setDataType(self::DATATYPE_TEXT);313314list($format, $data) = $this->formatDataForStorage($raw_data);315$this->setDataFormat($format);316$this->setData($data);317318$result = $this->save();319320$this->destroyData($old_type, $old_data);321322return $result;323}324325public function saveAsFile() {326$old_type = $this->getDataType();327$old_data = $this->getData();328329$raw_data = $this->getRawData();330331list($format, $data) = $this->formatDataForStorage($raw_data);332$this->setDataFormat($format);333334$file = PhabricatorFile::newFromFileData(335$data,336array(337'name' => 'differential-hunk',338'mime-type' => 'application/octet-stream',339'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,340));341342$this->setDataType(self::DATATYPE_FILE);343$this->setData($file->getPHID());344345// NOTE: Because hunks don't have a PHID and we just load hunk data with346// the omnipotent viewer, we do not need to attach the file to anything.347348$result = $this->save();349350$this->destroyData($old_type, $old_data);351352return $result;353}354355private function getRawData() {356if ($this->rawData === null) {357$type = $this->getDataType();358$data = $this->getData();359360switch ($type) {361case self::DATATYPE_TEXT:362// In this storage type, the changes are stored on the object.363$data = $data;364break;365case self::DATATYPE_FILE:366$data = $this->loadFileData();367break;368default:369throw new Exception(370pht('Hunk has unsupported data type "%s"!', $type));371}372373$format = $this->getDataFormat();374switch ($format) {375case self::DATAFORMAT_RAW:376// In this format, the changes are stored as-is.377$data = $data;378break;379case self::DATAFORMAT_DEFLATED:380$data = PhabricatorCaches::inflateData($data);381break;382default:383throw new Exception(384pht('Hunk has unsupported data encoding "%s"!', $type));385}386387$this->rawData = $data;388}389390return $this->rawData;391}392393private function loadFileData() {394if ($this->fileData === null) {395$type = $this->getDataType();396if ($type !== self::DATATYPE_FILE) {397throw new Exception(398pht(399'Unable to load file data for hunk with wrong data type ("%s").',400$type));401}402403$file_phid = $this->getData();404405$file = $this->loadRawFile($file_phid);406$data = $file->loadFileData();407408$this->fileData = $data;409}410411return $this->fileData;412}413414private function loadRawFile($file_phid) {415$viewer = PhabricatorUser::getOmnipotentUser();416417418$files = id(new PhabricatorFileQuery())419->setViewer($viewer)420->withPHIDs(array($file_phid))421->execute();422if (!$files) {423throw new Exception(424pht(425'Failed to load file ("%s") with hunk data.',426$file_phid));427}428429$file = head($files);430431return $file;432}433434private function destroyData(435$type,436$data,437PhabricatorDestructionEngine $engine = null) {438439if (!$engine) {440$engine = new PhabricatorDestructionEngine();441}442443switch ($type) {444case self::DATATYPE_FILE:445$file = $this->loadRawFile($data);446$engine->destroyObject($file);447break;448}449}450451452/* -( PhabricatorPolicyInterface )----------------------------------------- */453454455public function getCapabilities() {456return array(457PhabricatorPolicyCapability::CAN_VIEW,458);459}460461public function getPolicy($capability) {462return $this->getChangeset()->getPolicy($capability);463}464465public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {466return $this->getChangeset()->hasAutomaticCapability($capability, $viewer);467}468469470/* -( PhabricatorDestructibleInterface )----------------------------------- */471472473public function destroyObjectPermanently(474PhabricatorDestructionEngine $engine) {475476$type = $this->getDataType();477$data = $this->getData();478479$this->destroyData($type, $data, $engine);480481$this->delete();482}483484}485486487