summaryrefslogtreecommitdiffstats
path: root/3rdparty/granite/git/tree.php
blob: 2de722745320f36a811281d813631b6717f3a26e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
<?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;
    }

}