00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 require_once('binary.class.php');
00022 require_once('git_object.class.php');
00023 require_once('git_blob.class.php');
00024 require_once('git_commit.class.php');
00025 require_once('git_commit_stamp.class.php');
00026 require_once('git_tree.class.php');
00027
00035 function sha1_bin($hex)
00036 {
00037 return pack('H40', $hex);
00038 }
00039
00047 function sha1_hex($bin)
00048 {
00049 return bin2hex($bin);
00050 }
00051
00052 class Git
00053 {
00054 public $dir;
00055
00056 const OBJ_NONE = 0;
00057 const OBJ_COMMIT = 1;
00058 const OBJ_TREE = 2;
00059 const OBJ_BLOB = 3;
00060 const OBJ_TAG = 4;
00061 const OBJ_OFS_DELTA = 6;
00062 const OBJ_REF_DELTA = 7;
00063
00064 static public function getTypeID($name)
00065 {
00066 if ($name == 'commit')
00067 return Git::OBJ_COMMIT;
00068 else if ($name == 'tree')
00069 return Git::OBJ_TREE;
00070 else if ($name == 'blob')
00071 return Git::OBJ_BLOB;
00072 else if ($name == 'tag')
00073 return Git::OBJ_TAG;
00074 throw new Exception(sprintf('unknown type name: %s', $name));
00075 }
00076
00077 static public function getTypeName($type)
00078 {
00079 if ($type == Git::OBJ_COMMIT)
00080 return 'commit';
00081 else if ($type == Git::OBJ_TREE)
00082 return 'tree';
00083 else if ($type == Git::OBJ_BLOB)
00084 return 'blob';
00085 else if ($type == Git::OBJ_TAG)
00086 return 'tag';
00087 throw new Exception(sprintf('no string representation of type %d', $type));
00088 }
00089
00090 public function __construct($dir)
00091 {
00092 $this->dir = $dir;
00093
00094 $this->packs = array();
00095 $dh = opendir(sprintf('%s/objects/pack', $this->dir));
00096 while (($entry = readdir($dh)) !== FALSE)
00097 if (preg_match('#^pack-([0-9a-fA-F]{40})\.idx$#', $entry, $m))
00098 $this->packs[] = sha1_bin($m[1]);
00099 }
00100
00107 protected function readFanout($f, $object_name, $offset)
00108 {
00109 if ($object_name{0} == "\x00")
00110 {
00111 $cur = 0;
00112 fseek($f, $offset);
00113 $after = Binary::fuint32($f);
00114 }
00115 else
00116 {
00117 fseek($f, $offset + (ord($object_name{0}) - 1)*4);
00118 $cur = Binary::fuint32($f);
00119 $after = Binary::fuint32($f);
00120 }
00121
00122 return array($cur, $after);
00123 }
00124
00132 protected function findPackedObject($object_name)
00133 {
00134 foreach ($this->packs as $pack_name)
00135 {
00136 $index = fopen(sprintf('%s/objects/pack/pack-%s.idx', $this->dir, sha1_hex($pack_name)), 'rb');
00137 flock($index, LOCK_SH);
00138
00139
00140 $magic = fread($index, 4);
00141 if ($magic != "\xFFtOc")
00142 {
00143
00144
00145 list($cur, $after) = $this->readFanout($index, $object_name, 0);
00146
00147 $n = $after-$cur;
00148 if ($n == 0)
00149 continue;
00150
00151
00152
00153
00154 fseek($index, 4*256 + 24*$cur);
00155 for ($i = 0; $i < $n; $i++)
00156 {
00157 $off = Binary::fuint32($index);
00158 $name = fread($index, 20);
00159 if ($name == $object_name)
00160 {
00161
00162 fclose($index);
00163 return array($pack_name, $off);
00164 }
00165 }
00166 }
00167 else
00168 {
00169
00170 $version = Binary::fuint32($index);
00171 if ($version == 2)
00172 {
00173 list($cur, $after) = $this->readFanout($index, $object_name, 8);
00174
00175 if ($cur == $after)
00176 continue;
00177
00178 fseek($index, 8 + 4*255);
00179 $total_objects = Binary::fuint32($index);
00180
00181
00182 fseek($index, 8 + 4*256 + 20*$cur);
00183 for ($i = $cur; $i < $after; $i++)
00184 {
00185 $name = fread($index, 20);
00186 if ($name == $object_name)
00187 break;
00188 }
00189 if ($i == $after)
00190 continue;
00191
00192 fseek($index, 8 + 4*256 + 24*$total_objects + 4*$i);
00193 $off = Binary::fuint32($index);
00194 if ($off & 0x80000000)
00195 {
00196
00197
00198
00199 throw new Exception('64-bit packfiles offsets not implemented');
00200 }
00201
00202 fclose($index);
00203 return array($pack_name, $off);
00204 }
00205 else
00206 throw new Exception('unsupported pack index format');
00207 }
00208 fclose($index);
00209 }
00210
00211 return NULL;
00212 }
00213
00221 protected function applyDelta($delta, $base)
00222 {
00223 $pos = 0;
00224
00225 $base_size = Binary::git_varint($delta, &$pos);
00226 $result_size = Binary::git_varint($delta, &$pos);
00227
00228 $r = '';
00229 while ($pos < strlen($delta))
00230 {
00231 $opcode = ord($delta{$pos++});
00232 if ($opcode & 0x80)
00233 {
00234
00235 $off = 0;
00236 if ($opcode & 0x01) $off = ord($delta{$pos++});
00237 if ($opcode & 0x02) $off |= ord($delta{$pos++}) << 8;
00238 if ($opcode & 0x04) $off |= ord($delta{$pos++}) << 16;
00239 if ($opcode & 0x08) $off |= ord($delta{$pos++}) << 24;
00240 $len = 0;
00241 if ($opcode & 0x10) $len = ord($delta{$pos++});
00242 if ($opcode & 0x20) $len |= ord($delta{$pos++}) << 8;
00243 if ($opcode & 0x40) $len |= ord($delta{$pos++}) << 16;
00244 $r .= substr($base, $off, $len);
00245 }
00246 else
00247 {
00248
00249 $r .= substr($delta, $pos, $opcode);
00250 $pos += $opcode;
00251 }
00252 }
00253 return $r;
00254 }
00255
00264 protected function unpackObject($pack, $object_offset)
00265 {
00266 fseek($pack, $object_offset);
00267
00268
00269 $c = ord(fgetc($pack));
00270 $type = ($c >> 4) & 0x07;
00271 $size = $c & 0x0F;
00272 for ($i = 4; $c & 0x80; $i += 7)
00273 {
00274 $c = ord(fgetc($pack));
00275 $size |= ($c << $i);
00276 }
00277
00278
00279 if ($type == Git::OBJ_COMMIT || $type == Git::OBJ_TREE || $type == Git::OBJ_BLOB || $type == Git::OBJ_TAG)
00280 {
00281
00282
00283
00284
00285
00286
00287
00288
00289 $data = gzuncompress(fread($pack, $size+512), $size);
00290 }
00291 else if ($type == Git::OBJ_OFS_DELTA)
00292 {
00293
00294 $buf = fread($pack, $size+512+20);
00295
00296
00297
00298
00299
00300
00301 $pos = 0;
00302 $offset = -1;
00303 do
00304 {
00305 $offset++;
00306 $c = ord($buf{$pos++});
00307 $offset = ($offset << 7) + ($c & 0x7F);
00308 }
00309 while ($c & 0x80);
00310
00311 $delta = gzuncompress(substr($buf, $pos), $size);
00312 unset($buf);
00313
00314 $base_offset = $object_offset - $offset;
00315 assert($base_offset >= 0);
00316 list($type, $base) = $this->unpackObject($pack, $base_offset);
00317
00318 $data = $this->applyDelta($delta, $base);
00319 }
00320 else if ($type == Git::OBJ_REF_DELTA)
00321 {
00322 $base_name = fread($pack, 20);
00323 list($type, $base) = $this->getRawObject($base_name);
00324
00325
00326 $delta = gzuncompress(fread($pack, $size+512), $size);
00327
00328 $data = $this->applyDelta($delta, $base);
00329 }
00330 else
00331 throw new Exception(sprintf('object of unknown type %d', $type));
00332
00333 return array($type, $data);
00334 }
00335
00345 protected function getRawObject($object_name)
00346 {
00347 static $cache = array();
00348
00349
00350 if (isset($cache[$object_name]))
00351 return $cache[$object_name];
00352 $sha1 = sha1_hex($object_name);
00353 $path = sprintf('%s/objects/%s/%s', $this->dir, substr($sha1, 0, 2), substr($sha1, 2));
00354 if (file_exists($path))
00355 {
00356 list($hdr, $object_data) = explode("\0", gzuncompress(file_get_contents($path)), 2);
00357
00358 sscanf($hdr, "%s %d", $type, $object_size);
00359 $object_type = Git::getTypeID($type);
00360 $r = array($object_type, $object_data);
00361 }
00362 else if ($x = $this->findPackedObject($object_name))
00363 {
00364 list($pack_name, $object_offset) = $x;
00365
00366 $pack = fopen(sprintf('%s/objects/pack/pack-%s.pack', $this->dir, sha1_hex($pack_name)), 'rb');
00367 flock($pack, LOCK_SH);
00368
00369
00370 $magic = fread($pack, 4);
00371 $version = Binary::fuint32($pack);
00372 if ($magic != 'PACK' || $version != 2)
00373 throw new Exception('unsupported pack format');
00374
00375 $r = $this->unpackObject($pack, $object_offset);
00376 fclose($pack);
00377 }
00378 else
00379 throw new Exception(sprintf('object not found: %s', sha1_hex($object_name)));
00380 $cache[$object_name] = $r;
00381 return $r;
00382 }
00383
00390 public function getObject($name)
00391 {
00392 list($type, $data) = $this->getRawObject($name);
00393 $object = GitObject::create($this, $type);
00394 $object->unserialize($data);
00395 assert($name == $object->getName());
00396 return $object;
00397 }
00398
00405 public function getTip($branch='master')
00406 {
00407 $subpath = sprintf('refs/heads/%s', $branch);
00408 $path = sprintf('%s/%s', $this->dir, $subpath);
00409 if (file_exists($path))
00410 return sha1_bin(file_get_contents($path));
00411 $path = sprintf('%s/packed-refs', $this->dir);
00412 if (file_exists($path))
00413 {
00414 $head = NULL;
00415 $f = fopen($path, 'rb');
00416 flock($f, LOCK_SH);
00417 while ($head === NULL && ($line = fgets($f)) !== FALSE)
00418 {
00419 if ($line{0} == '#')
00420 continue;
00421 $parts = explode(' ', trim($line));
00422 if (count($parts) == 2 && $parts[1] == $subpath)
00423 $head = sha1_bin($parts[0]);
00424 }
00425 fclose($f);
00426 if ($head !== NULL)
00427 return $head;
00428 }
00429 throw new Exception(sprintf('no such branch: %s', $branch));
00430 }
00431 };
00432 ?>