nextcloud/3rdparty/granite/git/tree.php

199 lines
5.6 KiB
PHP

<?php
/**
* Tree - provides a 'tree' object
*
* PHP version 5.3
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
namespace Granite\Git;
use \Granite\Git\Tree\Node as Node;
/**
* Tree represents a full tree object, with nodes pointing to other tree objects
* and file blobs
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
class Tree
{
/**
* The SHA-1 id of the requested tree
*/
private $sha;
/**
* The nodes/entries for the requested tree
*/
private $nodes = array();
/**
* The path to the repository
*/
private $path;
/**
* Reads a tree object by fetching the raw object
*
* @param string $path The path to the repository root
* @param string $sha The SHA-1 id of the requested object
*/
public function __construct($path, $sha = NULL, $dbg = FALSE)
{
$this->path = $path;
if ($sha !== NULL) {
$object = Object\Raw::factory($path, $sha);
$this->sha = $sha;
if ($object->type() !== Object\Raw::OBJ_TREE) {
throw new \InvalidArgumentException(
"The object $sha is not a tree, type is " . $object->type()
);
}
$content = $object->content();
file_put_contents('/tmp/tree_from_real_repo'.time(), $content);
$nodes = array();
for ($i = 0; $i < strlen($content); $i = $data_start + 21) {
$data_start = strpos($content, "\0", $i);
$info = substr($content, $i, $data_start-$i);
list($mode, $name) = explode(' ', $info, 2);
// Read the object SHA-1 id
$sha = bin2hex(substr($content, $data_start + 1, 20));
$this->nodes[$name] = new Node($name, $mode, $sha);
}
}
}
/**
* Returns an array of Tree and Granite\Git\Blob objects,
* representing subdirectories and files
*
* @return array Array of Tree and Granite\Git\Blob objects
*/
public function nodes($nodes = null)
{
if ($nodes == null) {
return $this->nodes;
}
$this->nodes = $nodes;
}
/**
* Adds a blob or a tree to the list of nodes
*
* @param string $name The basename (filename) of the blob or tree
* @param string $mode The mode of the blob or tree (see above)
* @param string $sha The SHA-1 id of the blob or tree to add
*/
public function add($name, $mode, $sha)
{
$this->nodes[$name] = new Node($name, $mode, $sha);
uasort($this->nodes, array($this, '_sort'));
}
public function write()
{
$sha = $this->sha();
$path = $this->path
. 'objects'
. DIRECTORY_SEPARATOR
. substr($sha, 0, 2)
. DIRECTORY_SEPARATOR
. substr($sha, 2);
// FIXME: currently writes loose objects only
if (file_exists($path)) {
return FALSE;
}
if (!is_dir(dirname($path))) {
mkdir(dirname($path), 0777, TRUE);
}
$loose = fopen($path, 'wb');
$data = $this->_raw();
$data = 'tree ' . strlen($data) . "\0" . $data;
$write = fwrite($loose, gzcompress($data));
fclose($loose);
return ($write !== FALSE);
}
/**
* Returns the SHA-1 id of the Tree
*
* @return string SHA-1 id of the Tree
*/
public function sha()
{
$data = $this->_raw();
$raw = 'tree ' . strlen($data) . "\0" . $data;
$this->sha = hash('sha1', $raw);
return $this->sha;
}
/**
* Generates the raw object content to be saved to disk
*/
public function _raw()
{
uasort($this->nodes, array($this, '_sort'));
$data = '';
foreach ($this->nodes as $node)
{
$data .= base_convert($node->mode(), 10, 8) . ' ' . $node->name() . "\0";
$data .= pack('H40', $node->sha());
}
file_put_contents('/tmp/tree_made'.time(), $data);
return $data;
}
/**
* Sorts the node entries in a tree, general sort method adapted from original
* Git C code (see @see tag below).
*
* @return 1, 0 or -1 if the first entry is greater than, the same as, or less
* than the second, respectively.
* @see https://github.com/gitster/git/blob/master/read-cache.c Around line 352,
* the `base_name_compare` function
*/
public function _sort(&$a, &$b)
{
$length = strlen($a->name()) < strlen($b->name()) ? strlen($a->name()) : strlen($b->name());
$cmp = strncmp($a->name(), $b->name(), $length);
if ($cmp) {
return $cmp;
}
$suffix1 = $a->name();
$suffix1 = (strlen($suffix1) > $length) ? $suffix1{$length} : FALSE;
$suffix2 = $b->name();
$suffix2 = (strlen($suffix2) > $length) ? $suffix2{$length} : FALSE;
if (!$suffix1 && $a->isDirectory()) {
$suffix1 = '/';
}
if (!$suffix2 && $b->isDirectory()) {
$suffix2 = '/';
}
if ($suffix1 < $suffix2) {
return -1;
} elseif ($suffix1 > $suffix2) {
return 1;
}
return 0;
}
}