00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 class GitTreeError extends Exception {}
00022 class GitTreeInvalidPathError extends GitTreeError {}
00023
00024 require_once('git_object.class.php');
00025
00026 class GitTree extends GitObject
00027 {
00028 public $nodes = array();
00029
00030 public function __construct($repo)
00031 {
00032 parent::__construct($repo, Git::OBJ_TREE);
00033 }
00034
00035 public function _unserialize($data)
00036 {
00037 $this->nodes = array();
00038 $start = 0;
00039 while ($start < strlen($data))
00040 {
00041 $node = new stdClass;
00042
00043 $pos = strpos($data, "\0", $start);
00044 list($node->mode, $node->name) = explode(' ', substr($data, $start, $pos-$start), 2);
00045 $node->mode = intval($node->mode, 8);
00046 $node->is_dir = !!($node->mode & 040000);
00047 $node->object = substr($data, $pos+1, 20);
00048 $start = $pos+21;
00049
00050 $this->nodes[$node->name] = $node;
00051 }
00052 unset($data);
00053 }
00054
00055 protected static function nodecmp(&$a, &$b)
00056 {
00057 return strcmp($a->name, $b->name);
00058 }
00059
00060 public function _serialize()
00061 {
00062 $s = '';
00063
00064 usort($this->nodes, array('GitTree', 'nodecmp'));
00065 foreach ($this->nodes as $node)
00066 $s .= sprintf("%s %s\0%s", base_convert($node->mode, 10, 8), $node->name, $node->object);
00067 return $s;
00068 }
00069
00081 public function find($path)
00082 {
00083 if (!is_array($path))
00084 $path = explode('/', $path);
00085
00086 while ($path && !$path[0])
00087 array_shift($path);
00088 if (!$path)
00089 return $this->getName();
00090
00091 if (!isset($this->nodes[$path[0]]))
00092 return NULL;
00093 $cur = $this->nodes[$path[0]]->object;
00094
00095 array_shift($path);
00096 while ($path && !$path[0])
00097 array_shift($path);
00098
00099 if (!$path)
00100 return $cur;
00101 else
00102 {
00103 $cur = $this->repo->getObject($cur);
00104 if (!($cur instanceof GitTree))
00105 throw new GitTreeInvalidPathError;
00106 return $cur->find($path);
00107 }
00108 }
00109
00117 public function listRecursive()
00118 {
00119 $r = array();
00120
00121 foreach ($this->nodes as $node)
00122 {
00123 if ($node->is_dir)
00124 {
00125 $subtree = $this->repo->getObject($node->object);
00126 foreach ($subtree->listRecursive() as $entry => $blob)
00127 $r[$node->name . '/' . $entry] = $blob;
00128 }
00129 else
00130 $r[$node->name] = $node->object;
00131 }
00132
00133 return $r;
00134 }
00135
00151 public function updateNode($path, $mode, $object)
00152 {
00153 if (!is_array($path))
00154 $path = explode('/', $path);
00155 $name = array_shift($path);
00156 if (count($path) == 0)
00157 {
00158
00159 if ($mode)
00160 {
00161 $node = new stdClass;
00162 $node->mode = $mode;
00163 $node->name = $name;
00164 $node->object = $object;
00165 $node->is_dir = !!($mode & 040000);
00166
00167 $this->nodes[$node->name] = $node;
00168 }
00169 else
00170 unset($this->nodes[$name]);
00171
00172 return array();
00173 }
00174 else
00175 {
00176
00177 if (isset($this->nodes[$name]))
00178 {
00179 $node = $this->nodes[$name];
00180 if (!$node->is_dir)
00181 throw new GitTreeInvalidPathError;
00182 $subtree = clone $this->repo->getObject($node->object);
00183 }
00184 else
00185 {
00186
00187 $subtree = new GitTree($this->repo);
00188
00189 $node = new stdClass;
00190 $node->mode = 040000;
00191 $node->name = $name;
00192 $node->is_dir = TRUE;
00193
00194 $this->nodes[$node->name] = $node;
00195 }
00196 $pending = $subtree->updateNode($path, $mode, $object);
00197
00198 $subtree->rehash();
00199 $node->object = $subtree->getName();
00200
00201 $pending[] = $subtree;
00202 return $pending;
00203 }
00204 }
00205
00206 const TREEDIFF_A = 0x01;
00207 const TREEDIFF_B = 0x02;
00208
00209 const TREEDIFF_REMOVED = self::TREEDIFF_A;
00210 const TREEDIFF_ADDED = self::TREEDIFF_B;
00211 const TREEDIFF_CHANGED = 0x03;
00212
00213 static public function treeDiff($a_tree, $b_tree)
00214 {
00215 $a_blobs = $a_tree ? $a_tree->listRecursive() : array();
00216 $b_blobs = $b_tree ? $b_tree->listRecursive() : array();
00217
00218 $a_files = array_keys($a_blobs);
00219 $b_files = array_keys($b_blobs);
00220
00221 $changes = array();
00222
00223 sort($a_files);
00224 sort($b_files);
00225 $a = $b = 0;
00226 while ($a < count($a_files) || $b < count($b_files))
00227 {
00228 if ($a < count($a_files) && $b < count($b_files))
00229 $cmp = strcmp($a_files[$a], $b_files[$b]);
00230 else
00231 $cmp = 0;
00232 if ($b >= count($b_files) || $cmp < 0)
00233 {
00234 $changes[$a_files[$a]] = self::TREEDIFF_REMOVED;
00235 $a++;
00236 }
00237 else if ($a >= count($a_files) || $cmp > 0)
00238 {
00239 $changes[$b_files[$b]] = self::TREEDIFF_ADDED;
00240 $b++;
00241 }
00242 else
00243 {
00244 if ($a_blobs[$a_files[$a]] != $b_blobs[$b_files[$b]])
00245 $changes[$a_files[$a]] = self::TREEDIFF_CHANGED;
00246
00247 $a++;
00248 $b++;
00249 }
00250 }
00251
00252 return $changes;
00253 }
00254 };
00255 ?>