more refactoring of safe install

Signed-off-by: Andy Miller <rhuk@mac.com>
This commit is contained in:
Andy Miller
2025-10-17 20:47:48 -06:00
parent 4650bd073e
commit a0b64b6d88
2 changed files with 100 additions and 35 deletions

View File

@@ -171,7 +171,8 @@ class SafeUpgradeService
Folder::create(dirname($packagePath));
$this->reportProgress('installing', 'Preparing staged package...', null);
$this->stageExtractedPackage($extractedPath, $packagePath);
$stagingMode = $this->stageExtractedPackage($extractedPath, $packagePath);
$this->reportProgress('installing', 'Preparing staged package...', null, ['mode' => $stagingMode]);
$this->carryOverRootDotfiles($packagePath);
@@ -186,7 +187,6 @@ class SafeUpgradeService
$this->reportProgress('snapshot', 'Creating backup snapshot...', null);
$this->createBackupSnapshot($entries, $backupPath);
$this->syncGitDirectory($this->rootPath, $backupPath);
$manifest = $this->buildManifest($stageId, $targetVersion, $packagePath, $backupPath, $entries);
$manifestPath = $stagePath . DIRECTORY_SEPARATOR . 'manifest.json';
@@ -199,12 +199,10 @@ class SafeUpgradeService
$this->copyEntries($entries, $packagePath, $this->rootPath, 'installing', 'Deploying');
} catch (Throwable $e) {
$this->copyEntries($entries, $backupPath, $this->rootPath, 'installing', 'Restoring');
$this->syncGitDirectory($backupPath, $this->rootPath);
throw new RuntimeException('Failed to promote staged Grav release.', 0, $e);
}
$this->reportProgress('finalizing', 'Finalizing upgrade...', null);
$this->syncGitDirectory($backupPath, $this->rootPath);
$this->persistManifest($manifest);
$this->pruneOldSnapshots();
Folder::delete($stagePath);
@@ -234,20 +232,22 @@ class SafeUpgradeService
return $entries;
}
private function stageExtractedPackage(string $sourcePath, string $packagePath): void
private function stageExtractedPackage(string $sourcePath, string $packagePath): string
{
if (is_dir($packagePath)) {
Folder::delete($packagePath);
}
if (@rename($sourcePath, $packagePath)) {
return;
return 'move';
}
Folder::create($packagePath);
$entries = $this->collectPackageEntries($sourcePath);
$this->copyEntries($entries, $sourcePath, $packagePath, 'installing', 'Staging');
Folder::delete($sourcePath);
return 'copy';
}
private function createBackupSnapshot(array $entries, string $backupPath): void
@@ -360,7 +360,6 @@ class SafeUpgradeService
$this->reportProgress('rollback', 'Restoring snapshot...', null);
$this->copyEntries($entries, $backupPath, $this->rootPath, 'rollback', 'Restoring');
$this->syncGitDirectory($backupPath, $this->rootPath);
$this->markRollback($manifest['id']);
return $manifest;
@@ -552,14 +551,8 @@ class SafeUpgradeService
continue;
}
// Skip caches to avoid stale data.
if (in_array($relative, ['cache', 'tmp'], true)) {
Folder::create($stage);
continue;
}
Folder::create(dirname($stage));
Folder::rcopy($live, $stage, true);
// Use empty placeholders to preserve directory structure without duplicating data.
Folder::create($stage);
}
}
@@ -713,25 +706,6 @@ class SafeUpgradeService
* @param string $destination
* @return void
*/
private function syncGitDirectory(string $source, string $destination): void
{
if (!$source || !$destination) {
return;
}
$sourceGit = rtrim($source, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '.git';
if (!is_dir($sourceGit)) {
return;
}
$destinationGit = rtrim($destination, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '.git';
if (is_dir($destinationGit)) {
Folder::delete($destinationGit);
}
Folder::rcopy($sourceGit, $destinationGit, true);
}
/**
* Persist manifest into Grav data directory.
*

View File

@@ -46,6 +46,14 @@ class SelfupgradeCommand extends GpmCommand
private $upgrader;
/** @var string|null */
private $lastProgressMessage = null;
/** @var float|null */
private $operationTimerStart = null;
/** @var string|null */
private $currentProgressStage = null;
/** @var float|null */
private $currentStageStartedAt = null;
/** @var array */
private $currentStageExtras = [];
/** @var string */
protected $all_yes;
@@ -235,6 +243,7 @@ class SelfupgradeCommand extends GpmCommand
$this->file = $this->download($update);
$io->write(' |- Installing upgrade... ');
$this->operationTimerStart = microtime(true);
$installation = $this->upgrade();
$error = 0;
@@ -443,6 +452,13 @@ class SelfupgradeCommand extends GpmCommand
$this->lastProgressMessage = null;
$this->upgradeGrav($this->file);
$this->finalizeStageTracking();
$elapsed = null;
if (null !== $this->operationTimerStart) {
$elapsed = microtime(true) - $this->operationTimerStart;
$this->operationTimerStart = null;
}
$errorCode = Installer::lastErrorCode();
if ($errorCode) {
@@ -454,6 +470,10 @@ class SelfupgradeCommand extends GpmCommand
return false;
}
if (null !== $elapsed) {
$io->writeln(sprintf(' |- Safe upgrade staging completed in %s', $this->formatDuration($elapsed)));
}
$io->write("\x0D");
// extra white spaces to clear out the buffer properly
$io->writeln(' |- Installing upgrade... <green>ok</green> ');
@@ -528,13 +548,19 @@ class SelfupgradeCommand extends GpmCommand
private function handleServiceProgress(string $stage, string $message, ?int $percent = null, array $extra = []): void
{
$this->trackStageProgress($stage, $message, $extra);
if ($this->lastProgressMessage === $message) {
return;
}
$this->lastProgressMessage = $message;
$io = $this->getIO();
$io->writeln(sprintf(' |- %s', $message));
$suffix = '';
if (null !== $percent) {
$suffix = sprintf(' (%d%%)', $percent);
}
$io->writeln(sprintf(' |- %s%s', $message, $suffix));
}
private function ensureExecutablePermissions(): void
@@ -560,4 +586,69 @@ class SelfupgradeCommand extends GpmCommand
}
}
}
private function trackStageProgress(string $stage, string $message, array $extra = []): void
{
$now = microtime(true);
if (null !== $this->currentProgressStage && $stage !== $this->currentProgressStage && null !== $this->currentStageStartedAt) {
$elapsed = $now - $this->currentStageStartedAt;
$this->emitStageSummary($this->currentProgressStage, $elapsed, $this->currentStageExtras);
$this->currentStageExtras = [];
}
if ($stage !== $this->currentProgressStage) {
$this->currentProgressStage = $stage;
$this->currentStageStartedAt = $now;
$this->currentStageExtras = [];
}
if (!isset($this->currentStageExtras['label'])) {
$this->currentStageExtras['label'] = $message;
}
if ($extra) {
$this->currentStageExtras = array_merge($this->currentStageExtras, $extra);
}
}
private function finalizeStageTracking(): void
{
if (null !== $this->currentProgressStage && null !== $this->currentStageStartedAt) {
$elapsed = microtime(true) - $this->currentStageStartedAt;
$this->emitStageSummary($this->currentProgressStage, $elapsed, $this->currentStageExtras);
}
$this->currentProgressStage = null;
$this->currentStageStartedAt = null;
$this->currentStageExtras = [];
}
private function emitStageSummary(string $stage, float $seconds, array $extra = []): void
{
$io = $this->getIO();
$label = $extra['label'] ?? ucfirst($stage);
$modeText = '';
if (isset($extra['mode'])) {
$modeText = sprintf(' [%s]', $extra['mode']);
}
$io->writeln(sprintf(' |- %s completed in %s%s', $label, $this->formatDuration($seconds), $modeText));
}
private function formatDuration(float $seconds): string
{
if ($seconds < 1) {
return sprintf('%0.3fs', $seconds);
}
$minutes = (int)floor($seconds / 60);
$remaining = $seconds - ($minutes * 60);
if ($minutes === 0) {
return sprintf('%0.1fs', $remaining);
}
return sprintf('%dm %0.1fs', $minutes, $remaining);
}
}