remove git excuteable need
This commit is contained in:
@@ -21,7 +21,7 @@ This project serves configured Git repositories through PHP. It supports:
|
|||||||
`index.php` is the application entry point. Protocol responsibilities are split
|
`index.php` is the application entry point. Protocol responsibilities are split
|
||||||
into `operations/clone.php`, `operations/pull.php`, `operations/push.php`,
|
into `operations/clone.php`, `operations/pull.php`, `operations/push.php`,
|
||||||
`operations/branch.php` and `operations/tag.php`; shared routing, repository,
|
`operations/branch.php` and `operations/tag.php`; shared routing, repository,
|
||||||
HTTP and Git process code is under `lib/`.
|
HTTP and pure-PHP Git protocol code is under `lib/`.
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
@@ -30,23 +30,21 @@ Requirements
|
|||||||
- Apache with `mod_rewrite` and `.htaccess` enabled for normal deployment.
|
- Apache with `mod_rewrite` and `.htaccess` enabled for normal deployment.
|
||||||
- MySQL 5.7+/MariaDB 10.2+ and PHP PDO MySQL (`pdo_mysql`) when account
|
- MySQL 5.7+/MariaDB 10.2+ and PHP PDO MySQL (`pdo_mysql`) when account
|
||||||
authentication is enabled.
|
authentication is enabled.
|
||||||
- PHP zlib and hash extensions for the native Git protocol implementation.
|
- PHP zlib and hash extensions are required for the Smart HTTP implementation.
|
||||||
- Git and PHP `proc_open` are optional; when available, Git remains the Smart
|
|
||||||
HTTP backend for full protocol and hook compatibility.
|
|
||||||
- Read access to published repositories; write access is also required for push
|
- Read access to published repositories; write access is also required for push
|
||||||
and for the managed repository directory when home-page creation is enabled.
|
and for the managed repository directory when home-page creation is enabled.
|
||||||
|
|
||||||
If Git or `proc_open` is unavailable, the native PHP backend supports ordinary
|
The pure-PHP backend supports ordinary SHA-1 clone, fetch, pull and push,
|
||||||
SHA-1 clone, fetch, pull and push, including deltas, branches and tags. It does
|
including deltas, branches and tags. It does not currently support SHA-256
|
||||||
not currently support SHA-256 repositories, shallow or filtered fetches, signed
|
repositories, shallow or filtered fetches, signed push certificates, Git hooks
|
||||||
push certificates, Git hooks or protocol v2-only features.
|
or protocol v2-only features.
|
||||||
Push is disabled by default and should be enabled only behind HTTPS.
|
Push is disabled by default and should be enabled only behind HTTPS.
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
Copy `config.php.sample` to `config.php`, import `schema.mysql.sql`, then
|
Copy `config.php.sample` to `config.php`, import `schema.mysql.sql`, then
|
||||||
configure `$url_base`, `$git_executable`, `$auth`, `$repos` and optionally
|
configure `$url_base`, `$auth`, `$repos` and optionally
|
||||||
`$managed_repositories`. A writable repository can be configured as:
|
`$managed_repositories`. A writable repository can be configured as:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
|
|||||||
@@ -6,11 +6,6 @@
|
|||||||
$url_base = '';
|
$url_base = '';
|
||||||
|
|
||||||
|
|
||||||
/* Git executable used by Smart HTTP when available. If it is unavailable,
|
|
||||||
* supported SHA-1 Smart HTTP operations run through the native PHP backend. */
|
|
||||||
|
|
||||||
$git_executable = 'git';
|
|
||||||
|
|
||||||
/* Interface language: 'en', 'zh', or empty to follow the browser. */
|
/* Interface language: 'en', 'zh', or empty to follow the browser. */
|
||||||
$language = '';
|
$language = '';
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,6 @@ function home_creation_result_notice($result) {
|
|||||||
case 'already_exists': return array(409, 'error', t('notice.repository_exists', array('name' => $name)));
|
case 'already_exists': return array(409, 'error', t('notice.repository_exists', array('name' => $name)));
|
||||||
case 'create_busy': return array(409, 'error', t('notice.repository_create_busy'));
|
case 'create_busy': return array(409, 'error', t('notice.repository_create_busy'));
|
||||||
case 'root_unavailable': return array(503, 'error', t('notice.repository_root_unavailable'));
|
case 'root_unavailable': return array(503, 'error', t('notice.repository_root_unavailable'));
|
||||||
case 'git_unavailable': return array(503, 'error', t('notice.repository_git_unavailable'));
|
|
||||||
case 'metadata_unavailable': return array(503, 'error', t('notice.repository_metadata_unsaved'));
|
case 'metadata_unavailable': return array(503, 'error', t('notice.repository_metadata_unsaved'));
|
||||||
default: return array(500, 'error', t('notice.repository_create_failed'));
|
default: return array(500, 'error', t('notice.repository_create_failed'));
|
||||||
}
|
}
|
||||||
@@ -297,7 +296,6 @@ function home_create_repository(
|
|||||||
$result = array('status' => 'already_exists', 'name' => $name);
|
$result = array('status' => 'already_exists', 'name' => $name);
|
||||||
} else {
|
} else {
|
||||||
$result = git_service_create_managed_repository(
|
$result = git_service_create_managed_repository(
|
||||||
$application,
|
|
||||||
$configuration,
|
$configuration,
|
||||||
$value,
|
$value,
|
||||||
$owner['id'],
|
$owner['id'],
|
||||||
@@ -918,9 +916,6 @@ if (!isset($repos) || !is_array($repos)) {
|
|||||||
send_error(500, 'Internal Server Error', 'The repository configuration is invalid.');
|
send_error(500, 'Internal Server Error', 'The repository configuration is invalid.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isset($git_executable)) {
|
|
||||||
$git_executable = 'git';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($auth)) {
|
if (!isset($auth)) {
|
||||||
$auth = array();
|
$auth = array();
|
||||||
@@ -938,9 +933,7 @@ if (!is_array($managed_repositories)) {
|
|||||||
send_error(500, 'Internal Server Error', 'The managed repository configuration is invalid.');
|
send_error(500, 'Internal Server Error', 'The managed repository configuration is invalid.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$application = array(
|
$application = array('push_ref_rules' => array());
|
||||||
'git_executable' => $git_executable,
|
|
||||||
'push_ref_rules' => array());
|
|
||||||
$services = array();
|
$services = array();
|
||||||
|
|
||||||
register_branch_operation($application);
|
register_branch_operation($application);
|
||||||
|
|||||||
-11
@@ -121,10 +121,6 @@ function install_validate($input) {
|
|||||||
&& !preg_match('~^/[A-Za-z0-9._~/-]*[A-Za-z0-9._~-]$~D', $input['url_base'])) {
|
&& !preg_match('~^/[A-Za-z0-9._~/-]*[A-Za-z0-9._~-]$~D', $input['url_base'])) {
|
||||||
$errors[] = t('install.error_url_base');
|
$errors[] = t('install.error_url_base');
|
||||||
}
|
}
|
||||||
if ($input['git_executable'] === '' || strlen($input['git_executable']) > 255
|
|
||||||
|| preg_match('~[\x00-\x1F\x7F]~', $input['git_executable'])) {
|
|
||||||
$errors[] = t('install.error_git_executable');
|
|
||||||
}
|
|
||||||
if (!install_host_is_valid($input['db_host'])) {
|
if (!install_host_is_valid($input['db_host'])) {
|
||||||
$errors[] = t('install.error_db_host');
|
$errors[] = t('install.error_db_host');
|
||||||
}
|
}
|
||||||
@@ -223,7 +219,6 @@ function install_config_contents($input) {
|
|||||||
return "<?php\n\n"
|
return "<?php\n\n"
|
||||||
."/* Generated by install.php. See config.php.sample for every option. */\n\n"
|
."/* Generated by install.php. See config.php.sample for every option. */\n\n"
|
||||||
."\$url_base = ".var_export($input['url_base'], TRUE).";\n"
|
."\$url_base = ".var_export($input['url_base'], TRUE).";\n"
|
||||||
."\$git_executable = ".var_export($input['git_executable'], TRUE).";\n"
|
|
||||||
."\$language = ".var_export($input['language'], TRUE).";\n\n"
|
."\$language = ".var_export($input['language'], TRUE).";\n\n"
|
||||||
."\$auth = array(\n"
|
."\$auth = array(\n"
|
||||||
." 'enabled' => TRUE,\n"
|
." 'enabled' => TRUE,\n"
|
||||||
@@ -382,10 +377,6 @@ function install_send_form($input, $errors) {
|
|||||||
.'<input id="url_base" name="url_base" type="text" value="'
|
.'<input id="url_base" name="url_base" type="text" value="'
|
||||||
.install_escape($input['url_base']).'" placeholder="/php-git-server">'
|
.install_escape($input['url_base']).'" placeholder="/php-git-server">'
|
||||||
.'<p class="hint">'.t('install.hint_url_base').'</p></div>' ."\n";
|
.'<p class="hint">'.t('install.hint_url_base').'</p></div>' ."\n";
|
||||||
echo '<div><label for="git_executable">'.install_escape(t('install.label_git_executable')).'</label>'
|
|
||||||
.'<input id="git_executable" name="git_executable" type="text" value="'
|
|
||||||
.install_escape($input['git_executable']).'" required>'
|
|
||||||
.'<p class="hint">'.t('install.hint_git_executable').'</p></div>' ."\n";
|
|
||||||
echo '<div><label for="language">'.install_escape(t('install.label_language')).'</label>'
|
echo '<div><label for="language">'.install_escape(t('install.label_language')).'</label>'
|
||||||
.'<select id="language" name="language">'
|
.'<select id="language" name="language">'
|
||||||
.'<option value=""'.($input['language'] === '' ? ' selected' : '').'>'
|
.'<option value=""'.($input['language'] === '' ? ' selected' : '').'>'
|
||||||
@@ -489,7 +480,6 @@ if ($method !== 'GET' && $method !== 'HEAD' && $method !== 'POST') {
|
|||||||
|
|
||||||
$input = array(
|
$input = array(
|
||||||
'url_base' => rtrim(str_replace('\\', '/', dirname(install_page_url())), '/'),
|
'url_base' => rtrim(str_replace('\\', '/', dirname(install_page_url())), '/'),
|
||||||
'git_executable' => 'git',
|
|
||||||
'registration_enabled' => TRUE,
|
'registration_enabled' => TRUE,
|
||||||
'session_cookie_secure' => FALSE,
|
'session_cookie_secure' => FALSE,
|
||||||
'language' => '',
|
'language' => '',
|
||||||
@@ -523,7 +513,6 @@ if ($expected_token === FALSE
|
|||||||
|
|
||||||
$input = array(
|
$input = array(
|
||||||
'url_base' => rtrim(trim(install_post_value('url_base')), '/'),
|
'url_base' => rtrim(trim(install_post_value('url_base')), '/'),
|
||||||
'git_executable' => trim(install_post_value('git_executable')),
|
|
||||||
'registration_enabled' => install_post_checked('registration_enabled'),
|
'registration_enabled' => install_post_checked('registration_enabled'),
|
||||||
'session_cookie_secure' => install_post_checked('session_cookie_secure'),
|
'session_cookie_secure' => install_post_checked('session_cookie_secure'),
|
||||||
'language' => install_post_value('language'),
|
'language' => install_post_value('language'),
|
||||||
|
|||||||
+11
-306
@@ -1,32 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
function git_service_executable_available($application) {
|
|
||||||
if (!isset($application['git_executable'])
|
|
||||||
|| !is_string($application['git_executable'])
|
|
||||||
|| $application['git_executable'] === '') {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$executable = $application['git_executable'];
|
|
||||||
if (strpos($executable, DIRECTORY_SEPARATOR) !== FALSE) {
|
|
||||||
return is_file($executable) && is_executable($executable);
|
|
||||||
}
|
|
||||||
|
|
||||||
$path = getenv('PATH');
|
|
||||||
if ($path === FALSE) {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (explode(PATH_SEPARATOR, $path) as $directory) {
|
|
||||||
$candidate = rtrim($directory, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$executable;
|
|
||||||
if (is_file($candidate) && is_executable($candidate)) {
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
function git_service_native_available() {
|
function git_service_native_available() {
|
||||||
return function_exists('inflate_init')
|
return function_exists('inflate_init')
|
||||||
&& function_exists('inflate_add')
|
&& function_exists('inflate_add')
|
||||||
@@ -72,7 +45,7 @@ function git_service_update_unborn_head($git_path, $updated_refs) {
|
|||||||
=== strlen($new_contents);
|
=== strlen($new_contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
function git_service_init_bare_repository_with_php($path) {
|
function git_service_init_bare_repository($path) {
|
||||||
$directories = array(
|
$directories = array(
|
||||||
'',
|
'',
|
||||||
'/branches',
|
'/branches',
|
||||||
@@ -105,60 +78,7 @@ function git_service_init_bare_repository_with_php($path) {
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
function git_service_init_bare_repository($application, $path) {
|
|
||||||
if (!function_exists('proc_open') || !git_service_executable_available($application)) {
|
|
||||||
return git_service_init_bare_repository_with_php($path);
|
|
||||||
}
|
|
||||||
|
|
||||||
$error = @tmpfile();
|
|
||||||
if ($error === FALSE) {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$descriptor_spec = array(
|
|
||||||
0 => array('file', '/dev/null', 'r'),
|
|
||||||
1 => array('file', '/dev/null', 'w'),
|
|
||||||
2 => $error);
|
|
||||||
$pipes = array();
|
|
||||||
$command = array(
|
|
||||||
$application['git_executable'],
|
|
||||||
'init',
|
|
||||||
'--bare',
|
|
||||||
'--quiet',
|
|
||||||
$path);
|
|
||||||
$request = array('git_protocol' => NULL, 'user' => get_authenticated_user());
|
|
||||||
$process = @proc_open(
|
|
||||||
$command,
|
|
||||||
$descriptor_spec,
|
|
||||||
$pipes,
|
|
||||||
dirname($path),
|
|
||||||
git_service_environment($request));
|
|
||||||
|
|
||||||
if (!is_resource($process)) {
|
|
||||||
fclose($error);
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$exit_code = proc_close($process);
|
|
||||||
if ($exit_code !== 0) {
|
|
||||||
rewind($error);
|
|
||||||
$message = stream_get_contents($error);
|
|
||||||
error_log(
|
|
||||||
'Git repository initialization failed for '.$path.' with exit code '
|
|
||||||
.$exit_code.': '.trim($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose($error);
|
|
||||||
if ($exit_code !== 0) {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$head = "ref: refs/heads/main\n";
|
|
||||||
return @file_put_contents($path.'/HEAD', $head, LOCK_EX) === strlen($head);
|
|
||||||
}
|
|
||||||
|
|
||||||
function git_service_create_managed_repository(
|
function git_service_create_managed_repository(
|
||||||
$application,
|
|
||||||
$configuration,
|
$configuration,
|
||||||
$value,
|
$value,
|
||||||
$owner_user_id,
|
$owner_user_id,
|
||||||
@@ -213,7 +133,7 @@ function git_service_create_managed_repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
$temporary_path = $root.DIRECTORY_SEPARATOR.'.create-'.$suffix.'.tmp';
|
$temporary_path = $root.DIRECTORY_SEPARATOR.'.create-'.$suffix.'.tmp';
|
||||||
if (!git_service_init_bare_repository($application, $temporary_path)
|
if (!git_service_init_bare_repository($temporary_path)
|
||||||
|| !managed_repository_is_bare($temporary_path)) {
|
|| !managed_repository_is_bare($temporary_path)) {
|
||||||
return array('status' => 'create_failed', 'name' => $name);
|
return array('status' => 'create_failed', 'name' => $name);
|
||||||
}
|
}
|
||||||
@@ -255,212 +175,8 @@ function git_service_create_managed_repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function git_service_is_protocol_v2($request) {
|
|
||||||
if ($request['git_protocol'] === NULL) {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return preg_match('~(?:^|:)version=2(?:$|:)~', $request['git_protocol']) === 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function git_service_environment($request) {
|
|
||||||
$environment = getenv();
|
|
||||||
if (!is_array($environment)) {
|
|
||||||
$environment = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
unset($environment['GIT_DIR']);
|
|
||||||
unset($environment['GIT_WORK_TREE']);
|
|
||||||
unset($environment['GIT_PROTOCOL']);
|
|
||||||
|
|
||||||
if ($request['git_protocol'] !== NULL
|
|
||||||
&& strlen($request['git_protocol']) <= 1024
|
|
||||||
&& strpos($request['git_protocol'], "\0") === FALSE
|
|
||||||
&& strpos($request['git_protocol'], "\n") === FALSE
|
|
||||||
&& strpos($request['git_protocol'], "\r") === FALSE) {
|
|
||||||
$environment['GIT_PROTOCOL'] = $request['git_protocol'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request['user'] !== NULL) {
|
|
||||||
$environment['REMOTE_USER'] = $request['user'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($_SERVER['REMOTE_ADDR'])) {
|
|
||||||
$environment['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($_SERVER['HTTP_USER_AGENT'])) {
|
|
||||||
$environment['GIT_HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $environment;
|
|
||||||
}
|
|
||||||
|
|
||||||
function git_service_command($application, $service, $repository, $advertise) {
|
|
||||||
if ($service === 'git-upload-pack') {
|
|
||||||
$subcommand = 'upload-pack';
|
|
||||||
} else if ($service === 'git-receive-pack') {
|
|
||||||
$subcommand = 'receive-pack';
|
|
||||||
} else {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$command = array(
|
|
||||||
$application['git_executable'],
|
|
||||||
$subcommand,
|
|
||||||
'--stateless-rpc');
|
|
||||||
|
|
||||||
if ($subcommand === 'upload-pack') {
|
|
||||||
$command[] = '--strict';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($advertise) {
|
|
||||||
$command[] = '--advertise-refs';
|
|
||||||
}
|
|
||||||
|
|
||||||
$command[] = $repository['path'];
|
|
||||||
return $command;
|
|
||||||
}
|
|
||||||
|
|
||||||
function git_service_pump_process($pipes, $input) {
|
|
||||||
stream_set_blocking($pipes[0], FALSE);
|
|
||||||
stream_set_blocking($pipes[1], FALSE);
|
|
||||||
|
|
||||||
$input_done = $input === NULL;
|
|
||||||
$input_buffer = '';
|
|
||||||
$stdin_open = TRUE;
|
|
||||||
$stdout_open = TRUE;
|
|
||||||
|
|
||||||
while ($stdin_open || $stdout_open) {
|
|
||||||
if (!$input_done && strlen($input_buffer) < 65536) {
|
|
||||||
$chunk = fread($input, 65536);
|
|
||||||
if ($chunk === FALSE) {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($chunk !== '') {
|
|
||||||
$input_buffer .= $chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (feof($input)) {
|
|
||||||
$input_done = TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($stdin_open && $input_done && $input_buffer === '') {
|
|
||||||
fclose($pipes[0]);
|
|
||||||
$stdin_open = FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$read = $stdout_open ? array($pipes[1]) : array();
|
|
||||||
$write = $stdin_open && $input_buffer !== '' ? array($pipes[0]) : array();
|
|
||||||
$except = NULL;
|
|
||||||
|
|
||||||
if (empty($read) && empty($write)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$ready = @stream_select($read, $write, $except, 30);
|
|
||||||
if ($ready === FALSE) {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($write)) {
|
|
||||||
$written = fwrite($pipes[0], $input_buffer);
|
|
||||||
if ($written === FALSE) {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
if ($written > 0) {
|
|
||||||
$input_buffer = (string) substr($input_buffer, $written);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($read)) {
|
|
||||||
$output = fread($pipes[1], 65536);
|
|
||||||
if ($output === FALSE) {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($output !== '') {
|
|
||||||
echo $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (feof($pipes[1])) {
|
|
||||||
fclose($pipes[1]);
|
|
||||||
$stdout_open = FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
function git_service_run(
|
|
||||||
$application,
|
|
||||||
$repository,
|
|
||||||
$request,
|
|
||||||
$service,
|
|
||||||
$advertise,
|
|
||||||
$input=NULL,
|
|
||||||
$prefix='') {
|
|
||||||
if (!function_exists('proc_open') || !git_service_executable_available($application)) {
|
|
||||||
send_error(503, 'Service Unavailable', 'Git Smart HTTP is not available.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$command = git_service_command($application, $service, $repository, $advertise);
|
|
||||||
if ($command === FALSE) {
|
|
||||||
send_error(403, 'Forbidden', 'Unsupported Git service.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$error = @tmpfile();
|
|
||||||
if ($error === FALSE) {
|
|
||||||
send_error(500, 'Internal Server Error', 'Unable to create a Git error stream.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$descriptor_spec = array(
|
|
||||||
0 => array('pipe', 'r'),
|
|
||||||
1 => array('pipe', 'w'),
|
|
||||||
2 => $error);
|
|
||||||
$pipes = array();
|
|
||||||
$process = @proc_open(
|
|
||||||
$command,
|
|
||||||
$descriptor_spec,
|
|
||||||
$pipes,
|
|
||||||
dirname($repository['path']),
|
|
||||||
git_service_environment($request));
|
|
||||||
|
|
||||||
if (!is_resource($process)) {
|
|
||||||
fclose($error);
|
|
||||||
send_error(503, 'Service Unavailable', 'Unable to start the Git service.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($prefix !== '') {
|
|
||||||
echo $prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stream_succeeded = git_service_pump_process($pipes, $input);
|
|
||||||
foreach ($pipes as $pipe) {
|
|
||||||
if (is_resource($pipe)) {
|
|
||||||
fclose($pipe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$exit_code = proc_close($process);
|
|
||||||
if (!$stream_succeeded || $exit_code !== 0) {
|
|
||||||
rewind($error);
|
|
||||||
$message = stream_get_contents($error);
|
|
||||||
error_log(
|
|
||||||
'Git service '.$service.' failed for '.$repository['url'].' with exit code '
|
|
||||||
.$exit_code.': '.trim($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose($error);
|
|
||||||
return $stream_succeeded && $exit_code === 0 ? 0 : $exit_code;
|
|
||||||
}
|
|
||||||
|
|
||||||
function git_service_advertise($application, $repository, $request, $service) {
|
function git_service_advertise($application, $repository, $request, $service) {
|
||||||
if ((!function_exists('proc_open') || !git_service_executable_available($application))
|
if (!git_service_native_available()) {
|
||||||
&& !git_service_native_available()) {
|
|
||||||
send_error(503, 'Service Unavailable', 'The native Git service requires PHP zlib and hash support.');
|
send_error(503, 'Service Unavailable', 'The native Git service requires PHP zlib and hash support.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,34 +184,25 @@ function git_service_advertise($application, $repository, $request, $service) {
|
|||||||
header('Content-Type: application/x-'.$service.'-advertisement');
|
header('Content-Type: application/x-'.$service.'-advertisement');
|
||||||
header('X-Content-Type-Options: nosniff');
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
|
||||||
if ((!function_exists('proc_open') || !git_service_executable_available($application))
|
echo format_packet_line('# service='.$service."\n").'0000';
|
||||||
&& $service === 'git-upload-pack') {
|
if ($service === 'git-upload-pack') {
|
||||||
echo format_packet_line('# service='.$service."\n").'0000';
|
|
||||||
if (!git_upload_pack_advertise_native($repository)) {
|
if (!git_upload_pack_advertise_native($repository)) {
|
||||||
error_log('Native Git advertisement failed for '.$repository['url'].'.');
|
error_log('Native Git advertisement failed for '.$repository['url'].'.');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((!function_exists('proc_open') || !git_service_executable_available($application))
|
if ($service === 'git-receive-pack') {
|
||||||
&& $service === 'git-receive-pack') {
|
|
||||||
echo format_packet_line('# service='.$service."\n").'0000';
|
|
||||||
if (!git_receive_pack_advertise_native($repository)) {
|
if (!git_receive_pack_advertise_native($repository)) {
|
||||||
error_log('Native Git receive advertisement failed for '.$repository['url'].'.');
|
error_log('Native Git receive advertisement failed for '.$repository['url'].'.');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$prefix = '';
|
send_error(403, 'Forbidden', 'Unsupported Git service.');
|
||||||
if (!git_service_is_protocol_v2($request)) {
|
|
||||||
$prefix = format_packet_line('# service='.$service."\n").'0000';
|
|
||||||
}
|
|
||||||
|
|
||||||
git_service_run($application, $repository, $request, $service, TRUE, NULL, $prefix);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function git_service_rpc($application, $repository, $request, $service, $input) {
|
function git_service_rpc($application, $repository, $request, $service, $input) {
|
||||||
if ((!function_exists('proc_open') || !git_service_executable_available($application))
|
if (!git_service_native_available()) {
|
||||||
&& !git_service_native_available()) {
|
|
||||||
send_error(503, 'Service Unavailable', 'The native Git service requires PHP zlib and hash support.');
|
send_error(503, 'Service Unavailable', 'The native Git service requires PHP zlib and hash support.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,15 +210,13 @@ function git_service_rpc($application, $repository, $request, $service, $input)
|
|||||||
header('Content-Type: application/x-'.$service.'-result');
|
header('Content-Type: application/x-'.$service.'-result');
|
||||||
header('X-Content-Type-Options: nosniff');
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
|
||||||
if ((!function_exists('proc_open') || !git_service_executable_available($application))
|
if ($service === 'git-upload-pack') {
|
||||||
&& $service === 'git-upload-pack') {
|
|
||||||
if (!git_upload_pack_rpc_native($repository, $input)) {
|
if (!git_upload_pack_rpc_native($repository, $input)) {
|
||||||
error_log('Native Git upload-pack failed for '.$repository['url'].'.');
|
error_log('Native Git upload-pack failed for '.$repository['url'].'.');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((!function_exists('proc_open') || !git_service_executable_available($application))
|
if ($service === 'git-receive-pack') {
|
||||||
&& $service === 'git-receive-pack') {
|
|
||||||
if (!git_receive_pack_rpc_native($repository, $input)) {
|
if (!git_receive_pack_rpc_native($repository, $input)) {
|
||||||
error_log('Native Git receive-pack failed for '.$repository['url'].'.');
|
error_log('Native Git receive-pack failed for '.$repository['url'].'.');
|
||||||
return 1;
|
return 1;
|
||||||
@@ -519,5 +224,5 @@ function git_service_rpc($application, $repository, $request, $service, $input)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return git_service_run($application, $repository, $request, $service, FALSE, $input);
|
send_error(403, 'Forbidden', 'Unsupported Git service.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,8 +221,6 @@ function i18n_catalog_en() {
|
|||||||
.' shortly.',
|
.' shortly.',
|
||||||
'notice.repository_root_unavailable' => 'The repository directory is unavailable or'
|
'notice.repository_root_unavailable' => 'The repository directory is unavailable or'
|
||||||
.' not writable.',
|
.' not writable.',
|
||||||
'notice.repository_git_unavailable' => 'The Git initialization service is unavailable'
|
|
||||||
.' right now.',
|
|
||||||
'notice.repository_metadata_unsaved' => 'Repository ownership could not be saved.'
|
'notice.repository_metadata_unsaved' => 'Repository ownership could not be saved.'
|
||||||
.' Try again shortly.',
|
.' Try again shortly.',
|
||||||
'notice.repository_create_failed' => 'Repository creation failed. Check the server'
|
'notice.repository_create_failed' => 'Repository creation failed. Check the server'
|
||||||
@@ -332,9 +330,6 @@ function i18n_catalog_en() {
|
|||||||
'install.legend_application' => 'Application',
|
'install.legend_application' => 'Application',
|
||||||
'install.label_url_base' => 'Base path',
|
'install.label_url_base' => 'Base path',
|
||||||
'install.hint_url_base' => 'Leave empty when deploying at the domain root.',
|
'install.hint_url_base' => 'Leave empty when deploying at the domain root.',
|
||||||
'install.label_git_executable' => 'Git executable',
|
|
||||||
'install.hint_git_executable' => 'Falls back to the pure PHP implementation when'
|
|
||||||
.' unavailable.',
|
|
||||||
'install.label_language' => 'Interface language',
|
'install.label_language' => 'Interface language',
|
||||||
'install.language_auto' => 'Follow the browser (English fallback)',
|
'install.language_auto' => 'Follow the browser (English fallback)',
|
||||||
'install.language_en' => 'English',
|
'install.language_en' => 'English',
|
||||||
@@ -369,7 +364,6 @@ function i18n_catalog_en() {
|
|||||||
|
|
||||||
'install.error_url_base' => 'The base path must start with / and must not end with'
|
'install.error_url_base' => 'The base path must start with / and must not end with'
|
||||||
.' /. Leave it empty when deploying at the domain root.',
|
.' /. Leave it empty when deploying at the domain root.',
|
||||||
'install.error_git_executable' => 'The Git executable path is not valid.',
|
|
||||||
'install.error_db_host' => 'The database host name is not valid.',
|
'install.error_db_host' => 'The database host name is not valid.',
|
||||||
'install.error_db_port' => 'The database port is not valid.',
|
'install.error_db_port' => 'The database port is not valid.',
|
||||||
'install.error_db_name' => 'A database name may contain only letters, digits,'
|
'install.error_db_name' => 'A database name may contain only letters, digits,'
|
||||||
@@ -559,7 +553,6 @@ function i18n_catalog_zh() {
|
|||||||
'notice.repository_exists' => '仓库 {name} 已存在。',
|
'notice.repository_exists' => '仓库 {name} 已存在。',
|
||||||
'notice.repository_create_busy' => '另一个仓库正在创建,请稍后重试。',
|
'notice.repository_create_busy' => '另一个仓库正在创建,请稍后重试。',
|
||||||
'notice.repository_root_unavailable' => '仓库存放目录不可用或不可写。',
|
'notice.repository_root_unavailable' => '仓库存放目录不可用或不可写。',
|
||||||
'notice.repository_git_unavailable' => 'Git 初始化服务当前不可用。',
|
|
||||||
'notice.repository_metadata_unsaved' => '仓库所有权信息无法保存,请稍后重试。',
|
'notice.repository_metadata_unsaved' => '仓库所有权信息无法保存,请稍后重试。',
|
||||||
'notice.repository_create_failed' => '仓库创建失败,请检查服务器日志。',
|
'notice.repository_create_failed' => '仓库创建失败,请检查服务器日志。',
|
||||||
'notice.repository_created' => '仓库 {name} 已创建。',
|
'notice.repository_created' => '仓库 {name} 已创建。',
|
||||||
@@ -646,8 +639,6 @@ function i18n_catalog_zh() {
|
|||||||
'install.legend_application' => '应用',
|
'install.legend_application' => '应用',
|
||||||
'install.label_url_base' => '基础路径',
|
'install.label_url_base' => '基础路径',
|
||||||
'install.hint_url_base' => '部署在域名根路径时留空。',
|
'install.hint_url_base' => '部署在域名根路径时留空。',
|
||||||
'install.label_git_executable' => 'Git 可执行文件',
|
|
||||||
'install.hint_git_executable' => '不可用时会回退到纯 PHP 实现。',
|
|
||||||
'install.label_language' => '界面语言',
|
'install.label_language' => '界面语言',
|
||||||
'install.language_auto' => '跟随浏览器(默认英语)',
|
'install.language_auto' => '跟随浏览器(默认英语)',
|
||||||
'install.language_en' => 'English',
|
'install.language_en' => 'English',
|
||||||
@@ -676,7 +667,6 @@ function i18n_catalog_zh() {
|
|||||||
'install.button_submit' => '开始安装',
|
'install.button_submit' => '开始安装',
|
||||||
|
|
||||||
'install.error_url_base' => '基础路径必须以 / 开头,且不能以 / 结尾;部署在域名根路径时请留空。',
|
'install.error_url_base' => '基础路径必须以 / 开头,且不能以 / 结尾;部署在域名根路径时请留空。',
|
||||||
'install.error_git_executable' => 'Git 可执行文件路径无效。',
|
|
||||||
'install.error_db_host' => '数据库主机名无效。',
|
'install.error_db_host' => '数据库主机名无效。',
|
||||||
'install.error_db_port' => '数据库端口无效。',
|
'install.error_db_port' => '数据库端口无效。',
|
||||||
'install.error_db_name' => '数据库名只能包含字母、数字、下划线和短横线,最多 64 个字符。',
|
'install.error_db_name' => '数据库名只能包含字母、数字、下划线和短横线,最多 64 个字符。',
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
本项目通过 PHP 发布显式配置的 Git 仓库,同时支持 **Dumb HTTP** 与 **Smart HTTP**:
|
本项目通过 PHP 发布显式配置的 Git 仓库,同时支持 **Dumb HTTP** 与 **Smart HTTP**:
|
||||||
|
|
||||||
- `clone`:支持 Smart HTTP;服务器没有 Git/`proc_open` 时由 PHP 原生 upload-pack 提供,也保留 Dumb HTTP 兼容路径。
|
- `clone`:由 PHP 实现 Smart HTTP upload-pack,并保留 Dumb HTTP 兼容路径。
|
||||||
- `pull` / `fetch`:通过 `git-upload-pack --stateless-rpc` 提供。
|
- `pull` / `fetch`:由 PHP 实现的 upload-pack 提供。
|
||||||
- `push`:有 Git 时通过 `git-receive-pack --stateless-rpc` 提供;无 Git 时由 PHP 原生 receive-pack 提供,默认关闭。
|
- `push`:由 PHP 实现的 receive-pack 提供,默认关闭。
|
||||||
- `branch`:远程分支以 `refs/heads/*` 表示,可通过 push 创建、更新和删除。
|
- `branch`:远程分支以 `refs/heads/*` 表示,可通过 push 创建、更新和删除。
|
||||||
- `tag`:远程标签以 `refs/tags/*` 表示,可通过 push 创建、更新和删除。
|
- `tag`:远程标签以 `refs/tags/*` 表示,可通过 push 创建、更新和删除。
|
||||||
- `create`:可从主界面创建受控目录内的 bare 仓库;Git 不可用时由 PHP 直接初始化。
|
- `create`:可从主界面创建受控目录内的 bare 仓库,由 PHP 直接初始化。
|
||||||
|
|
||||||
Git 协议不会向服务器发送名为“branch”或“tag”的独立命令:本地 `git branch`、`git tag` 不访问服务器;远程分支和标签通过 fetch/pull 获取,通过 push 更新。
|
Git 协议不会向服务器发送名为“branch”或“tag”的独立命令:本地 `git branch`、`git tag` 不访问服务器;远程分支和标签通过 fetch/pull 获取,通过 push 更新。
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ lib/http.php HTTP 状态、响应头和认证用户读取
|
|||||||
lib/auth.php MySQL 用户、网页登录会话与 Access Token 验证
|
lib/auth.php MySQL 用户、网页登录会话与 Access Token 验证
|
||||||
lib/repository.php 仓库配置、安全路径和 Dumb HTTP refs
|
lib/repository.php 仓库配置、安全路径和 Dumb HTTP refs
|
||||||
lib/router.php 请求路由
|
lib/router.php 请求路由
|
||||||
lib/git_service.php Smart HTTP Git 子进程与流式传输
|
lib/git_service.php Smart HTTP 服务分发与 bare 仓库初始化
|
||||||
operations/clone.php Dumb HTTP clone/object 资源
|
operations/clone.php Dumb HTTP clone/object 资源
|
||||||
operations/pull.php upload-pack:clone/fetch/pull
|
operations/pull.php upload-pack:clone/fetch/pull
|
||||||
operations/push.php receive-pack:push 请求、大小及 refs 校验
|
operations/push.php receive-pack:push 请求、大小及 refs 校验
|
||||||
@@ -38,14 +38,14 @@ operations/tag.php refs/tags/* 标签更新规则
|
|||||||
- MySQL 5.7+/MariaDB 10.2+ 与 PHP PDO MySQL 扩展(`pdo_mysql`)。
|
- MySQL 5.7+/MariaDB 10.2+ 与 PHP PDO MySQL 扩展(`pdo_mysql`)。
|
||||||
- Apache `mod_rewrite` 模块。
|
- Apache `mod_rewrite` 模块。
|
||||||
- 允许项目目录中的 `.htaccess` 使用重写规则。
|
- 允许项目目录中的 `.htaccess` 使用重写规则。
|
||||||
- Smart HTTP 需要服务器安装 Git,并允许 PHP 使用 `proc_open`。
|
- Smart HTTP 需要 PHP zlib 与 hash 扩展。
|
||||||
- Web 服务器进程对仓库具有读取权限;启用 push 时还需要写入权限。
|
- Web 服务器进程对仓库具有读取权限;启用 push 时还需要写入权限。
|
||||||
|
|
||||||
项目没有 Composer 依赖,也不需要构建。若系统尚未启用 PDO MySQL,先安装对应 PHP 扩展并重启 Apache/PHP-FPM。
|
项目没有 Composer 依赖,也不需要构建。若系统尚未启用 PDO MySQL,先安装对应 PHP 扩展并重启 Apache/PHP-FPM。
|
||||||
|
|
||||||
服务器可以不安装 Git。Git 或 `proc_open` 不可用时,应用使用纯 PHP 实现的 Smart HTTP 服务端协议,支持普通 SHA-1 仓库的 clone、fetch、pull、push、delta、分支和标签;PHP 必须启用 zlib 与 hash 扩展。
|
服务器不需要安装 Git。应用使用纯 PHP 实现的 Smart HTTP 服务端协议,支持普通 SHA-1 仓库的 clone、fetch、pull、push、delta、分支和标签。
|
||||||
|
|
||||||
原生 PHP 后端当前不支持 SHA-256 仓库、shallow clone、partial clone/filter、push certificate、Git hooks 和仅 protocol v2 提供的功能。需要这些能力时仍应安装 Git 并允许 `proc_open`。
|
PHP 后端当前不支持 SHA-256 仓库、shallow clone、partial clone/filter、push certificate、Git hooks 和仅 protocol v2 提供的功能。
|
||||||
|
|
||||||
## 3. 安装
|
## 3. 安装
|
||||||
|
|
||||||
@@ -109,7 +109,6 @@ https://git.example.com/php-git-server/
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$url_base = '/php-git-server';
|
$url_base = '/php-git-server';
|
||||||
$git_executable = 'git';
|
|
||||||
|
|
||||||
$auth = array(
|
$auth = array(
|
||||||
'enabled' => TRUE,
|
'enabled' => TRUE,
|
||||||
@@ -191,7 +190,7 @@ $managed_repositories = array(
|
|||||||
- 设置 `$managed_repositories = array();` 可完全关闭主界面创建功能。
|
- 设置 `$managed_repositories = array();` 可完全关闭主界面创建功能。
|
||||||
- 仓库名称仅允许字母、数字、点、短横线和下划线,长度最多 64 个字符;`.git` 后缀可省略。
|
- 仓库名称仅允许字母、数字、点、短横线和下划线,长度最多 64 个字符;`.git` 后缀可省略。
|
||||||
- 新仓库是 bare 仓库,默认分支为 `main`。应用内的创建请求使用锁、暂存目录和原子改名,不会互相覆盖;托管目录不应由其他进程同时写入。
|
- 新仓库是 bare 仓库,默认分支为 `main`。应用内的创建请求使用锁、暂存目录和原子改名,不会互相覆盖;托管目录不应由其他进程同时写入。
|
||||||
- Git 或 `proc_open` 不可用时,应用以纯 PHP 创建标准 SHA-1 格式的空 bare 仓库;之后可以通过 Dumb HTTP clone,但首次写入仍需在其他具备 Git 的环境中生成仓库内容并同步到服务器。
|
- 应用以纯 PHP 创建标准 SHA-1 格式的空 bare 仓库,可以直接通过 Smart HTTP 完成首次 push。
|
||||||
|
|
||||||
静态 `$repos` 条目与托管目录中的仓库 URL 冲突时,以静态条目为准。仓库创建要求已登录 Session,所有修改表单还使用会话 CSRF 令牌。托管仓库的所有者和可见性保存在 `pgit_repositories`;缺少元数据的旧托管仓库按私有、无所有者处理,在完成迁移前不可 push。
|
静态 `$repos` 条目与托管目录中的仓库 URL 冲突时,以静态条目为准。仓库创建要求已登录 Session,所有修改表单还使用会话 CSRF 令牌。托管仓库的所有者和可见性保存在 `pgit_repositories`;缺少元数据的旧托管仓库按私有、无所有者处理,在完成迁移前不可 push。
|
||||||
|
|
||||||
@@ -215,7 +214,7 @@ $managed_repositories = array(
|
|||||||
|
|
||||||
所有 push 都必须提供 access token,且 token 所属用户名必须与 `owner` 完全一致。`branches`、`tags` 和 `other_refs` 只控制 push 更新,不会隐藏已经存在的 refs。私有仓库的 Smart HTTP 与 Dumb HTTP 路径都会先验证 token,私有对象响应禁止共享缓存。
|
所有 push 都必须提供 access token,且 token 所属用户名必须与 `owner` 完全一致。`branches`、`tags` 和 `other_refs` 只控制 push 更新,不会隐藏已经存在的 refs。私有仓库的 Smart HTTP 与 Dumb HTTP 路径都会先验证 token,私有对象响应禁止共享缓存。
|
||||||
|
|
||||||
push 请求会先写入系统临时目录,以便在交给 `git-receive-pack` 前检查 ref 命名空间。启用较大的 push 时,应保证 PHP 系统临时目录具有足够空间;也可以使用 `max_request_bytes` 设置上限。
|
push 请求会先写入系统临时目录,以便 PHP 在处理对象前检查 ref 命名空间。启用较大的 push 时,应保证 PHP 系统临时目录具有足够空间;也可以使用 `max_request_bytes` 设置上限。
|
||||||
|
|
||||||
## 6. 身份认证
|
## 6. 身份认证
|
||||||
|
|
||||||
@@ -270,7 +269,7 @@ git push origin main
|
|||||||
|
|
||||||
Git 收到私有读取或 push 的 `401` 响应后会提示输入用户名和密码。也可以使用操作系统的 Git Credential Manager 或其他安全凭据助手保存 token;不要把 token 写入远程 URL、shell 历史、仓库配置或脚本。
|
Git 收到私有读取或 push 的 `401` 响应后会提示输入用户名和密码。也可以使用操作系统的 Git Credential Manager 或其他安全凭据助手保存 token;不要把 token 写入远程 URL、shell 历史、仓库配置或脚本。
|
||||||
|
|
||||||
公开仓库允许匿名 clone/fetch/pull;私有仓库只接受 access token。浏览器登录 Session 只用于首页、创建仓库和显示私有仓库列表,不能代替 Git access token。Token 验证成功后,用户名会作为 `REMOTE_USER` 传给 Git 子进程和 hooks,现有 hooks 可以继续读取该变量。
|
公开仓库允许匿名 clone/fetch/pull;私有仓库只接受 access token。浏览器登录 Session 只用于首页、创建仓库和显示私有仓库列表,不能代替 Git access token。Token 验证成功后,应用使用该用户名执行仓库所有者权限检查。
|
||||||
|
|
||||||
### 现有数据库与仓库迁移
|
### 现有数据库与仓库迁移
|
||||||
|
|
||||||
@@ -315,7 +314,7 @@ sudo find /var/www/php-git-server/repos/project.git -type f -exec chmod 660 {} \
|
|||||||
|
|
||||||
权限策略应根据服务器实际用户、组和备份方案调整。不要使用 `chmod -R 777`。
|
权限策略应根据服务器实际用户、组和备份方案调整。不要使用 `chmod -R 777`。
|
||||||
|
|
||||||
仓库中的 hooks 会在 `git-receive-pack` 处理 push 时以 PHP/Web 服务器用户身份执行。只能发布受信任的仓库和 hooks,并确保 hooks 不接受未经校验的外部参数去执行任意命令。
|
PHP 服务端不会执行仓库中的 Git hooks。
|
||||||
|
|
||||||
## 8. 操作示例
|
## 8. 操作示例
|
||||||
|
|
||||||
@@ -402,8 +401,8 @@ Smart HTTP 支持:
|
|||||||
- `POST /git-upload-pack`
|
- `POST /git-upload-pack`
|
||||||
- `GET /info/refs?service=git-receive-pack`
|
- `GET /info/refs?service=git-receive-pack`
|
||||||
- `POST /git-receive-pack`
|
- `POST /git-receive-pack`
|
||||||
- upload-pack 的 Git protocol v0/v1/v2 协商
|
- upload-pack 的 Git protocol v0/v1 基本协商
|
||||||
- Git 自身支持的 SHA-1 或 SHA-256 仓库格式
|
- SHA-1 仓库格式
|
||||||
|
|
||||||
Dumb HTTP 支持:
|
Dumb HTTP 支持:
|
||||||
|
|
||||||
@@ -417,7 +416,7 @@ Dumb HTTP 支持:
|
|||||||
- loose refs、packed refs、packed annotated tag 的 peeled refs
|
- loose refs、packed refs、packed annotated tag 的 peeled refs
|
||||||
- SHA-1 和 SHA-256 长度的对象名称
|
- SHA-1 和 SHA-256 长度的对象名称
|
||||||
|
|
||||||
安装 Git 时,Smart HTTP 的具体协商、对象校验、fast-forward 规则和仓库 hooks 由服务器 Git 负责。无 Git 时,PHP 后端执行 SHA-1 对象哈希、pack 校验、OFS/REF delta、对象连通性、ref 锁和默认 fast-forward 检查,但不会执行 hooks。
|
PHP 后端执行 SHA-1 对象哈希、pack 校验、OFS/REF delta、对象连通性、ref 锁和默认 fast-forward 检查,但不会执行 hooks。
|
||||||
|
|
||||||
## 10. 验证部署
|
## 10. 验证部署
|
||||||
|
|
||||||
@@ -480,14 +479,7 @@ curl -i -X POST https://git.example.com/php-git-server/project.git/HEAD
|
|||||||
|
|
||||||
### clone / pull 返回 503
|
### clone / pull 返回 503
|
||||||
|
|
||||||
检查:
|
检查 PHP 是否启用了 zlib 与 hash、仓库是否为 SHA-1 格式,以及 Web 服务器进程是否可读取仓库。当前服务不支持浅克隆、filter、SHA-256 或 hooks。
|
||||||
|
|
||||||
1. `$git_executable` 是否指向可执行的 Git。
|
|
||||||
2. Web 服务器进程的 `PATH` 是否包含 Git。
|
|
||||||
3. PHP 是否允许 `proc_open`。
|
|
||||||
4. Web 服务器进程是否可读取仓库。
|
|
||||||
|
|
||||||
若 Git 不可用,服务使用原生 PHP Smart HTTP 后端。确认 PHP 启用了 zlib 与 hash,并确认仓库是 SHA-1 格式;浅克隆、filter、SHA-256 或 hooks 需求必须改用 Git 后端。
|
|
||||||
|
|
||||||
### push 返回 403
|
### push 返回 403
|
||||||
|
|
||||||
@@ -519,7 +511,7 @@ curl -i -X POST https://git.example.com/php-git-server/project.git/HEAD
|
|||||||
|
|
||||||
### push 返回 500 或远端断开
|
### push 返回 500 或远端断开
|
||||||
|
|
||||||
检查 PHP/Apache 错误日志、Git hooks 输出、仓库写权限、磁盘空间和临时目录空间。还应确认 `max_request_bytes` 没有设置得过小。
|
检查 PHP/Apache 错误日志、仓库写权限、磁盘空间和临时目录空间。还应确认 `max_request_bytes` 没有设置得过小。
|
||||||
|
|
||||||
### non-fast-forward 被拒绝
|
### non-fast-forward 被拒绝
|
||||||
|
|
||||||
@@ -529,7 +521,7 @@ curl -i -X POST https://git.example.com/php-git-server/project.git/HEAD
|
|||||||
git push --force-with-lease origin main
|
git push --force-with-lease origin main
|
||||||
```
|
```
|
||||||
|
|
||||||
仓库本地配置和 hooks 仍可进一步禁止 force push、删除或特定提交。
|
应用的仓库选项还可禁止 force push、删除或特定 ref 命名空间。
|
||||||
|
|
||||||
### 浏览器可以访问,但 Git 操作失败
|
### 浏览器可以访问,但 Git 操作失败
|
||||||
|
|
||||||
@@ -554,6 +546,5 @@ GIT_TRACE=1 GIT_CURL_VERBOSE=1 git clone \
|
|||||||
- 只给 Web 服务器最小必要的文件权限。
|
- 只给 Web 服务器最小必要的文件权限。
|
||||||
- 将 `other_refs` 保持为 `FALSE`,除非确实需要 notes、replace 或自定义 refs。
|
- 将 `other_refs` 保持为 `FALSE`,除非确实需要 notes、replace 或自定义 refs。
|
||||||
- 使用 `max_request_bytes`、Web 服务器请求体限制和磁盘配额防止超大 push。
|
- 使用 `max_request_bytes`、Web 服务器请求体限制和磁盘配额防止超大 push。
|
||||||
- 审查仓库 hooks;push 会执行 receive-pack hooks。
|
|
||||||
- 不提交 `config.php`,也不要把敏感信息写入 Git 历史。
|
- 不提交 `config.php`,也不要把敏感信息写入 Git 历史。
|
||||||
- 确保 `.htaccess` 或等价的虚拟主机规则生效,避免绕过 `index.php`。
|
- 确保 `.htaccess` 或等价的虚拟主机规则生效,避免绕过 `index.php`。
|
||||||
|
|||||||
Reference in New Issue
Block a user