Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions features/testing.feature
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,41 @@ Feature: Test that WP-CLI loads.
sqlite
"""

Scenario: Leave files a scenario created before installing out of the installation cache
Given an empty directory
And a scenario-files/wp-cli.yml file:
"""
user: admin
"""
And a WP installation in 'scenario-files/'
Then the scenario-files/wp-cli.yml file should exist

When I run `php -r "rename('scenario-files', 'first');"`

# Reuse the subdirectory to select the same installation cache.
Given a WP installation in 'scenario-files/'
Then the scenario-files/wp-config.php file should exist
And the scenario-files/wp-cli.yml file should not exist

@skip-windows
Scenario: Leave what a scenario linked to before installing out of the installation cache
Given an empty directory
And a linked-files/wp-cli.yml file:
"""
user: admin
"""
When I run `php -r "mkdir('scenario-links'); symlink(getcwd() . '/linked-files', 'scenario-links/linked');"`

Given a WP installation in 'scenario-links'
Then the scenario-links/linked/wp-cli.yml file should exist

When I run `php -r "rename('scenario-links', 'first');"`

# Reuse the subdirectory to select the same installation cache.
Given a WP installation in 'scenario-links'
Then the scenario-links/wp-config.php file should exist
And the scenario-links/linked directory should not exist

@require-sqlite
Scenario: Restore independent SQLite databases from the installation cache
Given a WP installation in 'cached'
Expand Down
64 changes: 63 additions & 1 deletion src/Context/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -1843,7 +1843,8 @@ public function download_wp( $subdir = '', $version = '' ): void {

$dest_dir = rtrim( $this->variables['RUN_DIR'], '/\\' ) . ( $subdir ? DIRECTORY_SEPARATOR . $subdir : '' );

if ( $subdir ) {
// The scenario may have created files in the subdirectory already.
if ( $subdir && ! is_dir( $dest_dir ) ) {
mkdir( $dest_dir, 0777, true /*recursive*/ );
}

Expand Down Expand Up @@ -1937,6 +1938,11 @@ public function install_wp( $subdir = '', $version = '' ): void {
$this->create_db();
}
$this->create_run_dir();

// Files the scenario created before installing belong to that scenario only, and must not
// end up in the installation cache that later scenarios are set up from.
$scenario_files = self::list_dir( '' !== $subdir ? ( $this->variables['RUN_DIR'] . "/$subdir" ) : $this->variables['RUN_DIR'] );

$this->download_wp( $subdir, $version );
$this->create_config( $subdir, $config_extra_php );

Expand Down Expand Up @@ -1969,6 +1975,7 @@ public function install_wp( $subdir = '', $version = '' ): void {
}

self::dir_diff_copy( $run_dir, self::$cache_dir, $install_cache_path );
self::remove_from_dir( $install_cache_path, $scenario_files );
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

if ( 'sqlite' !== self::$db_type ) {
$mysqldump_binary = Utils\get_sql_dump_command();
Expand Down Expand Up @@ -2239,6 +2246,61 @@ private static function log_run_times_after_scenario( $scope ): void {
}
}

/**
* List the files and directories within a directory, relative to it.
*
* Symbolic links are listed, but not followed.
*
* @param string $dir
* @return array<string, bool> Whether each entry is a symbolic link, keyed by relative path, directories after
* their contents. Empty if the directory does not exist.
*/
private static function list_dir( $dir ): array {
$dir = rtrim( $dir, '/\\' );
if ( ! is_dir( $dir ) ) {
return [];
}

$paths = [];
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator( $dir, \FilesystemIterator::SKIP_DOTS ),
Comment thread
schlessera marked this conversation as resolved.
Resolved
\RecursiveIteratorIterator::CHILD_FIRST
);

/**
* @var \SplFileInfo $item
*/
foreach ( $iterator as $item ) {
$paths[ substr( $item->getPathname(), strlen( $dir ) + 1 ) ] = $item->isLink();
Comment thread
schlessera marked this conversation as resolved.
Resolved
}

return $paths;
}

/**
* Remove the given files from a directory, as well as the given directories once they are empty.
*
* A copy of what a symbolic link pointed to is removed with all its contents.
*
* @param string $dir
* @param array<string, bool> $paths Whether each entry was a symbolic link, keyed by relative path, directories
* after their contents.
*/
private static function remove_from_dir( $dir, array $paths ): void {
foreach ( $paths as $path => $was_link ) {
$full_path = "{$dir}/{$path}";

if ( $was_link && is_dir( $full_path ) && ! is_link( $full_path ) ) {
self::remove_dir( $full_path );
} elseif ( is_dir( $full_path ) && ! is_link( $full_path ) ) {
// Only succeeds for directories that are empty by now.
@rmdir( $full_path );
} elseif ( file_exists( $full_path ) || is_link( $full_path ) ) {
unlink( $full_path );
}
}
}

/**
* Copy files in updated directory that are not in source directory to copy directory. ("Incremental backup".)
* Note: does not deal with changed files (ie does not compare file contents for changes), for speed reasons.
Expand Down
Loading