Subversion Repositories oidplus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1192 daniel-mar 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
}