Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2894356
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
19 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/differential/storage/DifferentialChangeset.php b/src/applications/differential/storage/DifferentialChangeset.php
index fa4cc37ec8..d7d66ae97d 100644
--- a/src/applications/differential/storage/DifferentialChangeset.php
+++ b/src/applications/differential/storage/DifferentialChangeset.php
@@ -1,730 +1,734 @@
<?php
final class DifferentialChangeset
extends DifferentialDAO
implements
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
protected $diffID;
protected $oldFile;
protected $filename;
protected $awayPaths;
protected $changeType;
protected $fileType;
protected $metadata = array();
protected $oldProperties;
protected $newProperties;
protected $addLines;
protected $delLines;
private $unsavedHunks = array();
private $hunks = self::ATTACHABLE;
private $diff = self::ATTACHABLE;
private $authorityPackages;
private $changesetPackages;
private $newFileObject = self::ATTACHABLE;
private $oldFileObject = self::ATTACHABLE;
private $hasOldState;
private $hasNewState;
private $oldStateMetadata;
private $newStateMetadata;
private $oldFileType;
private $newFileType;
const TABLE_CACHE = 'differential_changeset_parse_cache';
const METADATA_TRUSTED_ATTRIBUTES = 'attributes.trusted';
const METADATA_UNTRUSTED_ATTRIBUTES = 'attributes.untrusted';
const METADATA_EFFECT_HASH = 'hash.effect';
const ATTRIBUTE_GENERATED = 'generated';
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
'oldProperties' => self::SERIALIZATION_JSON,
'newProperties' => self::SERIALIZATION_JSON,
'awayPaths' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'oldFile' => 'bytes?',
'filename' => 'bytes',
'changeType' => 'uint32',
'fileType' => 'uint32',
'addLines' => 'uint32',
'delLines' => 'uint32',
// T6203/NULLABILITY
// These should all be non-nullable, and store reasonable default
// JSON values if empty.
'awayPaths' => 'text?',
'metadata' => 'text?',
'oldProperties' => 'text?',
'newProperties' => 'text?',
),
self::CONFIG_KEY_SCHEMA => array(
'diffID' => array(
'columns' => array('diffID'),
),
),
) + parent::getConfiguration();
}
public function getAffectedLineCount() {
return $this->getAddLines() + $this->getDelLines();
}
public function attachHunks(array $hunks) {
assert_instances_of($hunks, 'DifferentialHunk');
$this->hunks = $hunks;
return $this;
}
public function getHunks() {
return $this->assertAttached($this->hunks);
}
public function getDisplayFilename() {
$name = $this->getFilename();
if ($this->getFileType() == DifferentialChangeType::FILE_DIRECTORY) {
$name .= '/';
}
return $name;
}
public function getOwnersFilename() {
// TODO: For Subversion, we should adjust these paths to be relative to
// the repository root where possible.
$path = $this->getFilename();
if (!isset($path[0])) {
return '/';
}
if ($path[0] != '/') {
$path = '/'.$path;
}
return $path;
}
public function addUnsavedHunk(DifferentialHunk $hunk) {
if ($this->hunks === self::ATTACHABLE) {
$this->hunks = array();
}
$this->hunks[] = $hunk;
$this->unsavedHunks[] = $hunk;
return $this;
}
public function setAuthorityPackages(array $authority_packages) {
$this->authorityPackages = mpull($authority_packages, null, 'getPHID');
return $this;
}
public function getAuthorityPackages() {
return $this->authorityPackages;
}
public function setChangesetPackages($changeset_packages) {
$this->changesetPackages = mpull($changeset_packages, null, 'getPHID');
return $this;
}
public function getChangesetPackages() {
return $this->changesetPackages;
}
public function setHasOldState($has_old_state) {
$this->hasOldState = $has_old_state;
return $this;
}
public function setHasNewState($has_new_state) {
$this->hasNewState = $has_new_state;
return $this;
}
public function hasOldState() {
if ($this->hasOldState !== null) {
return $this->hasOldState;
}
$change_type = $this->getChangeType();
return !DifferentialChangeType::isCreateChangeType($change_type);
}
public function hasNewState() {
if ($this->hasNewState !== null) {
return $this->hasNewState;
}
$change_type = $this->getChangeType();
return !DifferentialChangeType::isDeleteChangeType($change_type);
}
public function save() {
$this->openTransaction();
$ret = parent::save();
foreach ($this->unsavedHunks as $hunk) {
$hunk->setChangesetID($this->getID());
$hunk->save();
}
$this->saveTransaction();
return $ret;
}
public function delete() {
$this->openTransaction();
$hunks = id(new DifferentialHunk())->loadAllWhere(
'changesetID = %d',
$this->getID());
foreach ($hunks as $hunk) {
$hunk->delete();
}
$this->unsavedHunks = array();
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE id = %d',
self::TABLE_CACHE,
$this->getID());
$ret = parent::delete();
$this->saveTransaction();
return $ret;
}
/**
* Test if this changeset and some other changeset put the affected file in
* the same state.
*
* @param DifferentialChangeset Changeset to compare against.
* @return bool True if the two changesets have the same effect.
*/
public function hasSameEffectAs(DifferentialChangeset $other) {
if ($this->getFilename() !== $other->getFilename()) {
return false;
}
$hash_key = self::METADATA_EFFECT_HASH;
$u_hash = $this->getChangesetMetadata($hash_key);
if ($u_hash === null) {
return false;
}
$v_hash = $other->getChangesetMetadata($hash_key);
if ($v_hash === null) {
return false;
}
if ($u_hash !== $v_hash) {
return false;
}
// Make sure the final states for the file properties (like the "+x"
// executable bit) match one another.
$u_props = $this->getNewProperties();
$v_props = $other->getNewProperties();
ksort($u_props);
ksort($v_props);
if ($u_props !== $v_props) {
return false;
}
return true;
}
public function getSortKey() {
$sort_key = $this->getFilename();
// Sort files with ".h" in them first, so headers (.h, .hpp) come before
// implementations (.c, .cpp, .cs).
$sort_key = str_replace('.h', '.!h', $sort_key);
return $sort_key;
}
public function makeNewFile() {
$file = mpull($this->getHunks(), 'makeNewFile');
return implode('', $file);
}
public function makeOldFile() {
$file = mpull($this->getHunks(), 'makeOldFile');
return implode('', $file);
}
public function makeChangesWithContext($num_lines = 3) {
$with_context = array();
foreach ($this->getHunks() as $hunk) {
$context = array();
$changes = explode("\n", $hunk->getChanges());
foreach ($changes as $l => $line) {
$type = substr($line, 0, 1);
if ($type == '+' || $type == '-') {
$context += array_fill($l - $num_lines, 2 * $num_lines + 1, true);
}
}
$with_context[] = array_intersect_key($changes, $context);
}
return array_mergev($with_context);
}
public function getAnchorName() {
return 'change-'.PhabricatorHash::digestForAnchor($this->getFilename());
}
public function getAbsoluteRepositoryPath(
PhabricatorRepository $repository = null,
DifferentialDiff $diff = null) {
$base = '/';
if ($diff && $diff->getSourceControlPath()) {
$base = id(new PhutilURI($diff->getSourceControlPath()))->getPath();
}
$path = $this->getFilename();
$path = rtrim($base, '/').'/'.ltrim($path, '/');
$svn = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN;
if ($repository && $repository->getVersionControlSystem() == $svn) {
$prefix = $repository->getDetail('remote-uri');
$prefix = id(new PhutilURI($prefix))->getPath();
if (!strncmp($path, $prefix, strlen($prefix))) {
$path = substr($path, strlen($prefix));
}
$path = '/'.ltrim($path, '/');
}
return $path;
}
public function attachDiff(DifferentialDiff $diff) {
$this->diff = $diff;
return $this;
}
public function getDiff() {
return $this->assertAttached($this->diff);
}
public function getOldStatePathVector() {
$path = $this->getOldFile();
if (!strlen($path)) {
$path = $this->getFilename();
}
$path = trim($path, '/');
$path = explode('/', $path);
return $path;
}
public function getNewStatePathVector() {
if (!$this->hasNewState()) {
return null;
}
$path = $this->getFilename();
$path = trim($path, '/');
$path = explode('/', $path);
return $path;
}
public function newFileTreeIcon() {
$icon = $this->getPathIconIcon();
$color = $this->getPathIconColor();
return id(new PHUIIconView())
->setIcon("{$icon} {$color}");
}
public function getIsOwnedChangeset() {
$authority_packages = $this->getAuthorityPackages();
$changeset_packages = $this->getChangesetPackages();
if (!$authority_packages || !$changeset_packages) {
return false;
}
return (bool)array_intersect_key($authority_packages, $changeset_packages);
}
public function getIsLowImportanceChangeset() {
if (!$this->hasNewState()) {
return true;
}
if ($this->isGeneratedChangeset()) {
return true;
}
return false;
}
public function getPathIconIcon() {
return idx($this->getPathIconDetails(), 'icon');
}
public function getPathIconColor() {
return idx($this->getPathIconDetails(), 'color');
}
private function getPathIconDetails() {
$change_icons = array(
DifferentialChangeType::TYPE_DELETE => array(
'icon' => 'fa-times',
'color' => 'delete-color',
),
DifferentialChangeType::TYPE_ADD => array(
'icon' => 'fa-plus',
'color' => 'create-color',
),
DifferentialChangeType::TYPE_MOVE_AWAY => array(
'icon' => 'fa-circle-o',
'color' => 'grey',
),
DifferentialChangeType::TYPE_MULTICOPY => array(
'icon' => 'fa-circle-o',
'color' => 'grey',
),
DifferentialChangeType::TYPE_MOVE_HERE => array(
'icon' => 'fa-plus-circle',
'color' => 'create-color',
),
DifferentialChangeType::TYPE_COPY_HERE => array(
'icon' => 'fa-plus-circle',
'color' => 'create-color',
),
);
$change_type = $this->getChangeType();
if (isset($change_icons[$change_type])) {
return $change_icons[$change_type];
}
if ($this->isGeneratedChangeset()) {
return array(
'icon' => 'fa-cogs',
'color' => 'grey',
);
}
$file_type = $this->getFileType();
$icon = DifferentialChangeType::getIconForFileType($file_type);
return array(
'icon' => $icon,
'color' => 'bluetext',
);
}
public function setChangesetMetadata($key, $value) {
if (!is_array($this->metadata)) {
$this->metadata = array();
}
$this->metadata[$key] = $value;
return $this;
}
public function getChangesetMetadata($key, $default = null) {
if (!is_array($this->metadata)) {
return $default;
}
return idx($this->metadata, $key, $default);
}
private function setInternalChangesetAttribute($trusted, $key, $value) {
if ($trusted) {
$meta_key = self::METADATA_TRUSTED_ATTRIBUTES;
} else {
$meta_key = self::METADATA_UNTRUSTED_ATTRIBUTES;
}
$attributes = $this->getChangesetMetadata($meta_key, array());
$attributes[$key] = $value;
$this->setChangesetMetadata($meta_key, $attributes);
return $this;
}
private function getInternalChangesetAttributes($trusted) {
if ($trusted) {
$meta_key = self::METADATA_TRUSTED_ATTRIBUTES;
} else {
$meta_key = self::METADATA_UNTRUSTED_ATTRIBUTES;
}
return $this->getChangesetMetadata($meta_key, array());
}
public function setTrustedChangesetAttribute($key, $value) {
return $this->setInternalChangesetAttribute(true, $key, $value);
}
public function getTrustedChangesetAttributes() {
return $this->getInternalChangesetAttributes(true);
}
public function getTrustedChangesetAttribute($key, $default = null) {
$map = $this->getTrustedChangesetAttributes();
return idx($map, $key, $default);
}
public function setUntrustedChangesetAttribute($key, $value) {
return $this->setInternalChangesetAttribute(false, $key, $value);
}
public function getUntrustedChangesetAttributes() {
return $this->getInternalChangesetAttributes(false);
}
public function getUntrustedChangesetAttribute($key, $default = null) {
$map = $this->getUntrustedChangesetAttributes();
return idx($map, $key, $default);
}
public function getChangesetAttributes() {
// Prefer trusted values over untrusted values when both exist.
return
$this->getTrustedChangesetAttributes() +
$this->getUntrustedChangesetAttributes();
}
public function getChangesetAttribute($key, $default = null) {
$map = $this->getChangesetAttributes();
return idx($map, $key, $default);
}
public function isGeneratedChangeset() {
return $this->getChangesetAttribute(self::ATTRIBUTE_GENERATED);
}
public function getNewFileObjectPHID() {
$metadata = $this->getMetadata();
return idx($metadata, 'new:binary-phid');
}
public function getOldFileObjectPHID() {
$metadata = $this->getMetadata();
return idx($metadata, 'old:binary-phid');
}
public function attachNewFileObject(PhabricatorFile $file) {
$this->newFileObject = $file;
return $this;
}
public function getNewFileObject() {
return $this->assertAttached($this->newFileObject);
}
public function attachOldFileObject(PhabricatorFile $file) {
$this->oldFileObject = $file;
return $this;
}
public function getOldFileObject() {
return $this->assertAttached($this->oldFileObject);
}
public function newComparisonChangeset(
DifferentialChangeset $against = null) {
$left = $this;
$right = $against;
$left_data = $left->makeNewFile();
$left_properties = $left->getNewProperties();
$left_metadata = $left->getNewStateMetadata();
$left_state = $left->hasNewState();
$shared_metadata = $left->getMetadata();
$left_type = $left->getNewFileType();
if ($right) {
$right_data = $right->makeNewFile();
$right_properties = $right->getNewProperties();
$right_metadata = $right->getNewStateMetadata();
$right_state = $right->hasNewState();
$shared_metadata = $right->getMetadata();
$right_type = $right->getNewFileType();
+
+ $file_name = $right->getFilename();
} else {
$right_data = $left->makeOldFile();
$right_properties = $left->getOldProperties();
$right_metadata = $left->getOldStateMetadata();
$right_state = $left->hasOldState();
$right_type = $left->getOldFileType();
+
+ $file_name = $left->getFilename();
}
$engine = new PhabricatorDifferenceEngine();
$synthetic = $engine->generateChangesetFromFileContent(
$left_data,
$right_data);
$comparison = id(new self())
->makeEphemeral(true)
->attachDiff($left->getDiff())
->setOldFile($left->getFilename())
- ->setFilename($right->getFilename());
+ ->setFilename($file_name);
// TODO: Change type?
// TODO: Away paths?
// TODO: View state key?
$comparison->attachHunks($synthetic->getHunks());
$comparison->setOldProperties($left_properties);
$comparison->setNewProperties($right_properties);
$comparison
->setOldStateMetadata($left_metadata)
->setNewStateMetadata($right_metadata)
->setHasOldState($left_state)
->setHasNewState($right_state)
->setOldFileType($left_type)
->setNewFileType($right_type);
// NOTE: Some metadata is not stored statefully, like the "generated"
// flag. For now, use the rightmost "new state" metadata to fill in these
// values.
$metadata = $comparison->getMetadata();
$metadata = $metadata + $shared_metadata;
$comparison->setMetadata($metadata);
return $comparison;
}
public function setNewFileType($new_file_type) {
$this->newFileType = $new_file_type;
return $this;
}
public function getNewFileType() {
if ($this->newFileType !== null) {
return $this->newFileType;
}
return $this->getFiletype();
}
public function setOldFileType($old_file_type) {
$this->oldFileType = $old_file_type;
return $this;
}
public function getOldFileType() {
if ($this->oldFileType !== null) {
return $this->oldFileType;
}
return $this->getFileType();
}
public function hasSourceTextBody() {
$type_map = array(
DifferentialChangeType::FILE_TEXT => true,
DifferentialChangeType::FILE_SYMLINK => true,
);
$old_body = isset($type_map[$this->getOldFileType()]);
$new_body = isset($type_map[$this->getNewFileType()]);
return ($old_body || $new_body);
}
public function getNewStateMetadata() {
return $this->getMetadataWithPrefix('new:');
}
public function setNewStateMetadata(array $metadata) {
return $this->setMetadataWithPrefix($metadata, 'new:');
}
public function getOldStateMetadata() {
return $this->getMetadataWithPrefix('old:');
}
public function setOldStateMetadata(array $metadata) {
return $this->setMetadataWithPrefix($metadata, 'old:');
}
private function getMetadataWithPrefix($prefix) {
$length = strlen($prefix);
$result = array();
foreach ($this->getMetadata() as $key => $value) {
if (strncmp($key, $prefix, $length)) {
continue;
}
$key = substr($key, $length);
$result[$key] = $value;
}
return $result;
}
private function setMetadataWithPrefix(array $metadata, $prefix) {
foreach ($metadata as $key => $value) {
$key = $prefix.$key;
$this->metadata[$key] = $value;
}
return $this;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return $this->getDiff()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getDiff()->hasAutomaticCapability($capability, $viewer);
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$hunks = id(new DifferentialHunk())->loadAllWhere(
'changesetID = %d',
$this->getID());
foreach ($hunks as $hunk) {
$engine->destroyObject($hunk);
}
$this->delete();
$this->saveTransaction();
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 19:45 (1 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1128120
Default Alt Text
(19 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment