Supporto Modifiche: sync deploy senza checkout git

This commit is contained in:
michele 2026-03-29 21:08:31 +00:00
parent 8ee59acb31
commit 53047a955a

View File

@ -1,9 +1,8 @@
<?php <?php
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Process;
@ -23,13 +22,18 @@ public function handle(): int
$branch = trim((string) $this->option('branch')) ?: 'main'; $branch = trim((string) $this->option('branch')) ?: 'main';
$force = (bool) $this->option('force'); $force = (bool) $this->option('force');
if (! is_dir(base_path('.git'))) { $deployPath = base_path();
$this->error('Repository Git non trovato nella base path corrente.'); $repoPath = is_dir(base_path('.git')) ? $deployPath : $this->resolveSourceRepositoryPath();
$this->writeProgress(100, 'Repository Git non trovato', 'failed', 1); $deploySync = $repoPath !== null && realpath($repoPath) !== realpath($deployPath);
if ($repoPath === null) {
$this->error('Repository Git sorgente non trovato.');
$this->writeProgress(100, 'Repository Git sorgente non trovato', 'failed', 1);
$this->persistSummary([ $this->persistSummary([
'status' => 'failed', 'status' => 'failed',
'exit_code' => 1, 'exit_code' => 1,
'message' => 'Repository Git non trovato', 'message' => 'Repository Git sorgente non trovato',
'deploy_path' => $deployPath,
]); ]);
return self::FAILURE; return self::FAILURE;
@ -37,35 +41,37 @@ public function handle(): int
$this->writeProgress(5, 'Preparazione sync da Gitea', 'running'); $this->writeProgress(5, 'Preparazione sync da Gitea', 'running');
$currentBranch = $this->runGit(['rev-parse', '--abbrev-ref', 'HEAD']); $currentBranch = $this->runGit($repoPath, ['rev-parse', '--abbrev-ref', 'HEAD']);
$currentCommit = $this->runGit(['rev-parse', '--short', 'HEAD']); $currentCommit = $this->runGit($repoPath, ['rev-parse', '--short', 'HEAD']);
if ($currentBranch === null || $currentCommit === null) { if ($currentBranch === null || $currentCommit === null) {
$this->error('Impossibile leggere lo stato Git locale.'); $this->error('Impossibile leggere lo stato Git locale.');
$this->writeProgress(100, 'Impossibile leggere stato Git locale', 'failed', 1); $this->writeProgress(100, 'Impossibile leggere stato Git locale', 'failed', 1);
$this->persistSummary([ $this->persistSummary([
'status' => 'failed', 'status' => 'failed',
'exit_code' => 1, 'exit_code' => 1,
'message' => 'Impossibile leggere stato Git locale', 'message' => 'Impossibile leggere stato Git locale',
'repo_path' => $repoPath,
]); ]);
return self::FAILURE; return self::FAILURE;
} }
$dirtyResult = Process::path(base_path())->run(['git', 'status', '--porcelain']); $dirtyResult = Process::path($repoPath)->run(['git', 'status', '--porcelain']);
$isDirty = trim((string) $dirtyResult->output()) !== ''; $isDirty = trim((string) $dirtyResult->output()) !== '';
if ($isDirty && ! $force) { if ($isDirty && ! $force && ! $deploySync) {
$this->error('Working tree sporco: sync bloccata per evitare conflitti locali.'); $this->error('Working tree sporco: sync bloccata per evitare conflitti locali.');
$this->writeProgress(100, 'Working tree sporco: sync bloccata', 'failed', 1); $this->writeProgress(100, 'Working tree sporco: sync bloccata', 'failed', 1);
$this->persistSummary([ $this->persistSummary([
'status' => 'failed', 'status' => 'failed',
'exit_code' => 1, 'exit_code' => 1,
'message' => 'Working tree sporco: sync bloccata', 'message' => 'Working tree sporco: sync bloccata',
'remote' => $remote, 'remote' => $remote,
'branch' => $branch, 'branch' => $branch,
'before_commit' => $currentCommit, 'before_commit' => $currentCommit,
'working_tree_dirty' => true, 'working_tree_dirty' => true,
'repo_path' => $repoPath,
]); ]);
return self::FAILURE; return self::FAILURE;
@ -74,7 +80,7 @@ public function handle(): int
$this->info("Fetch da {$remote}..."); $this->info("Fetch da {$remote}...");
$this->writeProgress(18, 'Fetch remoto da Gitea', 'running'); $this->writeProgress(18, 'Fetch remoto da Gitea', 'running');
$fetch = Process::path(base_path()) $fetch = Process::path($repoPath)
->timeout(1800) ->timeout(1800)
->run(['git', 'fetch', $remote]); ->run(['git', 'fetch', $remote]);
@ -82,90 +88,181 @@ public function handle(): int
$this->line(trim((string) ($fetch->errorOutput() !== '' ? $fetch->errorOutput() : $fetch->output()))); $this->line(trim((string) ($fetch->errorOutput() !== '' ? $fetch->errorOutput() : $fetch->output())));
$this->writeProgress(100, 'Fetch remoto fallito', 'failed', 1); $this->writeProgress(100, 'Fetch remoto fallito', 'failed', 1);
$this->persistSummary([ $this->persistSummary([
'status' => 'failed', 'status' => 'failed',
'exit_code' => 1, 'exit_code' => 1,
'message' => 'Fetch remoto fallito', 'message' => 'Fetch remoto fallito',
'remote' => $remote, 'remote' => $remote,
'branch' => $branch, 'branch' => $branch,
'before_commit' => $currentCommit, 'before_commit' => $currentCommit,
'working_tree_dirty' => false, 'working_tree_dirty' => false,
'repo_path' => $repoPath,
]); ]);
return self::FAILURE; return self::FAILURE;
} }
$this->writeProgress(32, 'Allineamento branch locale', 'running'); $remoteRef = 'refs/remotes/' . $remote . '/' . $branch;
$docsRepoPath = $deployPath;
$newCommit = $currentCommit;
$cleanupWorktree = null;
if ($currentBranch !== $branch) { if ($deploySync) {
$checkout = Process::path(base_path()) $this->writeProgress(32, 'Creazione sorgente remota pulita', 'running');
$worktreePath = storage_path('app/support/git-sync-worktrees/' . now()->format('YmdHis') . '-' . uniqid());
File::ensureDirectoryExists(dirname($worktreePath));
$worktreeAdd = Process::path($repoPath)
->timeout(1800) ->timeout(1800)
->run(['git', 'checkout', $branch]); ->run(['git', 'worktree', 'add', '--force', '--detach', $worktreePath, $remoteRef]);
if (! $checkout->successful()) { if (! $worktreeAdd->successful()) {
$this->line(trim((string) ($checkout->errorOutput() !== '' ? $checkout->errorOutput() : $checkout->output()))); $this->line(trim((string) ($worktreeAdd->errorOutput() !== '' ? $worktreeAdd->errorOutput() : $worktreeAdd->output())));
$this->writeProgress(100, 'Checkout branch fallito', 'failed', 1); $this->writeProgress(100, 'Creazione worktree remoto fallita', 'failed', 1);
$this->persistSummary([ $this->persistSummary([
'status' => 'failed', 'status' => 'failed',
'exit_code' => 1, 'exit_code' => 1,
'message' => 'Checkout branch fallito', 'message' => 'Creazione worktree remoto fallita',
'remote' => $remote, 'remote' => $remote,
'branch' => $branch, 'branch' => $branch,
'before_commit' => $currentCommit, 'before_commit' => $currentCommit,
'working_tree_dirty' => false, 'working_tree_dirty' => $isDirty,
'repo_path' => $repoPath,
'deploy_path' => $deployPath,
]); ]);
return self::FAILURE; return self::FAILURE;
} }
}
$this->info("Pull ff-only da {$remote}/{$branch}..."); $cleanupWorktree = static function () use ($repoPath, $worktreePath): void {
$this->writeProgress(48, 'Pull ff-only da Gitea', 'running'); Process::path($repoPath)->timeout(1800)->run(['git', 'worktree', 'remove', '--force', $worktreePath]);
if (is_dir($worktreePath)) {
File::deleteDirectory($worktreePath);
}
};
$pull = Process::path(base_path()) $docsRepoPath = $worktreePath;
->timeout(1800) $newCommit = $this->runGit($worktreePath, ['rev-parse', '--short', 'HEAD']) ?? $currentCommit;
->run(['git', 'pull', '--ff-only', $remote, $branch]);
if (! $pull->successful()) { $this->writeProgress(48, 'Sync deploy da worktree remoto', 'running');
$this->line(trim((string) ($pull->errorOutput() !== '' ? $pull->errorOutput() : $pull->output())));
$this->writeProgress(100, 'Pull da Gitea fallito', 'failed', 1);
$this->persistSummary([
'status' => 'failed',
'exit_code' => 1,
'message' => 'Pull da Gitea fallito',
'remote' => $remote,
'branch' => $branch,
'before_commit' => $currentCommit,
'working_tree_dirty' => false,
]);
return self::FAILURE; $sync = Process::path($deployPath)
->timeout(1800)
->run([
'bash',
'scripts/ops/netgescon-sync-prod-update.sh',
'--src',
$worktreePath,
'--dst',
$deployPath,
]);
if (! $sync->successful()) {
$this->line(trim((string) ($sync->errorOutput() !== '' ? $sync->errorOutput() : $sync->output())));
if ($cleanupWorktree instanceof \Closure) {
$cleanupWorktree();
}
$this->writeProgress(100, 'Sync deploy fallita', 'failed', 1);
$this->persistSummary([
'status' => 'failed',
'exit_code' => 1,
'message' => 'Sync deploy fallita',
'remote' => $remote,
'branch' => $branch,
'before_commit' => $currentCommit,
'after_commit' => $newCommit,
'working_tree_dirty' => $isDirty,
'repo_path' => $repoPath,
'deploy_path' => $deployPath,
]);
return self::FAILURE;
}
} else {
$this->writeProgress(32, 'Allineamento branch locale', 'running');
if ($currentBranch !== $branch) {
$checkout = Process::path($repoPath)
->timeout(1800)
->run(['git', 'checkout', $branch]);
if (! $checkout->successful()) {
$this->line(trim((string) ($checkout->errorOutput() !== '' ? $checkout->errorOutput() : $checkout->output())));
$this->writeProgress(100, 'Checkout branch fallito', 'failed', 1);
$this->persistSummary([
'status' => 'failed',
'exit_code' => 1,
'message' => 'Checkout branch fallito',
'remote' => $remote,
'branch' => $branch,
'before_commit' => $currentCommit,
'working_tree_dirty' => false,
'repo_path' => $repoPath,
]);
return self::FAILURE;
}
}
$this->info("Pull ff-only da {$remote}/{$branch}...");
$this->writeProgress(48, 'Pull ff-only da Gitea', 'running');
$pull = Process::path($repoPath)
->timeout(1800)
->run(['git', 'pull', '--ff-only', $remote, $branch]);
if (! $pull->successful()) {
$this->line(trim((string) ($pull->errorOutput() !== '' ? $pull->errorOutput() : $pull->output())));
$this->writeProgress(100, 'Pull da Gitea fallito', 'failed', 1);
$this->persistSummary([
'status' => 'failed',
'exit_code' => 1,
'message' => 'Pull da Gitea fallito',
'remote' => $remote,
'branch' => $branch,
'before_commit' => $currentCommit,
'working_tree_dirty' => false,
'repo_path' => $repoPath,
]);
return self::FAILURE;
}
$docsRepoPath = $deployPath;
$newCommit = $this->runGit($repoPath, ['rev-parse', '--short', 'HEAD']) ?? $currentCommit;
} }
$this->writeProgress(68, 'Rigenerazione changelog Git automatico', 'running'); $this->writeProgress(68, 'Rigenerazione changelog Git automatico', 'running');
File::ensureDirectoryExists(storage_path('app/support/generated')); File::ensureDirectoryExists(storage_path('app/support/generated'));
$docsSync = Process::path(base_path()) $docsSync = Process::path($deployPath)
->timeout(1800) ->timeout(1800)
->run([ ->run([
'bash', 'bash',
'scripts/ops/netgescon-sync-git-changelog.sh', 'scripts/ops/netgescon-sync-git-changelog.sh',
'--repo', '--repo',
base_path(), $docsRepoPath,
'--output', '--output',
storage_path('app/support/generated/NETGESCON-GIT-AUTOCHANGELOG.md'), storage_path('app/support/generated/NETGESCON-GIT-AUTOCHANGELOG.md'),
]); ]);
if (! $docsSync->successful()) { if (! $docsSync->successful()) {
$this->line(trim((string) ($docsSync->errorOutput() !== '' ? $docsSync->errorOutput() : $docsSync->output()))); $this->line(trim((string) ($docsSync->errorOutput() !== '' ? $docsSync->errorOutput() : $docsSync->output())));
if ($cleanupWorktree instanceof \Closure) {
$cleanupWorktree();
}
$this->writeProgress(100, 'Rigenerazione changelog Git fallita', 'failed', 1); $this->writeProgress(100, 'Rigenerazione changelog Git fallita', 'failed', 1);
$this->persistSummary([ $this->persistSummary([
'status' => 'failed', 'status' => 'failed',
'exit_code' => 1, 'exit_code' => 1,
'message' => 'Rigenerazione changelog Git fallita', 'message' => 'Rigenerazione changelog Git fallita',
'remote' => $remote, 'remote' => $remote,
'branch' => $branch, 'branch' => $branch,
'before_commit' => $currentCommit, 'before_commit' => $currentCommit,
'after_commit' => $newCommit,
'working_tree_dirty' => false, 'working_tree_dirty' => false,
'repo_path' => $repoPath,
'deploy_path' => $deployPath,
]); ]);
return self::FAILURE; return self::FAILURE;
@ -174,15 +271,21 @@ public function handle(): int
$this->writeProgress(76, 'Rigenerazione snapshot continuita sviluppo', 'running'); $this->writeProgress(76, 'Rigenerazione snapshot continuita sviluppo', 'running');
$snapshotExit = Artisan::call('netgescon:development-snapshot'); $snapshotExit = Artisan::call('netgescon:development-snapshot');
if ($snapshotExit !== 0) { if ($snapshotExit !== 0) {
if ($cleanupWorktree instanceof \Closure) {
$cleanupWorktree();
}
$this->writeProgress(100, 'Rigenerazione snapshot sviluppo fallita', 'failed', 1); $this->writeProgress(100, 'Rigenerazione snapshot sviluppo fallita', 'failed', 1);
$this->persistSummary([ $this->persistSummary([
'status' => 'failed', 'status' => 'failed',
'exit_code' => 1, 'exit_code' => 1,
'message' => 'Rigenerazione snapshot sviluppo fallita', 'message' => 'Rigenerazione snapshot sviluppo fallita',
'remote' => $remote, 'remote' => $remote,
'branch' => $branch, 'branch' => $branch,
'before_commit' => $currentCommit, 'before_commit' => $currentCommit,
'after_commit' => $newCommit,
'working_tree_dirty' => false, 'working_tree_dirty' => false,
'repo_path' => $repoPath,
'deploy_path' => $deployPath,
]); ]);
return self::FAILURE; return self::FAILURE;
@ -191,19 +294,25 @@ public function handle(): int
$this->writeProgress(82, 'Pulizia cache applicativa', 'running'); $this->writeProgress(82, 'Pulizia cache applicativa', 'running');
$this->callSilently('optimize:clear'); $this->callSilently('optimize:clear');
$newCommit = $this->runGit(['rev-parse', '--short', 'HEAD']) ?? $currentCommit; $remoteCommit = $this->runGit($repoPath, ['rev-parse', '--short', $remoteRef]) ?? $newCommit;
$remoteRef = $this->runGit(['rev-parse', '--short', 'refs/remotes/' . $remote . '/' . $branch]) ?? $newCommit;
if ($cleanupWorktree instanceof \Closure) {
$cleanupWorktree();
}
$this->persistSummary([ $this->persistSummary([
'status' => 'completed', 'status' => 'completed',
'exit_code' => 0, 'exit_code' => 0,
'message' => 'Sync Git completata', 'message' => 'Sync Git completata',
'remote' => $remote, 'remote' => $remote,
'branch' => $branch, 'branch' => $branch,
'before_commit' => $currentCommit, 'before_commit' => $currentCommit,
'after_commit' => $newCommit, 'after_commit' => $newCommit,
'remote_commit' => $remoteRef, 'remote_commit' => $remoteCommit,
'working_tree_dirty' => false, 'working_tree_dirty' => $isDirty,
'repo_path' => $repoPath,
'deploy_path' => $deployPath,
'mode' => $deploySync ? 'external-repo-rsync' : 'in-place-git',
]); ]);
$this->info('Sync Git completata con successo.'); $this->info('Sync Git completata con successo.');
@ -212,9 +321,9 @@ public function handle(): int
return self::SUCCESS; return self::SUCCESS;
} }
private function runGit(array $arguments): ?string private function runGit(string $path, array $arguments): ?string
{ {
$result = Process::path(base_path()) $result = Process::path($path)
->timeout(1800) ->timeout(1800)
->run(array_merge(['git'], $arguments)); ->run(array_merge(['git'], $arguments));
@ -227,6 +336,25 @@ private function runGit(array $arguments): ?string
return $output !== '' ? $output : null; return $output !== '' ? $output : null;
} }
private function resolveSourceRepositoryPath(): ?string
{
$candidates = array_values(array_filter(array_unique([
trim((string) env('NETGESCON_GIT_SYNC_SOURCE_REPO', '')),
'/home/michele/netgescon/netgescon-day0',
'/home/michele/netgescon/netgescon-laravel',
'/var/www/netgescon_backup_20250722_220433',
'/var/www/netgescon_backup_20250722_215454',
]), static fn(string $path): bool => $path !== ''));
foreach ($candidates as $candidate) {
if (is_dir($candidate . '/.git')) {
return $candidate;
}
}
return null;
}
private function writeProgress(int $percent, string $message, string $status, ?int $exitCode = null): void private function writeProgress(int $percent, string $message, string $status, ?int $exitCode = null): void
{ {
$path = trim((string) $this->option('progress-file')); $path = trim((string) $this->option('progress-file'));
@ -241,9 +369,9 @@ private function writeProgress(int $percent, string $message, string $status, ?i
@file_put_contents($path, json_encode([ @file_put_contents($path, json_encode([
'timestamp' => now()->toIso8601String(), 'timestamp' => now()->toIso8601String(),
'percent' => max(0, min(100, $percent)), 'percent' => max(0, min(100, $percent)),
'message' => $message, 'message' => $message,
'status' => $status, 'status' => $status,
'exit_code' => $exitCode, 'exit_code' => $exitCode,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
} }
@ -259,4 +387,4 @@ private function persistSummary(array $payload): void
], $payload), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ], $payload), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
); );
} }
} }