Files
php-git-server/lib/git_upload_pack.php
2026-08-16 20:45:30 +08:00

192 lines
5.7 KiB
PHP

<?php
function git_upload_pack_capabilities($git_path) {
$capabilities = array(
'multi_ack_detailed',
'no-done',
'side-band-64k',
'ofs-delta',
'thin-pack',
'include-tag',
'agent=php-git-server/1');
$head = resolve_ref($git_path, 'HEAD');
if ($head[0] !== NULL && strpos($head[0], 'refs/') === 0) {
$capabilities[] = 'symref=HEAD:'.$head[0];
}
return implode(' ', $capabilities);
}
function git_upload_pack_advertised_refs($git_path) {
$refs = array();
$head = resolve_ref($git_path, 'HEAD');
if ($head[1] !== NULL) {
$refs[] = array('HEAD', $head[1]);
}
foreach (get_repository_refs($git_path) as $ref) {
$refs[] = $ref;
}
return $refs;
}
function git_upload_pack_advertise_native($repository) {
$refs = git_upload_pack_advertised_refs($repository['path']);
if (empty($refs)) {
echo git_protocol_format_packet(
str_repeat('0', 40).' capabilities^{}'."\0".git_upload_pack_capabilities($repository['path'])."\n");
echo '0000';
return TRUE;
}
foreach ($refs as $index => $ref) {
$payload = $ref[1].' '.$ref[0];
if ($index === 0) {
$payload .= "\0".git_upload_pack_capabilities($repository['path']);
}
echo git_protocol_format_packet($payload."\n");
}
echo '0000';
return TRUE;
}
function git_upload_pack_parse_request($input) {
$request = array(
'wants' => array(),
'haves' => array(),
'capabilities' => array(),
'done' => FALSE);
$first_want = TRUE;
$want_phase_done = FALSE;
$complete = FALSE;
while (($packet = git_protocol_read_packet($input)) !== FALSE) {
if ($packet['type'] === 'flush') {
if ($want_phase_done && !empty($request['haves'])
&& isset($request['capabilities']['no-done'])) {
$complete = TRUE;
break;
}
$want_phase_done = TRUE;
continue;
}
if ($packet['type'] !== 'data') {
return FALSE;
}
$line = rtrim($packet['payload'], "\r\n");
if (preg_match('~^want ([0-9a-f]{40})(?: (.*))?$~D', $line, $matches)) {
$request['wants'][] = $matches[1];
if ($first_want && isset($matches[2]) && $matches[2] !== '') {
foreach (explode(' ', $matches[2]) as $capability) {
$request['capabilities'][$capability] = TRUE;
}
}
$first_want = FALSE;
} else if (preg_match('~^have ([0-9a-f]{40})$~D', $line, $matches)) {
$request['haves'][] = $matches[1];
} else if ($line === 'done') {
$request['done'] = TRUE;
$complete = TRUE;
break;
} else {
return FALSE;
}
}
if (!$complete || empty($request['wants'])) {
return FALSE;
}
return $request;
}
function git_upload_pack_send_sideband($channel, $buffer) {
$offset = 0;
$length = strlen($buffer);
while ($offset < $length) {
$chunk = substr($buffer, $offset, 65515);
echo git_protocol_format_packet(chr($channel).$chunk);
$offset += strlen($chunk);
}
}
function git_upload_pack_rpc_native($repository, $input) {
$request = git_upload_pack_parse_request($input);
if ($request === FALSE) {
return FALSE;
}
$store = git_object_store_create($repository['path']);
if ($store === FALSE) {
return FALSE;
}
$advertised_roots = array();
foreach (git_upload_pack_advertised_refs($repository['path']) as $ref) {
$advertised_roots[$ref[1]] = TRUE;
}
$advertised = git_object_store_collect($store, array_keys($advertised_roots));
if ($advertised === FALSE) {
return FALSE;
}
foreach ($request['wants'] as $want) {
if (!isset($advertised[$want])) {
return FALSE;
}
}
$objects = git_object_store_collect($store, $request['wants']);
if ($objects === FALSE) {
return FALSE;
}
if (isset($request['capabilities']['include-tag'])) {
foreach (get_repository_refs($repository['path']) as $ref) {
if (strpos($ref[0], 'refs/tags/') !== 0) {
continue;
}
$tag = git_object_store_read($store, $ref[1]);
if ($tag === FALSE || $tag['type'] !== 'tag') {
continue;
}
$links = git_object_store_links($tag);
if ($links !== FALSE && isset($objects[$links[0]])) {
$objects[$tag['oid']] = $tag;
}
}
}
$last_common = NULL;
foreach ($request['haves'] as $have) {
$common = git_object_store_collect($store, array($have));
if ($common === FALSE) {
continue;
}
$last_common = $have;
foreach ($common as $oid => $object) {
unset($objects[$oid]);
}
}
$pack = git_object_store_build_pack($objects);
if ($pack === FALSE) {
return FALSE;
}
if ($last_common === NULL) {
echo git_protocol_format_packet("NAK\n");
} else if (isset($request['capabilities']['multi_ack_detailed']) && !$request['done']) {
echo git_protocol_format_packet('ACK '.$last_common." common\n");
echo git_protocol_format_packet('ACK '.$last_common." ready\n");
echo git_protocol_format_packet("NAK\n");
echo git_protocol_format_packet('ACK '.$last_common."\n");
} else {
echo git_protocol_format_packet('ACK '.$last_common."\n");
}
if (isset($request['capabilities']['side-band-64k'])) {
git_upload_pack_send_sideband(1, $pack);
echo '0000';
} else {
echo $pack;
}
return TRUE;
}