<commit_before><?php
namespace TYPO3\Flow\Core\Migrations;

/*                                                                        *
 * This script belongs to the Flow package "Flow".                      *
 *                                                                        *
 * It is free software; you can redistribute it and/or modify it under    *
 * the terms of the GNU Lesser General Public License, either version 3   *
 * of the License, or (at your option) any later version.                 *
 *                                                                        *
 * The TYPO3 project - inspiring people to share!                         *
 *                                                                        */

use TYPO3\Flow\Utility\Files;

/**
 * The central hub of the code migration tool in Flow.
 */
class Manager {

	const STATE_NOT_MIGRATED = 0;
	const STATE_MIGRATED = 1;

	const EVENT_MIGRATION_START = 'migrationStart';
	const EVENT_MIGRATION_EXECUTE = 'migrationExecute';
	const EVENT_MIGRATION_SKIPPED_LOCAL_CHANGES = 'migrationSkippedLocalChanges';
	const EVENT_MIGRATION_SKIPPED_ALREADY_APPLIED = 'migrationSkippedAlreadyApplied';
	const EVENT_MIGRATION_EXECUTED = 'migrationExecuted';
	const EVENT_MIGRATION_DONE = 'migrationDone';

	/**
	 * @var string
	 */
	protected $packagesPath = FLOW_PATH_PACKAGES;

	/**
	 * @var array
	 */
	protected $ignoredPackageCategories = array('Framework', 'Libraries');

	/**
	 * @var array
	 */
	protected $packagesData = NULL;

	/**
	 * @var AbstractMigration[]
	 */
	protected $migrations = NULL;

	/**
	 * Callbacks to be invoked when an event is triggered
	 *
	 * @see triggerEvent()
	 * @var array
	 */
	protected $eventCallbacks;

	/**
	 * Allows to set the packages path.
	 *
	 * The level directly inside is expected to consist of package "categories"
	 * (Framework, Application, Plugins, ...).
	 *
	 * @param string $packagesPath
	 * @return void
	 */
	public function setPackagesPath($packagesPath) {
		$this->packagesPath = $packagesPath;
	}

	/**
	 * Returns the migration status for all packages.
	 *
	 * @param string $packageKey key of the package to migrate, or NULL to migrate all packages
	 * @param string $versionNumber version of the migration to fetch the status for (e.g. "20120126163610"), or NULL to consider all migrations
	 * @return array in the format [<versionNumber> => ['migration' => <AbstractMigration>, 'state' => <STATE_*>], [...]]
	 */
	public function getStatus($packageKey = NULL, $versionNumber = NULL) {
		$status = array();
		$migrations = $this->getMigrations($versionNumber);
		foreach ($this->getPackagesData($packageKey) as $packageKey => $packageData) {
			$packageStatus = array();
			foreach ($migrations as $migration) {
				if ($this->hasMigrationApplied($packageData['path'], $migration)) {
					$state = self::STATE_MIGRATED;
				} else {
					$state = self::STATE_NOT_MIGRATED;
				}
				$packageStatus[$migration->getVersionNumber()] = array('migration' => $migration, 'state' => $state);
			}
			$status[$packageKey] = $packageStatus;
		}
		return $status;
	}

	/**
	 * This iterates over available migrations and applies them to
	 * the existing packages if
	 * - the package needs the migration
	 * - is a clean git working copy
	 *
	 * @param string $packageKey key of the package to migrate, or NULL to migrate all packages
	 * @param string $versionNumber version of the migration to execute (e.g. "20120126163610"), or NULL to execute all migrations
	 * @return void
	 */
	public function migrate($packageKey = NULL, $versionNumber = NULL) {
		$packagesData = $this->getPackagesData($packageKey);
		foreach ($this->getMigrations($versionNumber) as $migration) {
			$this->triggerEvent(self::EVENT_MIGRATION_START, array($migration));
			foreach ($packagesData as $key => $packageData) {
				if ($packageKey === NULL && $this->shouldPackageBeSkippedByDefault($packageData)) {
					continue;
				}
				$this->migratePackage($key, $packageData, $migration);
			}
			$this->triggerEvent(self::EVENT_MIGRATION_DONE, array($migration));
		}
	}

	/**
	 * By default we skip "TYPO3.*" packages and all packages of the ignored categories (@see ignoredPackageCategories)
	 *
	 * @param array $packageData @see getPackagesData()
	 * @return boolean
	 */
	protected function shouldPackageBeSkippedByDefault(array $packageData) {
		if (strpos($packageData['packageKey'], 'TYPO3.') === 0) {
			return TRUE;
		}
		if (in_array($packageData['category'], $this->ignoredPackageCategories)) {
			return TRUE;
		}
		return FALSE;
	}

	/**
	 * Apply the given migration to the package and commit the result.
	 *
	 * @param string $packageKey
	 * @param array $packageData
	 * @param AbstractMigration $migration
	 * @return void
	 * @throws \RuntimeException
	 */
	protected function migratePackage($packageKey, array $packageData, AbstractMigration $migration) {
		$packagePath = $packageData['path'];
		if (!Git::isWorkingCopyClean($packagePath)) {
			$this->triggerEvent(self::EVENT_MIGRATION_SKIPPED_LOCAL_CHANGES, array($migration, $packageKey, 'working copy contains local changes'));
			return;
		}
		if ($this->hasMigrationApplied($packagePath, $migration)) {
			$this->triggerEvent(self::EVENT_MIGRATION_SKIPPED_ALREADY_APPLIED, array($migration, $packageKey, 'migration already applied'));
			return;
		}
		$this->triggerEvent(self::EVENT_MIGRATION_EXECUTE, array($migration, $packageKey));
		try {
			$migration->prepare($this->packagesData[$packageKey]);
			$migration->up();
			$migration->execute();
			$migrationResult = $this->commitMigration($packagePath, $migration);
			$this->triggerEvent(self::EVENT_MIGRATION_EXECUTED, array($migration, $packageKey, $migrationResult));
		} catch (\Exception $exception) {
			throw new \RuntimeException(sprintf('Applying migration "%s" to "%s" failed.', $migration->getIdentifier(), $packageKey), 1421692982, $exception);
		}
	}

	/**
	 * Whether or not the given migration has been applied in the given path
	 *<commit_msg>[TASK] Output actual exception message for failed core migrations<commit_after><?php
namespace TYPO3\Flow\Core\Migrations;

/*                                                                        *
 * This script belongs to the Flow package "Flow".                      *
 *                                                                        *
 * It is free software; you can redistribute it and/or modify it under    *
 * the terms of the GNU Lesser General Public License, either version 3   *
 * of the License, or (at your option) any later version.                 *
 *                                                                        *
 * The TYPO3 project - inspiring people to share!                         *
 *                                                                        */

use TYPO3\Flow\Utility\Files;

/**
 * The central hub of the code migration tool in Flow.
 */
class Manager {

	const STATE_NOT_MIGRATED = 0;
	const STATE_MIGRATED = 1;

	const EVENT_MIGRATION_START = 'migrationStart';
	const EVENT_MIGRATION_EXECUTE = 'migrationExecute';
	const EVENT_MIGRATION_SKIPPED_LOCAL_CHANGES = 'migrationSkippedLocalChanges';
	const EVENT_MIGRATION_SKIPPED_ALREADY_APPLIED = 'migrationSkippedAlreadyApplied';
	const EVENT_MIGRATION_EXECUTED = 'migrationExecuted';
	const EVENT_MIGRATION_DONE = 'migrationDone';

	/**
	 * @var string
	 */
	protected $packagesPath = FLOW_PATH_PACKAGES;

	/**
	 * @var array
	 */
	protected $ignoredPackageCategories = array('Framework', 'Libraries');

	/**
	 * @var array
	 */
	protected $packagesData = NULL;

	/**
	 * @var AbstractMigration[]
	 */
	protected $migrations = NULL;

	/**
	 * Callbacks to be invoked when an event is triggered
	 *
	 * @see triggerEvent()
	 * @var array
	 */
	protected $eventCallbacks;

	/**
	 * Allows to set the packages path.
	 *
	 * The level directly inside is expected to consist of package "categories"
	 * (Framework, Application, Plugins, ...).
	 *
	 * @param string $packagesPath
	 * @return void
	 */
	public function setPackagesPath($packagesPath) {
		$this->packagesPath = $packagesPath;
	}

	/**
	 * Returns the migration status for all packages.
	 *
	 * @param string $packageKey key of the package to migrate, or NULL to migrate all packages
	 * @param string $versionNumber version of the migration to fetch the status for (e.g. "20120126163610"), or NULL to consider all migrations
	 * @return array in the format [<versionNumber> => ['migration' => <AbstractMigration>, 'state' => <STATE_*>], [...]]
	 */
	public function getStatus($packageKey = NULL, $versionNumber = NULL) {
		$status = array();
		$migrations = $this->getMigrations($versionNumber);
		foreach ($this->getPackagesData($packageKey) as $packageKey => $packageData) {
			$packageStatus = array();
			foreach ($migrations as $migration) {
				if ($this->hasMigrationApplied($packageData['path'], $migration)) {
					$state = self::STATE_MIGRATED;
				} else {
					$state = self::STATE_NOT_MIGRATED;
				}
				$packageStatus[$migration->getVersionNumber()] = array('migration' => $migration, 'state' => $state);
			}
			$status[$packageKey] = $packageStatus;
		}
		return $status;
	}

	/**
	 * This iterates over available migrations and applies them to
	 * the existing packages if
	 * - the package needs the migration
	 * - is a clean git working copy
	 *
	 * @param string $packageKey key of the package to migrate, or NULL to migrate all packages
	 * @param string $versionNumber version of the migration to execute (e.g. "20120126163610"), or NULL to execute all migrations
	 * @return void
	 */
	public function migrate($packageKey = NULL, $versionNumber = NULL) {
		$packagesData = $this->getPackagesData($packageKey);
		foreach ($this->getMigrations($versionNumber) as $migration) {
			$this->triggerEvent(self::EVENT_MIGRATION_START, array($migration));
			foreach ($packagesData as $key => $packageData) {
				if ($packageKey === NULL && $this->shouldPackageBeSkippedByDefault($packageData)) {
					continue;
				}
				$this->migratePackage($key, $packageData, $migration);
			}
			$this->triggerEvent(self::EVENT_MIGRATION_DONE, array($migration));
		}
	}

	/**
	 * By default we skip "TYPO3.*" packages and all packages of the ignored categories (@see ignoredPackageCategories)
	 *
	 * @param array $packageData @see getPackagesData()
	 * @return boolean
	 */
	protected function shouldPackageBeSkippedByDefault(array $packageData) {
		if (strpos($packageData['packageKey'], 'TYPO3.') === 0) {
			return TRUE;
		}
		if (in_array($packageData['category'], $this->ignoredPackageCategories)) {
			return TRUE;
		}
		return FALSE;
	}

	/**
	 * Apply the given migration to the package and commit the result.
	 *
	 * @param string $packageKey
	 * @param array $packageData
	 * @param AbstractMigration $migration
	 * @return void
	 * @throws \RuntimeException
	 */
	protected function migratePackage($packageKey, array $packageData, AbstractMigration $migration) {
		$packagePath = $packageData['path'];
		if (!Git::isWorkingCopyClean($packagePath)) {
			$this->triggerEvent(self::EVENT_MIGRATION_SKIPPED_LOCAL_CHANGES, array($migration, $packageKey, 'working copy contains local changes'));
			return;
		}
		if ($this->hasMigrationApplied($packagePath, $migration)) {
			$this->triggerEvent(self::EVENT_MIGRATION_SKIPPED_ALREADY_APPLIED, array($migration, $packageKey, 'migration already applied'));
			return;
		}
		$this->triggerEvent(self::EVENT_MIGRATION_EXECUTE, array($migration, $packageKey));
		try {
			$migration->prepare($this->packagesData[$packageKey]);
			$migration->up();
			$migration->execute();
			$migrationResult = $this->commitMigration($packagePath, $migration);
			$this->triggerEvent(self::EVENT_MIGRATION_EXECUTED, array($migration, $packageKey, $migrationResult));
		} catch (\Exception $exception) {
			throw new \RuntimeException(sprintf('Applying migration "%s" to "%s" failed: "%s"', $migration->getIdentifier(), $packageKey, $exception->getMessage()), 1421692982, $exception);
		}
	}

	/**
	 * Whether or not the given migration has been applied in the given path
	 *
{'fraction_non_alphanumeric': 0.12081369387248822, 'fraction_numerical': 0.007773091871330522, 'mean_word_length': 3.393025790047221, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 70, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2388389', 'n_tokens_mistral': 3821, 'n_tokens_neox': 3435, 'n_words': 946}