152 lines
5.2 KiB
PHP
152 lines
5.2 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
function adminCheckForUpdates(string $projectRoot, string $branch, string $remoteName = 'origin', string $remoteUrl = ''): array
|
||
{
|
||
if (!is_dir($projectRoot . '/.git')) {
|
||
throw new RuntimeException('Репозиторий не найден: .git отсутствует');
|
||
}
|
||
|
||
$remoteName = adminNormalizeRemoteName($remoteName);
|
||
adminEnsureRemote($projectRoot, $remoteName, $remoteUrl);
|
||
|
||
$remoteRef = $remoteName . '/' . $branch;
|
||
|
||
$fetch = adminRunShellCommand('git fetch ' . escapeshellarg($remoteName) . ' ' . escapeshellarg($branch) . ' --prune', $projectRoot);
|
||
if ($fetch['code'] !== 0) {
|
||
throw new RuntimeException('Не удалось обновить данные из ' . $remoteName . ': ' . adminTailOutput($fetch['output']));
|
||
}
|
||
|
||
$local = adminRunShellCommand('git rev-parse --short=12 HEAD', $projectRoot);
|
||
$remote = adminRunShellCommand('git rev-parse --short=12 ' . escapeshellarg($remoteRef), $projectRoot);
|
||
$behindRaw = adminRunShellCommand('git rev-list --count HEAD..' . escapeshellarg($remoteRef), $projectRoot);
|
||
$aheadRaw = adminRunShellCommand('git rev-list --count ' . escapeshellarg($remoteRef) . '..HEAD', $projectRoot);
|
||
|
||
if ($local['code'] !== 0 || $remote['code'] !== 0 || $behindRaw['code'] !== 0 || $aheadRaw['code'] !== 0) {
|
||
throw new RuntimeException('Не удалось определить состояние ветки');
|
||
}
|
||
|
||
$behind = (int)trim($behindRaw['output']);
|
||
$ahead = (int)trim($aheadRaw['output']);
|
||
|
||
$state = 'up_to_date';
|
||
if ($behind > 0 && $ahead === 0) {
|
||
$state = 'update_available';
|
||
} elseif ($ahead > 0 && $behind === 0) {
|
||
$state = 'local_ahead';
|
||
} elseif ($ahead > 0 && $behind > 0) {
|
||
$state = 'diverged';
|
||
}
|
||
|
||
return [
|
||
'state' => $state,
|
||
'remote_name' => $remoteName,
|
||
'branch' => $branch,
|
||
'local_ref' => trim($local['output']),
|
||
'remote_ref' => trim($remote['output']),
|
||
'behind' => $behind,
|
||
'ahead' => $ahead,
|
||
'can_deploy' => $state === 'update_available',
|
||
];
|
||
}
|
||
|
||
function adminRunDeployScript(string $projectRoot, string $branch, string $scriptPath, string $phpBin, string $remoteName = 'origin', string $remoteUrl = ''): array
|
||
{
|
||
if (!is_file($scriptPath)) {
|
||
throw new RuntimeException('Скрипт деплоя не найден: ' . $scriptPath);
|
||
}
|
||
|
||
$remoteName = adminNormalizeRemoteName($remoteName);
|
||
|
||
$run = adminRunShellCommand('bash ' . escapeshellarg($scriptPath), $projectRoot, [
|
||
'BRANCH' => $branch,
|
||
'PHP_BIN' => $phpBin,
|
||
'REMOTE_NAME' => $remoteName,
|
||
'REMOTE_URL' => $remoteUrl,
|
||
]);
|
||
|
||
return [
|
||
'ok' => $run['code'] === 0,
|
||
'code' => $run['code'],
|
||
'output' => adminTailOutput($run['output']),
|
||
];
|
||
}
|
||
|
||
function adminEnsureRemote(string $projectRoot, string $remoteName, string $remoteUrl): void
|
||
{
|
||
$getRemote = adminRunShellCommand('git remote get-url ' . escapeshellarg($remoteName), $projectRoot);
|
||
if ($getRemote['code'] !== 0) {
|
||
if ($remoteUrl === '') {
|
||
throw new RuntimeException('Remote ' . $remoteName . ' не найден');
|
||
}
|
||
|
||
$add = adminRunShellCommand('git remote add ' . escapeshellarg($remoteName) . ' ' . escapeshellarg($remoteUrl), $projectRoot);
|
||
if ($add['code'] !== 0) {
|
||
throw new RuntimeException('Не удалось добавить remote ' . $remoteName . ': ' . adminTailOutput($add['output']));
|
||
}
|
||
return;
|
||
}
|
||
|
||
if ($remoteUrl === '') {
|
||
return;
|
||
}
|
||
|
||
$currentUrl = trim($getRemote['output']);
|
||
if ($currentUrl === $remoteUrl) {
|
||
return;
|
||
}
|
||
|
||
$set = adminRunShellCommand('git remote set-url ' . escapeshellarg($remoteName) . ' ' . escapeshellarg($remoteUrl), $projectRoot);
|
||
if ($set['code'] !== 0) {
|
||
throw new RuntimeException('Не удалось обновить remote ' . $remoteName . ': ' . adminTailOutput($set['output']));
|
||
}
|
||
}
|
||
|
||
function adminNormalizeRemoteName(string $remoteName): string
|
||
{
|
||
$remoteName = trim($remoteName);
|
||
if ($remoteName === '') {
|
||
return 'origin';
|
||
}
|
||
|
||
if (!preg_match('/^[A-Za-z0-9._-]+$/', $remoteName)) {
|
||
throw new RuntimeException('Некорректное имя remote');
|
||
}
|
||
|
||
return $remoteName;
|
||
}
|
||
|
||
function adminRunShellCommand(string $command, string $cwd, array $env = []): array
|
||
{
|
||
$envPrefix = '';
|
||
foreach ($env as $key => $value) {
|
||
if (!preg_match('/^[A-Z_][A-Z0-9_]*$/', (string)$key)) {
|
||
continue;
|
||
}
|
||
$envPrefix .= $key . '=' . escapeshellarg((string)$value) . ' ';
|
||
}
|
||
|
||
$fullCommand = 'cd ' . escapeshellarg($cwd) . ' && ' . $envPrefix . $command . ' 2>&1';
|
||
$output = [];
|
||
$code = 0;
|
||
exec($fullCommand, $output, $code);
|
||
|
||
return ['code' => $code, 'output' => implode("\n", $output)];
|
||
}
|
||
|
||
function adminTailOutput(string $output, int $maxLines = 80): string
|
||
{
|
||
$output = trim($output);
|
||
if ($output === '') {
|
||
return '';
|
||
}
|
||
|
||
$lines = preg_split('/\r\n|\r|\n/', $output);
|
||
if (!is_array($lines)) {
|
||
return $output;
|
||
}
|
||
|
||
return implode("\n", array_slice($lines, -$maxLines));
|
||
}
|