1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 11:30:55 +01:00

Completely remove the legacy hunk table

Summary: Depends on D19056. Fixes T8475. Ref T13054. Merges "ModernHunk" back into "Hunk".

Test Plan: Grepped for `modernhunk`. Reviewed revisions. Created a new revision. Used `bin/differential migrate-hunk` to migrate hunks between storage formats and back.

Maniphest Tasks: T13054, T8475

Differential Revision: https://secure.phabricator.com/D19057
This commit is contained in:
epriestley 2018-02-10 12:04:42 -08:00
parent b0d1d46a73
commit f43d08c2bb
13 changed files with 267 additions and 296 deletions

View file

@ -25,9 +25,9 @@ foreach (new LiskRawMigrationIterator($conn, $src_table) as $row) {
$row['oldLen'], $row['oldLen'],
$row['newOffset'], $row['newOffset'],
$row['newLen'], $row['newLen'],
DifferentialModernHunk::DATATYPE_TEXT, DifferentialHunk::DATATYPE_TEXT,
'utf8', 'utf8',
DifferentialModernHunk::DATAFORMAT_RAW, DifferentialHunk::DATAFORMAT_RAW,
// In rare cases, this could be NULL. See T12090. // In rare cases, this could be NULL. See T12090.
(string)$row['changes'], (string)$row['changes'],
$row['dateCreated'], $row['dateCreated'],

View file

@ -0,0 +1,2 @@
RENAME TABLE {$NAMESPACE}_differential.differential_hunk_modern
TO {$NAMESPACE}_differential.differential_hunk;

View file

@ -489,7 +489,6 @@ phutil_register_library_map(array(
'DifferentialMailEngineExtension' => 'applications/differential/engineextension/DifferentialMailEngineExtension.php', 'DifferentialMailEngineExtension' => 'applications/differential/engineextension/DifferentialMailEngineExtension.php',
'DifferentialMailView' => 'applications/differential/mail/DifferentialMailView.php', 'DifferentialMailView' => 'applications/differential/mail/DifferentialMailView.php',
'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php', 'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php',
'DifferentialModernHunk' => 'applications/differential/storage/DifferentialModernHunk.php',
'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php', 'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php',
'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php', 'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php',
'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php', 'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php',
@ -5651,7 +5650,6 @@ phutil_register_library_map(array(
'DifferentialMailEngineExtension' => 'PhabricatorMailEngineExtension', 'DifferentialMailEngineExtension' => 'PhabricatorMailEngineExtension',
'DifferentialMailView' => 'Phobject', 'DifferentialMailView' => 'Phobject',
'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField', 'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
'DifferentialModernHunk' => 'DifferentialHunk',
'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector', 'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector',
'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialParseRenderTestCase' => 'PhabricatorTestCase', 'DifferentialParseRenderTestCase' => 'PhabricatorTestCase',

View file

@ -217,8 +217,8 @@ final class DifferentialDiffExtractionEngine extends Phobject {
// -echo "test"; // -echo "test";
// -(empty line) // -(empty line)
$hunk = id(new DifferentialModernHunk())->setChanges($context); $hunk = id(new DifferentialHunk())->setChanges($context);
$vs_hunk = id(new DifferentialModernHunk())->setChanges($vs_context); $vs_hunk = id(new DifferentialHunk())->setChanges($vs_context);
if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() || if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() ||
$hunk->makeNewFile() != $vs_hunk->makeNewFile()) { $hunk->makeNewFile() != $vs_hunk->makeNewFile()) {
return true; return true;

View file

@ -32,8 +32,8 @@ final class PhabricatorDifferentialMigrateHunkWorkflow
$storage = $args->getArg('to'); $storage = $args->getArg('to');
switch ($storage) { switch ($storage) {
case DifferentialModernHunk::DATATYPE_TEXT: case DifferentialHunk::DATATYPE_TEXT:
case DifferentialModernHunk::DATATYPE_FILE: case DifferentialHunk::DATATYPE_FILE:
break; break;
default: default:
throw new PhutilArgumentUsageException( throw new PhutilArgumentUsageException(
@ -44,13 +44,13 @@ final class PhabricatorDifferentialMigrateHunkWorkflow
$old_data = $hunk->getChanges(); $old_data = $hunk->getChanges();
switch ($storage) { switch ($storage) {
case DifferentialModernHunk::DATATYPE_TEXT: case DifferentialHunk::DATATYPE_TEXT:
$hunk->saveAsText(); $hunk->saveAsText();
$this->logOkay( $this->logOkay(
pht('TEXT'), pht('TEXT'),
pht('Convereted hunk to text storage.')); pht('Convereted hunk to text storage.'));
break; break;
case DifferentialModernHunk::DATATYPE_FILE: case DifferentialHunk::DATATYPE_FILE:
$hunk->saveAsFile(); $hunk->saveAsFile();
$this->logOkay( $this->logOkay(
pht('FILE'), pht('FILE'),
@ -71,7 +71,7 @@ final class PhabricatorDifferentialMigrateHunkWorkflow
} }
private function loadHunk($id) { private function loadHunk($id) {
$hunk = id(new DifferentialModernHunk())->load($id); $hunk = id(new DifferentialHunk())->load($id);
if (!$hunk) { if (!$hunk) {
throw new PhutilArgumentUsageException( throw new PhutilArgumentUsageException(
pht( pht(

View file

@ -3,7 +3,7 @@
final class DifferentialChangesetParserTestCase extends PhabricatorTestCase { final class DifferentialChangesetParserTestCase extends PhabricatorTestCase {
public function testDiffChangesets() { public function testDiffChangesets() {
$hunk = new DifferentialModernHunk(); $hunk = new DifferentialHunk();
$hunk->setChanges("+a\n b\n-c"); $hunk->setChanges("+a\n b\n-c");
$hunk->setNewOffset(1); $hunk->setNewOffset(1);
$hunk->setNewLen(2); $hunk->setNewLen(2);
@ -20,7 +20,7 @@ final class DifferentialChangesetParserTestCase extends PhabricatorTestCase {
); );
foreach ($tests as $changes => $expected) { foreach ($tests as $changes => $expected) {
$hunk = new DifferentialModernHunk(); $hunk = new DifferentialHunk();
$hunk->setChanges($changes); $hunk->setChanges($changes);
$hunk->setNewOffset(11); $hunk->setNewOffset(11);
$hunk->setNewLen(3); $hunk->setNewLen(3);

View file

@ -14,7 +14,7 @@ final class DifferentialHunkParserTestCase extends PhabricatorTestCase {
$new_len, $new_len,
$changes) { $changes) {
$hunk = id(new DifferentialModernHunk()) $hunk = id(new DifferentialHunk())
->setOldOffset($old_offset) ->setOldOffset($old_offset)
->setOldLen($old_len) ->setOldLen($old_len)
->setNewOffset($new_offset) ->setNewOffset($new_offset)

View file

@ -30,25 +30,12 @@ final class DifferentialHunkQuery
} }
} }
public function newResultObject() {
return new DifferentialHunk();
}
protected function loadPage() { protected function loadPage() {
$all_results = array(); return $this->loadStandardPage($this->newResultObject());
// Load modern hunks.
$table = new DifferentialModernHunk();
$conn_r = $table->establishConnection('r');
$modern_data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
$modern_results = $table->loadAllFromArray($modern_data);
// Strip all the IDs off since they're not unique and nothing should be
// using them.
return array_values($modern_results);
} }
protected function willFilterPage(array $hunks) { protected function willFilterPage(array $hunks) {
@ -76,8 +63,8 @@ final class DifferentialHunkQuery
return $hunks; return $hunks;
} }
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = array(); $where = parent::buildWhereClauseParts($conn);
if (!$this->changesets) { if (!$this->changesets) {
throw new Exception( throw new Exception(
@ -87,13 +74,11 @@ final class DifferentialHunkQuery
} }
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'changesetID IN (%Ld)', 'changesetID IN (%Ld)',
mpull($this->changesets, 'getID')); mpull($this->changesets, 'getID'));
$where[] = $this->buildPagingClause($conn_r); return $where;
return $this->formatWhereClause($where);
} }
public function getQueryApplicationClass() { public function getQueryApplicationClass() {

View file

@ -118,11 +118,11 @@ final class DifferentialChangeset
public function delete() { public function delete() {
$this->openTransaction(); $this->openTransaction();
$modern_hunks = id(new DifferentialModernHunk())->loadAllWhere( $hunks = id(new DifferentialHunk())->loadAllWhere(
'changesetID = %d', 'changesetID = %d',
$this->getID()); $this->getID());
foreach ($modern_hunks as $modern_hunk) { foreach ($hunks as $hunk) {
$modern_hunk->delete(); $hunk->delete();
} }
$this->unsavedHunks = array(); $this->unsavedHunks = array();
@ -292,7 +292,7 @@ final class DifferentialChangeset
PhabricatorDestructionEngine $engine) { PhabricatorDestructionEngine $engine) {
$this->openTransaction(); $this->openTransaction();
$hunks = id(new DifferentialModernHunk())->loadAllWhere( $hunks = id(new DifferentialHunk())->loadAllWhere(
'changesetID = %d', 'changesetID = %d',
$this->getID()); $this->getID());
foreach ($hunks as $hunk) { foreach ($hunks as $hunk) {

View file

@ -189,7 +189,7 @@ final class DifferentialDiff
$hunks = $change->getHunks(); $hunks = $change->getHunks();
if ($hunks) { if ($hunks) {
foreach ($hunks as $hunk) { foreach ($hunks as $hunk) {
$dhunk = new DifferentialModernHunk(); $dhunk = new DifferentialHunk();
$dhunk->setOldOffset($hunk->getOldOffset()); $dhunk->setOldOffset($hunk->getOldOffset());
$dhunk->setOldLen($hunk->getOldLength()); $dhunk->setOldLen($hunk->getOldLength());
$dhunk->setNewOffset($hunk->getNewOffset()); $dhunk->setNewOffset($hunk->getNewOffset());

View file

@ -1,6 +1,6 @@
<?php <?php
abstract class DifferentialHunk final class DifferentialHunk
extends DifferentialDAO extends DifferentialDAO
implements implements
PhabricatorPolicyInterface, PhabricatorPolicyInterface,
@ -11,16 +11,55 @@ abstract class DifferentialHunk
protected $oldLen; protected $oldLen;
protected $newOffset; protected $newOffset;
protected $newLen; protected $newLen;
protected $dataType;
protected $dataEncoding;
protected $dataFormat;
protected $data;
private $changeset; private $changeset;
private $splitLines; private $splitLines;
private $structuredLines; private $structuredLines;
private $structuredFiles = array(); private $structuredFiles = array();
private $rawData;
private $forcedEncoding;
private $fileData;
const FLAG_LINES_ADDED = 1; const FLAG_LINES_ADDED = 1;
const FLAG_LINES_REMOVED = 2; const FLAG_LINES_REMOVED = 2;
const FLAG_LINES_STABLE = 4; const FLAG_LINES_STABLE = 4;
const DATATYPE_TEXT = 'text';
const DATATYPE_FILE = 'file';
const DATAFORMAT_RAW = 'byte';
const DATAFORMAT_DEFLATED = 'gzde';
protected function getConfiguration() {
return array(
self::CONFIG_BINARY => array(
'data' => true,
),
self::CONFIG_COLUMN_SCHEMA => array(
'dataType' => 'bytes4',
'dataEncoding' => 'text16?',
'dataFormat' => 'bytes4',
'oldOffset' => 'uint32',
'oldLen' => 'uint32',
'newOffset' => 'uint32',
'newLen' => 'uint32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_changeset' => array(
'columns' => array('changesetID'),
),
'key_created' => array(
'columns' => array('dateCreated'),
),
),
) + parent::getConfiguration();
}
public function getAddedLines() { public function getAddedLines() {
return $this->makeContent($include = '+'); return $this->makeContent($include = '+');
} }
@ -214,6 +253,197 @@ abstract class DifferentialHunk
} }
/* -( Storage )------------------------------------------------------------ */
public function setChanges($text) {
$this->rawData = $text;
$this->dataEncoding = $this->detectEncodingForStorage($text);
$this->dataType = self::DATATYPE_TEXT;
list($format, $data) = $this->formatDataForStorage($text);
$this->dataFormat = $format;
$this->data = $data;
return $this;
}
public function getChanges() {
return $this->getUTF8StringFromStorage(
$this->getRawData(),
nonempty($this->forcedEncoding, $this->getDataEncoding()));
}
public function forceEncoding($encoding) {
$this->forcedEncoding = $encoding;
return $this;
}
private function formatDataForStorage($data) {
$deflated = PhabricatorCaches::maybeDeflateData($data);
if ($deflated !== null) {
return array(self::DATAFORMAT_DEFLATED, $deflated);
}
return array(self::DATAFORMAT_RAW, $data);
}
public function saveAsText() {
$old_type = $this->getDataType();
$old_data = $this->getData();
if ($old_type == self::DATATYPE_TEXT) {
return $this;
}
$raw_data = $this->getRawData();
$this->setDataType(self::DATATYPE_TEXT);
list($format, $data) = $this->formatDataForStorage($raw_data);
$this->setDataFormat($format);
$this->setData($data);
$result = $this->save();
$this->destroyData($old_type, $old_data);
return $result;
}
public function saveAsFile() {
$old_type = $this->getDataType();
$old_data = $this->getData();
if ($old_type == self::DATATYPE_FILE) {
return $this;
}
$raw_data = $this->getRawData();
list($format, $data) = $this->formatDataForStorage($raw_data);
$this->setDataFormat($format);
$file = PhabricatorFile::newFromFileData(
$data,
array(
'name' => 'differential-hunk',
'mime-type' => 'application/octet-stream',
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
$this->setDataType(self::DATATYPE_FILE);
$this->setData($file->getPHID());
// NOTE: Because hunks don't have a PHID and we just load hunk data with
// the omnipotent viewer, we do not need to attach the file to anything.
$result = $this->save();
$this->destroyData($old_type, $old_data);
return $result;
}
private function getRawData() {
if ($this->rawData === null) {
$type = $this->getDataType();
$data = $this->getData();
switch ($type) {
case self::DATATYPE_TEXT:
// In this storage type, the changes are stored on the object.
$data = $data;
break;
case self::DATATYPE_FILE:
$data = $this->loadFileData();
break;
default:
throw new Exception(
pht('Hunk has unsupported data type "%s"!', $type));
}
$format = $this->getDataFormat();
switch ($format) {
case self::DATAFORMAT_RAW:
// In this format, the changes are stored as-is.
$data = $data;
break;
case self::DATAFORMAT_DEFLATED:
$data = PhabricatorCaches::inflateData($data);
break;
default:
throw new Exception(
pht('Hunk has unsupported data encoding "%s"!', $type));
}
$this->rawData = $data;
}
return $this->rawData;
}
private function loadFileData() {
if ($this->fileData === null) {
$type = $this->getDataType();
if ($type !== self::DATATYPE_FILE) {
throw new Exception(
pht(
'Unable to load file data for hunk with wrong data type ("%s").',
$type));
}
$file_phid = $this->getData();
$file = $this->loadRawFile($file_phid);
$data = $file->loadFileData();
$this->fileData = $data;
}
return $this->fileData;
}
private function loadRawFile($file_phid) {
$viewer = PhabricatorUser::getOmnipotentUser();
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->execute();
if (!$files) {
throw new Exception(
pht(
'Failed to load file ("%s") with hunk data.',
$file_phid));
}
$file = head($files);
return $file;
}
private function destroyData(
$type,
$data,
PhabricatorDestructionEngine $engine = null) {
if (!$engine) {
$engine = new PhabricatorDestructionEngine();
}
switch ($type) {
case self::DATATYPE_FILE:
$file = $this->loadRawFile($data);
$engine->destroyObject($file);
break;
}
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */ /* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -237,8 +467,13 @@ abstract class DifferentialHunk
public function destroyObjectPermanently( public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) { PhabricatorDestructionEngine $engine) {
$type = $this->getDataType();
$data = $this->getData();
$this->destroyData($type, $data, $engine);
$this->delete(); $this->delete();
} }
} }

View file

@ -1,249 +0,0 @@
<?php
final class DifferentialModernHunk extends DifferentialHunk {
const DATATYPE_TEXT = 'text';
const DATATYPE_FILE = 'file';
const DATAFORMAT_RAW = 'byte';
const DATAFORMAT_DEFLATED = 'gzde';
protected $dataType;
protected $dataEncoding;
protected $dataFormat;
protected $data;
private $rawData;
private $forcedEncoding;
private $fileData;
public function getTableName() {
return 'differential_hunk_modern';
}
protected function getConfiguration() {
return array(
self::CONFIG_BINARY => array(
'data' => true,
),
self::CONFIG_COLUMN_SCHEMA => array(
'dataType' => 'bytes4',
'dataEncoding' => 'text16?',
'dataFormat' => 'bytes4',
'oldOffset' => 'uint32',
'oldLen' => 'uint32',
'newOffset' => 'uint32',
'newLen' => 'uint32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_changeset' => array(
'columns' => array('changesetID'),
),
'key_created' => array(
'columns' => array('dateCreated'),
),
),
) + parent::getConfiguration();
}
public function setChanges($text) {
$this->rawData = $text;
$this->dataEncoding = $this->detectEncodingForStorage($text);
$this->dataType = self::DATATYPE_TEXT;
list($format, $data) = $this->formatDataForStorage($text);
$this->dataFormat = $format;
$this->data = $data;
return $this;
}
public function getChanges() {
return $this->getUTF8StringFromStorage(
$this->getRawData(),
nonempty($this->forcedEncoding, $this->getDataEncoding()));
}
public function forceEncoding($encoding) {
$this->forcedEncoding = $encoding;
return $this;
}
private function formatDataForStorage($data) {
$deflated = PhabricatorCaches::maybeDeflateData($data);
if ($deflated !== null) {
return array(self::DATAFORMAT_DEFLATED, $deflated);
}
return array(self::DATAFORMAT_RAW, $data);
}
public function saveAsText() {
$old_type = $this->getDataType();
$old_data = $this->getData();
if ($old_type == self::DATATYPE_TEXT) {
return $this;
}
$raw_data = $this->getRawData();
$this->setDataType(self::DATATYPE_TEXT);
list($format, $data) = $this->formatDataForStorage($raw_data);
$this->setDataFormat($format);
$this->setData($data);
$result = $this->save();
$this->destroyData($old_type, $old_data);
return $result;
}
public function saveAsFile() {
$old_type = $this->getDataType();
$old_data = $this->getData();
if ($old_type == self::DATATYPE_FILE) {
return $this;
}
$raw_data = $this->getRawData();
list($format, $data) = $this->formatDataForStorage($raw_data);
$this->setDataFormat($format);
$file = PhabricatorFile::newFromFileData(
$data,
array(
'name' => 'differential-hunk',
'mime-type' => 'application/octet-stream',
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
$this->setDataType(self::DATATYPE_FILE);
$this->setData($file->getPHID());
// NOTE: Because hunks don't have a PHID and we just load hunk data with
// the omnipotent viewer, we do not need to attach the file to anything.
$result = $this->save();
$this->destroyData($old_type, $old_data);
return $result;
}
private function getRawData() {
if ($this->rawData === null) {
$type = $this->getDataType();
$data = $this->getData();
switch ($type) {
case self::DATATYPE_TEXT:
// In this storage type, the changes are stored on the object.
$data = $data;
break;
case self::DATATYPE_FILE:
$data = $this->loadFileData();
break;
default:
throw new Exception(
pht('Hunk has unsupported data type "%s"!', $type));
}
$format = $this->getDataFormat();
switch ($format) {
case self::DATAFORMAT_RAW:
// In this format, the changes are stored as-is.
$data = $data;
break;
case self::DATAFORMAT_DEFLATED:
$data = PhabricatorCaches::inflateData($data);
break;
default:
throw new Exception(
pht('Hunk has unsupported data encoding "%s"!', $type));
}
$this->rawData = $data;
}
return $this->rawData;
}
private function loadFileData() {
if ($this->fileData === null) {
$type = $this->getDataType();
if ($type !== self::DATATYPE_FILE) {
throw new Exception(
pht(
'Unable to load file data for hunk with wrong data type ("%s").',
$type));
}
$file_phid = $this->getData();
$file = $this->loadRawFile($file_phid);
$data = $file->loadFileData();
$this->fileData = $data;
}
return $this->fileData;
}
private function loadRawFile($file_phid) {
$viewer = PhabricatorUser::getOmnipotentUser();
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->execute();
if (!$files) {
throw new Exception(
pht(
'Failed to load file ("%s") with hunk data.',
$file_phid));
}
$file = head($files);
return $file;
}
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$type = $this->getDataType();
$data = $this->getData();
$this->destroyData($type, $data, $engine);
return parent::destroyObjectPermanently($engine);
}
private function destroyData(
$type,
$data,
PhabricatorDestructionEngine $engine = null) {
if (!$engine) {
$engine = new PhabricatorDestructionEngine();
}
switch ($type) {
case self::DATATYPE_FILE:
$file = $this->loadRawFile($data);
$engine->destroyObject($file);
break;
}
}
}

View file

@ -5,7 +5,7 @@ final class DifferentialHunkTestCase extends PhutilTestCase {
public function testMakeChanges() { public function testMakeChanges() {
$root = dirname(__FILE__).'/hunk/'; $root = dirname(__FILE__).'/hunk/';
$hunk = new DifferentialModernHunk(); $hunk = new DifferentialHunk();
$hunk->setChanges(Filesystem::readFile($root.'basic.diff')); $hunk->setChanges(Filesystem::readFile($root.'basic.diff'));
$hunk->setOldOffset(1); $hunk->setOldOffset(1);
$hunk->setNewOffset(11); $hunk->setNewOffset(11);
@ -23,7 +23,7 @@ final class DifferentialHunkTestCase extends PhutilTestCase {
); );
$this->assertEqual($added, $hunk->getAddedLines()); $this->assertEqual($added, $hunk->getAddedLines());
$hunk = new DifferentialModernHunk(); $hunk = new DifferentialHunk();
$hunk->setChanges(Filesystem::readFile($root.'newline.diff')); $hunk->setChanges(Filesystem::readFile($root.'newline.diff'));
$hunk->setOldOffset(1); $hunk->setOldOffset(1);
$hunk->setNewOffset(11); $hunk->setNewOffset(11);