From 648f8901f7be12170fc7a32167ee7c789f15d718 Mon Sep 17 00:00:00 2001
From: phenaproxima <phenaproxima@205645.no-reply.drupal.org>
Date: Mon, 23 Aug 2021 19:20:46 +0000
Subject: [PATCH] Issue #3229485 by phenaproxima, tedbow: Clean up some
 exclusion code

---
 automatic_updates.services.yml          |  2 +-
 src/Updater.php                         | 65 ++++++++++++++++------
 tests/src/Functional/ExclusionsTest.php | 74 +++++++++++++++++++++++++
 3 files changed, 122 insertions(+), 19 deletions(-)
 create mode 100644 tests/src/Functional/ExclusionsTest.php

diff --git a/automatic_updates.services.yml b/automatic_updates.services.yml
index c5638b40d3..d1ab120bd3 100644
--- a/automatic_updates.services.yml
+++ b/automatic_updates.services.yml
@@ -4,7 +4,7 @@ services:
     arguments: ['@keyvalue.expirable', '@datetime.time', 24]
   automatic_updates.updater:
     class: Drupal\automatic_updates\Updater
-    arguments: ['@state', '@string_translation','@automatic_updates.beginner', '@automatic_updates.stager', '@automatic_updates.cleaner', '@automatic_updates.committer' , '@file_system', '@event_dispatcher']
+    arguments: ['@state', '@string_translation','@automatic_updates.beginner', '@automatic_updates.stager', '@automatic_updates.cleaner', '@automatic_updates.committer' , '@file_system', '@event_dispatcher', '%app.root%', '%site.path%']
   automatic_updates.staged_package_validator:
     class: Drupal\automatic_updates\Validation\StagedProjectsValidation
     arguments: ['@string_translation', '@automatic_updates.updater' ]
diff --git a/src/Updater.php b/src/Updater.php
index 7c2c351038..65f908bbdf 100644
--- a/src/Updater.php
+++ b/src/Updater.php
@@ -79,6 +79,20 @@ class Updater {
    */
   protected $eventDispatcher;
 
+  /**
+   * The Drupal root.
+   *
+   * @var string
+   */
+  protected $appRoot;
+
+  /**
+   * The current site directory, relative to the Drupal root.
+   *
+   * @var string
+   */
+  protected $sitePath;
+
   /**
    * Constructs an Updater object.
    *
@@ -98,8 +112,12 @@ class Updater {
    *   The file system service.
    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
    *   The event dispatcher service.
+   * @param string $app_root
+   *   The Drupal root.
+   * @param string $site_path
+   *   The current site directory, relative to the Drupal root.
    */
-  public function __construct(StateInterface $state, TranslationInterface $translation, BeginnerInterface $beginner, StagerInterface $stager, CleanerInterface $cleaner, CommitterInterface $committer, FileSystemInterface $file_system, EventDispatcherInterface $event_dispatcher) {
+  public function __construct(StateInterface $state, TranslationInterface $translation, BeginnerInterface $beginner, StagerInterface $stager, CleanerInterface $cleaner, CommitterInterface $committer, FileSystemInterface $file_system, EventDispatcherInterface $event_dispatcher, string $app_root, string $site_path) {
     $this->state = $state;
     $this->beginner = $beginner;
     $this->stager = $stager;
@@ -108,6 +126,8 @@ class Updater {
     $this->setStringTranslation($translation);
     $this->fileSystem = $file_system;
     $this->eventDispatcher = $event_dispatcher;
+    $this->appRoot = $app_root;
+    $this->sitePath = $site_path;
   }
 
   /**
@@ -171,34 +191,43 @@ class Updater {
    * Gets the paths that should be excluded from the staging area.
    *
    * @return string[]
-   *   The paths relative to the active directory to exclude.
+   *   The paths to exclude, relative to the active directory.
    */
-  private function getExclusions(): array {
+  protected function getExclusions(): array {
     $exclusions = [];
-    $make_relative = function ($path) {
-      return str_replace(static::getActiveDirectory() . '/', '', $path);
-    };
     if ($public = $this->fileSystem->realpath('public://')) {
-      $exclusions[] = $make_relative($public);
+      $exclusions[] = $public;
     }
     if ($private = $this->fileSystem->realpath('private://')) {
-      $exclusions[] = $make_relative($private);
+      $exclusions[] = $private;
     }
-    /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
-    $module_handler = \Drupal::service('module_handler');
-    $module_path = $this->fileSystem->realpath($module_handler->getModule('automatic_updates')->getPath());
-    if (is_dir("$module_path/.git")) {
-      // If the current module is git clone. Don't copy it.
-      $exclusions[] = $make_relative($module_path);
+    // If this module is a git clone, exclude it.
+    if (is_dir(__DIR__ . '/../.git')) {
+      $exclusions[] = $this->fileSystem->realpath(__DIR__ . '/..');
     }
-    $settings_files = ['settings.php', 'settings.local.php', 'services.yml'];
+
+    // Exclude site-specific settings files.
+    $settings_files = [
+      'settings.php',
+      'settings.local.php',
+      'services.yml',
+    ];
     foreach ($settings_files as $settings_file) {
-      $file_path = "sites/default/$settings_file";
+      $file_path = implode(DIRECTORY_SEPARATOR, [
+        $this->appRoot,
+        $this->sitePath,
+        $settings_file,
+      ]);
+      $file_path = $this->fileSystem->realpath($file_path);
       if (file_exists($file_path)) {
-        $exclusions[] = $make_relative($this->fileSystem->realpath("sites/default/$settings_file"));
+        $exclusions[] = $file_path;
       }
     }
-    return $exclusions;
+
+    $make_relative = function (string $path): string {
+      return str_replace($this->getActiveDirectory() . '/', '', $path);
+    };
+    return array_map($make_relative, $exclusions);
   }
 
   /**
diff --git a/tests/src/Functional/ExclusionsTest.php b/tests/src/Functional/ExclusionsTest.php
new file mode 100644
index 0000000000..8d0832eb8c
--- /dev/null
+++ b/tests/src/Functional/ExclusionsTest.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Drupal\Tests\automatic_updates\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests exclusion of certain files and directories from the staging area.
+ *
+ * @group automatic_updates
+ */
+class ExclusionsTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['automatic_updates'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * The names of site-specific settings files to mock.
+   *
+   * @var string[]
+   */
+  private const SETTINGS_FILES = [
+    'settings.php',
+    'settings.local.php',
+    'services.yml',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    foreach (static::SETTINGS_FILES as $settings_file) {
+      $settings_file = "$this->siteDirectory/$settings_file";
+      touch($settings_file);
+      $this->assertFileExists($settings_file);
+    }
+  }
+
+  /**
+   * Tests that certain files and directories are not staged.
+   *
+   * @covers \Drupal\automatic_updates\Updater::getExclusions
+   */
+  public function testExclusions(): void {
+    /** @var \Drupal\automatic_updates\Updater $updater */
+    $updater = $this->container->get('automatic_updates.updater');
+
+    $reflector = new \ReflectionObject($updater);
+    $method = $reflector->getMethod('getExclusions');
+    $method->setAccessible(TRUE);
+    $exclusions = $method->invoke($updater);
+
+    $this->assertContains("$this->siteDirectory/files", $exclusions);
+    $this->assertContains("$this->siteDirectory/private", $exclusions);
+    foreach (static::SETTINGS_FILES as $settings_file) {
+      $this->assertContains("$this->siteDirectory/$settings_file", $exclusions);
+    }
+    if (is_dir(__DIR__ . '/../../../.git')) {
+      $module_path = $this->container->get('extension.list.module')
+        ->getPath('automatic_updates');
+      $this->assertContains($module_path, $exclusions);
+    }
+  }
+
+}
-- 
GitLab