1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-26 00:32:41 +01:00

Replace "PhutilFileTree" with a more abstract "VectorTree"

Summary:
Ref T13520. Replace "FileTree" with a "VectorTree" that does roughly the same thing. The major goals are:

  - Compress trees which contain sequences of child directories with no sibilings.
  - Build hierarchies of paths where path components may include renames.

This is approximately similar to "FileTree" and similar to client logic in the new paths panel.

Test Plan: See next change.

Maniphest Tasks: T13520

Differential Revision: https://secure.phabricator.com/D21182
This commit is contained in:
epriestley 2020-04-28 11:10:33 -07:00
parent b81818b287
commit 6ec09b2f48
4 changed files with 212 additions and 114 deletions

View file

@ -154,6 +154,8 @@ phutil_register_library_map(array(
'ArcanistDiffParserTestCase' => 'parser/__tests__/ArcanistDiffParserTestCase.php', 'ArcanistDiffParserTestCase' => 'parser/__tests__/ArcanistDiffParserTestCase.php',
'ArcanistDiffUtils' => 'difference/ArcanistDiffUtils.php', 'ArcanistDiffUtils' => 'difference/ArcanistDiffUtils.php',
'ArcanistDiffUtilsTestCase' => 'difference/__tests__/ArcanistDiffUtilsTestCase.php', 'ArcanistDiffUtilsTestCase' => 'difference/__tests__/ArcanistDiffUtilsTestCase.php',
'ArcanistDiffVectorNode' => 'difference/ArcanistDiffVectorNode.php',
'ArcanistDiffVectorTree' => 'difference/ArcanistDiffVectorTree.php',
'ArcanistDiffWorkflow' => 'workflow/ArcanistDiffWorkflow.php', 'ArcanistDiffWorkflow' => 'workflow/ArcanistDiffWorkflow.php',
'ArcanistDifferentialCommitMessage' => 'differential/ArcanistDifferentialCommitMessage.php', 'ArcanistDifferentialCommitMessage' => 'differential/ArcanistDifferentialCommitMessage.php',
'ArcanistDifferentialCommitMessageParserException' => 'differential/ArcanistDifferentialCommitMessageParserException.php', 'ArcanistDifferentialCommitMessageParserException' => 'differential/ArcanistDifferentialCommitMessageParserException.php',
@ -691,7 +693,6 @@ phutil_register_library_map(array(
'PhutilExecutionEnvironment' => 'utils/PhutilExecutionEnvironment.php', 'PhutilExecutionEnvironment' => 'utils/PhutilExecutionEnvironment.php',
'PhutilFileLock' => 'filesystem/PhutilFileLock.php', 'PhutilFileLock' => 'filesystem/PhutilFileLock.php',
'PhutilFileLockTestCase' => 'filesystem/__tests__/PhutilFileLockTestCase.php', 'PhutilFileLockTestCase' => 'filesystem/__tests__/PhutilFileLockTestCase.php',
'PhutilFileTree' => 'filesystem/PhutilFileTree.php',
'PhutilFrenchLocale' => 'internationalization/locales/PhutilFrenchLocale.php', 'PhutilFrenchLocale' => 'internationalization/locales/PhutilFrenchLocale.php',
'PhutilGermanLocale' => 'internationalization/locales/PhutilGermanLocale.php', 'PhutilGermanLocale' => 'internationalization/locales/PhutilGermanLocale.php',
'PhutilGitBinaryAnalyzer' => 'filesystem/binary/PhutilGitBinaryAnalyzer.php', 'PhutilGitBinaryAnalyzer' => 'filesystem/binary/PhutilGitBinaryAnalyzer.php',
@ -1134,6 +1135,8 @@ phutil_register_library_map(array(
'ArcanistDiffParserTestCase' => 'PhutilTestCase', 'ArcanistDiffParserTestCase' => 'PhutilTestCase',
'ArcanistDiffUtils' => 'Phobject', 'ArcanistDiffUtils' => 'Phobject',
'ArcanistDiffUtilsTestCase' => 'PhutilTestCase', 'ArcanistDiffUtilsTestCase' => 'PhutilTestCase',
'ArcanistDiffVectorNode' => 'Phobject',
'ArcanistDiffVectorTree' => 'Phobject',
'ArcanistDiffWorkflow' => 'ArcanistWorkflow', 'ArcanistDiffWorkflow' => 'ArcanistWorkflow',
'ArcanistDifferentialCommitMessage' => 'Phobject', 'ArcanistDifferentialCommitMessage' => 'Phobject',
'ArcanistDifferentialCommitMessageParserException' => 'Exception', 'ArcanistDifferentialCommitMessageParserException' => 'Exception',
@ -1702,7 +1705,6 @@ phutil_register_library_map(array(
'PhutilExecutionEnvironment' => 'Phobject', 'PhutilExecutionEnvironment' => 'Phobject',
'PhutilFileLock' => 'PhutilLock', 'PhutilFileLock' => 'PhutilLock',
'PhutilFileLockTestCase' => 'PhutilTestCase', 'PhutilFileLockTestCase' => 'PhutilTestCase',
'PhutilFileTree' => 'Phobject',
'PhutilFrenchLocale' => 'PhutilLocale', 'PhutilFrenchLocale' => 'PhutilLocale',
'PhutilGermanLocale' => 'PhutilLocale', 'PhutilGermanLocale' => 'PhutilLocale',
'PhutilGitBinaryAnalyzer' => 'PhutilBinaryAnalyzer', 'PhutilGitBinaryAnalyzer' => 'PhutilBinaryAnalyzer',

View file

@ -0,0 +1,113 @@
<?php
final class ArcanistDiffVectorNode
extends Phobject {
private $vector;
private $children = array();
private $parentNode;
private $displayNode;
private $displayVector;
private $displayDepth;
private $valueNode;
private $attributes = array();
public function setVector(array $vector) {
$this->vector = $vector;
return $this;
}
public function getVector() {
return $this->vector;
}
public function getChildren() {
return $this->children;
}
public function setParentNode(ArcanistDiffVectorNode $parent) {
$this->parentNode = $parent;
return $this;
}
public function getParentNode() {
return $this->parentNode;
}
public function addChild(array $vector, $length, $idx) {
$is_node = ($idx === ($length - 1));
$element = $vector[$idx];
if (!isset($this->children[$element])) {
$this->children[$element] = id(new self())
->setParentNode($this)
->setVector(array_slice($vector, 0, $idx + 1));
}
$child = $this->children[$element];
if ($is_node) {
$child->setValueNode($child);
return;
}
$child->addChild($vector, $length, $idx + 1);
}
public function getDisplayVector() {
return $this->displayVector;
}
public function appendDisplayElement($element) {
if ($this->displayVector === null) {
$this->displayVector = array();
}
$this->displayVector[] = $element;
return $this;
}
public function setDisplayNode(ArcanistDiffVectorNode $display_node) {
$this->displayNode = $display_node;
return $this;
}
public function getDisplayNode() {
return $this->displayNode;
}
public function setDisplayDepth($display_depth) {
$this->displayDepth = $display_depth;
return $this;
}
public function getDisplayDepth() {
return $this->displayDepth;
}
public function setValueNode($value_node) {
$this->valueNode = $value_node;
return $this;
}
public function getValueNode() {
return $this->valueNode;
}
public function setAncestralAttribute($key, $value) {
$this->attributes[$key] = $value;
$parent = $this->getParentNode();
if ($parent) {
$parent->setAncestralAttribute($key, $value);
}
return $this;
}
public function getAttribute($key, $default = null) {
return idx($this->attributes, $key, $default);
}
}

View file

@ -0,0 +1,95 @@
<?php
final class ArcanistDiffVectorTree
extends Phobject {
private $vectors;
public function addVector(array $vector) {
$this->vectors[] = $vector;
return $this;
}
public function newDisplayList() {
$root = new ArcanistDiffVectorNode();
foreach ($this->vectors as $vector) {
$root->addChild($vector, count($vector), 0);
}
foreach ($root->getChildren() as $child) {
$this->compressTree($child);
}
$root->setDisplayDepth(-1);
foreach ($root->getChildren() as $child) {
$this->updateDisplayDepth($child);
}
return $this->getDisplayList($root);
}
private function compressTree(ArcanistDiffVectorNode $node) {
$display_node = $node;
$children = $node->getChildren();
if ($children) {
$parent = $node->getParentNode();
if ($parent) {
$siblings = $parent->getChildren();
if (count($siblings) === 1) {
if (!$parent->getValueNode()) {
$parent_display = $parent->getDisplayNode();
if ($parent_display) {
$display_node = $parent_display;
if ($node->getValueNode()) {
$parent->setValueNode($node->getValueNode());
}
}
}
}
}
}
$node->setDisplayNode($display_node);
$display_element = last($node->getVector());
$display_node->appendDisplayElement($display_element);
foreach ($children as $child) {
$this->compressTree($child);
}
}
private function updateDisplayDepth(ArcanistDiffVectorNode $node) {
$parent_depth = $node->getParentNode()->getDisplayDepth();
if ($node->getDisplayVector() === null) {
$display_depth = $parent_depth;
} else {
$display_depth = $parent_depth + 1;
}
$node->setDisplayDepth($display_depth);
foreach ($node->getChildren() as $child) {
$this->updateDisplayDepth($child);
}
}
private function getDisplayList(ArcanistDiffVectorNode $node) {
$result = array();
foreach ($node->getChildren() as $child) {
if ($child->getDisplayVector() !== null) {
$result[] = $child;
}
foreach ($this->getDisplayList($child) as $item) {
$result[] = $item;
}
}
return $result;
}
}

View file

@ -1,112 +0,0 @@
<?php
/**
* Data structure for representing filesystem directory trees.
*/
final class PhutilFileTree extends Phobject {
private $name;
private $fullPath;
private $data;
private $depth = 0;
private $parentNode;
private $children = array();
public function addPath($path, $data) {
$parts = $this->splitPath($path);
$parts = array_reverse($parts);
$this->insertPath($parts, $data);
return $this;
}
public function destroy() {
$this->parentNode = null;
foreach ($this->children as $child) {
$child->destroy();
}
$this->children = array();
return $this;
}
/**
* Get the next node, iterating in depth-first order.
*/
public function getNextNode() {
if ($this->children) {
return head($this->children);
}
$cursor = $this;
while ($cursor) {
if ($cursor->getNextSibling()) {
return $cursor->getNextSibling();
}
$cursor = $cursor->parentNode;
}
return null;
}
public function getName() {
return $this->name;
}
public function getFullPath() {
return $this->fullPath;
}
public function getDepth() {
return $this->depth;
}
public function getData() {
return $this->data;
}
protected function insertPath(array $parts, $data) {
$part = array_pop($parts);
if ($part === null) {
if ($this->data) {
$full_path = $this->getFullPath();
throw new Exception(
pht("Duplicate insertion for path '%s'.", $full_path));
}
$this->data = $data;
return;
}
if (empty($this->children[$part])) {
$node = new PhutilFileTree();
$node->parentNode = $this;
$node->depth = $this->depth + 1;
$node->name = $part;
$node->fullPath = $this->parentNode ? ($this->fullPath.'/'.$part) : $part;
$this->children[$part] = $node;
}
$this->children[$part]->insertPath($parts, $data);
}
protected function splitPath($path) {
$path = trim($path, '/');
$parts = preg_split('@/+@', $path);
return $parts;
}
protected function getNextSibling() {
if (!$this->parentNode) {
return null;
}
$found = false;
foreach ($this->parentNode->children as $node) {
if ($found) {
return $node;
}
if ($this->name === $node->name) {
$found = true;
}
}
return null;
}
}