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 | } |