config_preview_deploy-1.0.0-alpha3/src/PatchTool.php

src/PatchTool.php
<?php

declare(strict_types=1);

namespace Drupal\config_preview_deploy;

use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\Process\Process;

/**
 * Service for applying unified diff patches using CLI patch tool.
 */
class PatchTool {

  use StringTranslationTrait;

  /**
   * The file system service.
   */
  protected FileSystemInterface $fileSystem;

  /**
   * The logger service.
   */
  protected LoggerChannelInterface $logger;

  /**
   * Constructs a PatchTool object.
   */
  public function __construct(
    FileSystemInterface $fileSystem,
    LoggerChannelFactoryInterface $loggerFactory,
  ) {
    $this->fileSystem = $fileSystem;
    $this->logger = $loggerFactory->get('config_preview_deploy');
  }

  /**
   * Check if CLI patch tool is available.
   */
  public function isAvailable(): bool {
    static $available = NULL;

    if ($available === NULL) {
      $process = new Process(['which', 'patch']);
      $process->run();
      $available = $process->isSuccessful();
    }

    return $available;
  }

  /**
   * Get patch tool version and information.
   */
  public function getToolInfo(): array {
    if (!$this->isAvailable()) {
      return ['available' => FALSE];
    }

    $process = new Process(['patch', '--version']);
    $process->run();

    $output = $process->getOutput();
    $version = 'unknown';

    // Extract version from output (e.g., "GNU patch 2.7.6")
    if (preg_match('/patch\s+(\d+\.\d+(?:\.\d+)?)/', $output, $matches)) {
      $version = $matches[1];
    }

    return [
      'available' => TRUE,
      'version' => $version,
      'output' => $output,
    ];
  }

  /**
   * Apply unified diff to files in a directory.
   *
   * @param string $unifiedDiff
   *   The complete unified diff content.
   * @param string $baseDirectory
   *   The base directory containing files to patch.
   *
   * @throws \Drupal\config_preview_deploy\Exception\PatchException
   *   If patch application fails.
   */
  public function applyMultiFileDiff(string $unifiedDiff, string $baseDirectory): void {
    if (!$this->isAvailable()) {
      throw new PatchException($this->t('Patch tool not available')->render());
    }

    if (!is_dir($baseDirectory)) {
      throw new PatchException($this->t('Base directory does not exist: @dir', ['@dir' => $baseDirectory])->render());
    }

    // Execute patch command with diff via stdin.
    $process = new Process([
      'patch',
      '--directory=' . $baseDirectory,
    // Remove one directory level from paths.
      '--strip=1',
    // Don't create .orig files.
      '--no-backup-if-mismatch',
    // Minimize output.
      '--silent',
    ]);

    $process->setInput($unifiedDiff);
    $process->setTimeout(60);
    $process->run();

    if (!$process->isSuccessful()) {
      $errorOutput = $process->getErrorOutput();
      $standardOutput = $process->getOutput();
      $exitCode = $process->getExitCode();

      // Combine both outputs to get complete error information.
      $fullErrorMessage = trim($errorOutput . "\n" . $standardOutput);

      // If no output at all, provide a meaningful default.
      if (empty($fullErrorMessage)) {
        $fullErrorMessage = "Patch command failed with exit code {$exitCode}";
      }

      $this->logger->error('Patch application failed: @error (exit code: @code)', [
        '@error' => $fullErrorMessage,
        '@code' => $exitCode,
      ]);

      throw new PatchException(
        $this->t('Patch application failed: @error', ['@error' => $fullErrorMessage])->render(),
        $exitCode
      );
    }
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc