Subversion Repositories oidplus

Rev

Blame | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * Copyright (C) 2008, 2009 Patrik Fimml
  5.  * Copyright (c) 2023 Daniel Marschall
  6.  *
  7.  * This file is part of glip.
  8.  *
  9.  * glip is free software: you can redistribute it and/or modify
  10.  * it under the terms of the GNU General Public License as published by
  11.  * the Free Software Foundation, either version 2 of the License, or
  12.  * (at your option) any later version.
  13.  
  14.  * glip is distributed in the hope that it will be useful,
  15.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17.  * GNU General Public License for more details.
  18.  *
  19.  * You should have received a copy of the GNU General Public License
  20.  * along with glip.  If not, see <http://www.gnu.org/licenses/>.
  21.  */
  22.  
  23. namespace ViaThinkSoft\Glip;
  24.  
  25. class GitTree extends GitObject
  26. {
  27.         public $nodes = array();
  28.  
  29.         public function __construct($repo) {
  30.                 parent::__construct($repo, Git::OBJ_TREE);
  31.         }
  32.  
  33.         public function _unserialize($data) {
  34.                 $this->nodes = array();
  35.                 $start = 0;
  36.                 while ($start < strlen($data)) {
  37.                         $node = new \stdClass;
  38.  
  39.                         $pos = strpos($data, "\0", $start);
  40.                         list($node->mode, $node->name) = explode(' ', substr($data, $start, $pos - $start), 2);
  41.                         $node->mode = intval($node->mode, 8);
  42.                         $node->is_dir = !!($node->mode & 040000);
  43.                         $node->is_submodule = ($node->mode == 57344);
  44.                         $node->object = substr($data, $pos + 1, 20);
  45.                         $start = $pos + 21;
  46.  
  47.                         $this->nodes[$node->name] = $node;
  48.                 }
  49.                 unset($data);
  50.         }
  51.  
  52.         protected static function nodecmp(&$a, &$b) {
  53.                 return strcmp($a->name, $b->name);
  54.         }
  55.  
  56.         public function _serialize() {
  57.                 $s = '';
  58.                 /* git requires nodes to be sorted */
  59.                 uasort($this->nodes, array('GitTree', 'nodecmp'));
  60.                 foreach ($this->nodes as $node)
  61.                         $s .= sprintf("%s %s\0%s", base_convert($node->mode, 10, 8), $node->name, $node->object);
  62.                 return $s;
  63.         }
  64.  
  65.         /**
  66.          * @brief Find the tree or blob at a certain path.
  67.          *
  68.          * @param string|array $path The path to look for, relative to this tree.
  69.          * @returns GitTree|GitBlob|null The GitTree or GitBlob at the specified path, or NULL if none
  70.          * could be found.
  71.          * @throws GitTreeInvalidPathError The path was found to be invalid. This
  72.          * can happen if you are trying to treat a file like a directory (i.e.
  73.          * @em foo/bar where @em foo is a file).
  74.          *
  75.          */
  76.         public function find($path) {
  77.                 if (!is_array($path))
  78.                         $path = explode('/', $path);
  79.  
  80.                 while ($path && !$path[0])
  81.                         array_shift($path);
  82.                 if (!$path)
  83.                         return $this->getName();
  84.  
  85.                 if (!isset($this->nodes[$path[0]]))
  86.                         return null;
  87.                 $cur = $this->nodes[$path[0]]->object;
  88.  
  89.                 array_shift($path);
  90.                 while ($path && !$path[0])
  91.                         array_shift($path);
  92.  
  93.                 if (!$path)
  94.                         return $cur;
  95.                 else {
  96.                         $cur = $this->repo->getObject($cur);
  97.                         if (!($cur instanceof GitTree))
  98.                                 throw new GitTreeInvalidPathError;
  99.                         return $cur->find($path);
  100.                 }
  101.         }
  102.  
  103.         /**
  104.          * @brief Recursively list the contents of a tree.
  105.          *
  106.          * @returns (array mapping string to string) An array where the keys are
  107.          * paths relative to the current tree, and the values are SHA-1 names of
  108.          * the corresponding blobs in binary representation.
  109.          */
  110.         public function listRecursive() {
  111.                 $r = array();
  112.  
  113.                 foreach ($this->nodes as $node) {
  114.                         if ($node->is_dir) {
  115.                                 if ($node->is_submodule) {
  116.                                         $r[$node->name . ':submodule'] = $node->object;
  117.                                 } else {
  118.                                         $subtree = $this->repo->getObject($node->object);
  119.                                         foreach ($subtree->listRecursive() as $entry => $blob) {
  120.                                                 $r[$node->name . '/' . $entry] = $blob;
  121.                                         }
  122.                                 }
  123.                         } else {
  124.                                 $r[$node->name] = $node->object;
  125.                         }
  126.                 }
  127.  
  128.                 return $r;
  129.         }
  130.  
  131.         /**
  132.          * @brief Updates a node in this tree.
  133.          *
  134.          * Missing directories in the path will be created automatically.
  135.          *
  136.          * @param $path (string) Path to the node, relative to this tree.
  137.          * @param $mode Git mode to set the node to. 0 if the node shall be
  138.          * cleared, i.e. the tree or blob shall be removed from this path.
  139.          * @param $object (string) Binary SHA-1 hash of the object that shall be
  140.          * placed at the given path.
  141.          *
  142.          * @returns (array of GitObject) An array of GitObject%s that were newly
  143.          * created while updating the specified node. Those need to be written to
  144.          * the repository together with the modified tree.
  145.          */
  146.         public function updateNode($path, $mode, $object) {
  147.                 if (!is_array($path))
  148.                         $path = explode('/', $path);
  149.                 $name = array_shift($path);
  150.                 if (count($path) == 0) {
  151.                         /* create leaf node */
  152.                         if ($mode) {
  153.                                 $node = new \stdClass;
  154.                                 $node->mode = $mode;
  155.                                 $node->name = $name;
  156.                                 $node->object = $object;
  157.                                 $node->is_dir = !!($mode & 040000);
  158.  
  159.                                 $this->nodes[$node->name] = $node;
  160.                         } else
  161.                                 unset($this->nodes[$name]);
  162.  
  163.                         return array();
  164.                 } else {
  165.                         /* descend one level */
  166.                         if (isset($this->nodes[$name])) {
  167.                                 $node = $this->nodes[$name];
  168.                                 if (!$node->is_dir)
  169.                                         throw new GitTreeInvalidPathError;
  170.                                 $subtree = clone $this->repo->getObject($node->object);
  171.                         } else {
  172.                                 /* create new tree */
  173.                                 $subtree = new GitTree($this->repo);
  174.  
  175.                                 $node = new \stdClass;
  176.                                 $node->mode = 040000;
  177.                                 $node->name = $name;
  178.                                 $node->is_dir = true;
  179.  
  180.                                 $this->nodes[$node->name] = $node;
  181.                         }
  182.                         $pending = $subtree->updateNode($path, $mode, $object);
  183.  
  184.                         $subtree->rehash();
  185.                         $node->object = $subtree->getName();
  186.  
  187.                         $pending[] = $subtree;
  188.                         return $pending;
  189.                 }
  190.         }
  191.  
  192.         const TREEDIFF_A = 0x01;
  193.         const TREEDIFF_B = 0x02;
  194.  
  195.         const TREEDIFF_REMOVED = self::TREEDIFF_A;
  196.         const TREEDIFF_ADDED = self::TREEDIFF_B;
  197.         const TREEDIFF_CHANGED = 0x03;
  198.  
  199.         static public function treeDiff($a_tree, $b_tree) {
  200.                 $a_blobs = $a_tree ? $a_tree->listRecursive() : array();
  201.                 $b_blobs = $b_tree ? $b_tree->listRecursive() : array();
  202.  
  203.                 $a_files = array_keys($a_blobs);
  204.                 $b_files = array_keys($b_blobs);
  205.  
  206.                 $changes = array();
  207.  
  208.                 sort($a_files);
  209.                 sort($b_files);
  210.                 $a = $b = 0;
  211.                 while ($a < count($a_files) || $b < count($b_files)) {
  212.                         if ($a < count($a_files) && $b < count($b_files))
  213.                                 $cmp = strcmp($a_files[$a], $b_files[$b]);
  214.                         else
  215.                                 $cmp = 0;
  216.                         if ($b >= count($b_files) || $cmp < 0) {
  217.                                 $changes[$a_files[$a]] = self::TREEDIFF_REMOVED;
  218.                                 $a++;
  219.                         } else if ($a >= count($a_files) || $cmp > 0) {
  220.                                 $changes[$b_files[$b]] = self::TREEDIFF_ADDED;
  221.                                 $b++;
  222.                         } else {
  223.                                 if ($a_blobs[$a_files[$a]] != $b_blobs[$b_files[$b]])
  224.                                         $changes[$a_files[$a]] = self::TREEDIFF_CHANGED;
  225.  
  226.                                 $a++;
  227.                                 $b++;
  228.                         }
  229.                 }
  230.  
  231.                 return $changes;
  232.         }
  233. }
  234.