diff --git a/.circleci/config.yml b/.circleci/config.yml
index c0ee4be..8af941c 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -5,7 +5,7 @@ aliases:
- &container_config
working_directory: /workspace/code
docker:
- - image: drevops/ci-runner:24.4.0
+ - image: drevops/ci-runner:24.11.0
environment:
COMPOSER_ALLOW_SUPERUSER: 1
DEPLOY_SSH_FINGERPRINT: *deploy_ssh_fingerprint
diff --git a/composer.json b/composer.json
index 03df261..095c6c7 100644
--- a/composer.json
+++ b/composer.json
@@ -62,8 +62,8 @@
],
"lint": [
"phpcs",
- "phpmd --exclude vendor,vendor-bin,node_modules . text phpmd.xml",
"phpstan --memory-limit=-1",
+ "phpmd --exclude vendor,vendor-bin,node_modules,TokenTest.php . text phpmd.xml",
"rector --clear-cache --dry-run"
],
"lint-fix": [
@@ -71,6 +71,6 @@
"phpcbf"
],
"reset": "rm -rf vendor vendor-bin composer.lock",
- "test": "if [ \"${XDEBUG_MODE}\" = 'coverage' ]; then phpunit; else phpunit --no-coverage; fi"
+ "test": "phpunit"
}
}
diff --git a/phpcs.xml b/phpcs.xml
index 3be9ccb..b0f5c20 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -23,6 +23,12 @@
*.test
+
+
+ *.Test\.php
+ *.TestCase\.php
+ *.test
+
*.Test\.php
diff --git a/rector.php b/rector.php
index d18c2c2..cafede2 100644
--- a/rector.php
+++ b/rector.php
@@ -14,6 +14,7 @@
use Rector\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector;
use Rector\CodeQuality\Rector\Empty_\SimplifyEmptyCheckOnEmptyArrayRector;
+use Rector\CodingStyle\Rector\Catch_\CatchExceptionNameMatchingTypeRector;
use Rector\CodingStyle\Rector\ClassMethod\NewlineBeforeNewAssignSetRector;
use Rector\CodingStyle\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector;
use Rector\CodingStyle\Rector\Stmt\NewlineAfterStatementRector;
@@ -41,6 +42,7 @@
$rectorConfig->skip([
// Rules added by Rector's rule sets.
+ CatchExceptionNameMatchingTypeRector::class,
CountArrayToEmptyArrayComparisonRector::class,
DisallowedEmptyRuleFixerRector::class,
InlineArrayReturnAssignRector::class,
diff --git a/src/Commands/ArtifactCommand.php b/src/Commands/ArtifactCommand.php
index 7f19ecd..a0729dc 100644
--- a/src/Commands/ArtifactCommand.php
+++ b/src/Commands/ArtifactCommand.php
@@ -4,7 +4,7 @@
namespace DrevOps\GitArtifact\Commands;
-use DrevOps\GitArtifact\Git\ArtifactGit;
+use CzProject\GitPhp\GitException;
use DrevOps\GitArtifact\Git\ArtifactGitRepository;
use DrevOps\GitArtifact\Traits\FilesystemTrait;
use DrevOps\GitArtifact\Traits\LogTrait;
@@ -15,7 +15,6 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
-use Symfony\Component\Finder\Finder;
/**
* Artifact Command.
@@ -33,15 +32,21 @@ class ArtifactCommand extends Command {
const GIT_REMOTE_NAME = 'dst';
+ const MODE_BRANCH = 'branch';
+
+ const MODE_FORCE_PUSH = 'force-push';
+
+ const MODE_DIFF = 'diff';
+
/**
- * Represent to current repository.
+ * Current Git repository.
*/
- protected ArtifactGitRepository $gitRepository;
+ protected ArtifactGitRepository $repo;
/**
- * Source path of git repository.
+ * Path to the dir of the source git repository.
*/
- protected string $sourcePathGitRepository = '';
+ protected string $sourceDir = '';
/**
* Mode in which current build is going to run.
@@ -71,7 +76,7 @@ class ArtifactCommand extends Command {
protected string $remoteName = '';
/**
- * Remote URL includes uri or local path.
+ * Remote URL includes URI or local path.
*/
protected string $remoteUrl = '';
@@ -85,7 +90,7 @@ class ArtifactCommand extends Command {
/**
* Commit message with optional tokens.
*/
- protected string $message = '';
+ protected string $commitMessage = '';
/**
* Flag to specify if using dry run.
@@ -98,7 +103,7 @@ class ArtifactCommand extends Command {
protected bool $needCleanup = TRUE;
/**
- * Path to log file.
+ * Path to the log file.
*/
protected string $logFile = '';
@@ -108,9 +113,9 @@ class ArtifactCommand extends Command {
protected bool $showChanges = FALSE;
/**
- * Artifact build result.
+ * Flag to specify if push was successful.
*/
- protected bool $result = FALSE;
+ protected bool $pushSuccessful = FALSE;
/**
* Internal option to set current timestamp.
@@ -118,33 +123,24 @@ class ArtifactCommand extends Command {
protected int $now;
/**
- * Output.
+ * Output interface.
*/
protected OutputInterface $output;
- /**
- * Git wrapper.
- */
- protected ArtifactGit $git;
-
/**
* Artifact constructor.
*
- * @param \DrevOps\GitArtifact\Git\ArtifactGit $gitWrapper
- * Git wrapper.
- * @param \Symfony\Component\Filesystem\Filesystem $fsFileSystem
- * File system.
* @param string|null $name
+ * File system.
+ * @param \Symfony\Component\Filesystem\Filesystem $fs
* Command name.
*/
public function __construct(
- ?ArtifactGit $gitWrapper = NULL,
- ?Filesystem $fsFileSystem = NULL,
?string $name = NULL,
+ ?Filesystem $fs = NULL,
) {
parent::__construct($name);
- $this->fs = is_null($fsFileSystem) ? new Filesystem() : $fsFileSystem;
- $this->git = is_null($gitWrapper) ? new ArtifactGit() : $gitWrapper;
+ $this->fs = is_null($fs) ? new Filesystem() : $fs;
}
/**
@@ -157,50 +153,24 @@ protected function configure(): void {
$this->addArgument('remote', InputArgument::REQUIRED, 'Path to the remote git repository.');
+ // @formatter:off
+ // phpcs:disable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
+ // phpcs:disable Drupal.WhiteSpace.Comma.TooManySpaces
$this
- ->addOption('branch', NULL, InputOption::VALUE_REQUIRED, 'Destination branch with optional tokens.', '[branch]')
- ->addOption(
- 'gitignore',
- NULL,
- InputOption::VALUE_REQUIRED,
- 'Path to gitignore file to replace current .gitignore.'
- )
- ->addOption(
- 'message',
- NULL,
- InputOption::VALUE_REQUIRED,
- 'Commit message with optional tokens.',
- 'Deployment commit'
- )
- ->addOption(
- 'mode',
- NULL,
- InputOption::VALUE_REQUIRED,
- 'Mode of artifact build: branch, force-push or diff. Defaults to force-push.',
- 'force-push'
- )
- ->addOption('no-cleanup', NULL, InputOption::VALUE_NONE, 'Do not cleanup after run.')
- ->addOption('now', NULL, InputOption::VALUE_REQUIRED, 'Internal value used to set internal time.')
- ->addOption('dry-run', NULL, InputOption::VALUE_NONE, 'Run without pushing to the remote repository.')
- ->addOption('log', NULL, InputOption::VALUE_REQUIRED, 'Path to the log file.')
- ->addOption(
- 'root',
- NULL,
- InputOption::VALUE_REQUIRED,
- 'Path to the root for file path resolution. If not specified, current directory is used.'
- )
- ->addOption(
- 'show-changes',
- NULL,
- InputOption::VALUE_NONE,
- 'Show changes made to the repo by the build in the output.'
- )
- ->addOption(
- 'src',
- NULL,
- InputOption::VALUE_REQUIRED,
- 'Directory where source repository is located. If not specified, root directory is used.'
- );
+ ->addOption('branch', NULL, InputOption::VALUE_REQUIRED, 'Destination branch with optional tokens.', '[branch]')
+ ->addOption('dry-run', NULL, InputOption::VALUE_NONE, 'Run without pushing to the remote repository.')
+ ->addOption('gitignore', NULL, InputOption::VALUE_REQUIRED, 'Path to gitignore file to replace current .gitignore.')
+ ->addOption('message', NULL, InputOption::VALUE_REQUIRED, 'Commit message with optional tokens.', 'Deployment commit')
+ ->addOption('mode', NULL, InputOption::VALUE_REQUIRED, 'Mode of artifact build: branch, force-push or diff. Defaults to force-push.', 'force-push')
+ ->addOption('no-cleanup', NULL, InputOption::VALUE_NONE, 'Do not cleanup after run.')
+ ->addOption('now', NULL, InputOption::VALUE_REQUIRED, 'Internal value used to set internal time.')
+ ->addOption('log', NULL, InputOption::VALUE_REQUIRED, 'Path to the log file.')
+ ->addOption('root', NULL, InputOption::VALUE_REQUIRED, 'Path to the root for file path resolution. If not specified, current directory is used.')
+ ->addOption('show-changes', NULL, InputOption::VALUE_NONE, 'Show changes made to the repo by the build in the output.')
+ ->addOption('src', NULL, InputOption::VALUE_REQUIRED, 'Directory where source repository is located. If not specified, root directory is used.');
+ // @formatter:on
+ // phpcs:enable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
+ // phpcs:enable Drupal.WhiteSpace.Comma.TooManySpaces
}
/**
@@ -217,39 +187,25 @@ protected function configure(): void {
* @throws \Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
- if ($input->getOption('log')) {
- $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
- }
-
$this->output = $output;
- $logfile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . time() . '-artifact-log.log';
- $this->logger = self::loggerCreate((string) $this->getName(), $output, $logfile);
+ $this->logPrepare((string) $this->getName(), $input, $output);
$remote = $input->getArgument('remote');
- if (!is_string($remote)) {
- throw new \RuntimeException('Remote argument must be a string');
+ if (empty($remote) || !is_string($remote)) {
+ throw new \RuntimeException('Remote argument must be a non-empty string');
}
try {
- // Now we have all what we need.
- // Let process artifact function.
$this->checkRequirements();
- $this->processArtifact($remote, $input->getOptions());
-
- // Dump log file and clean tmp log file.
- if ($this->fs->exists($logfile)) {
- if (!empty($this->logFile)) {
- $this->fs->copy($logfile, $this->logFile);
- }
+ $this->resolveOptions($remote, $input->getOptions());
- $this->fs->remove($logfile);
- }
+ $this->doExecute();
}
catch (\Exception $exception) {
$this->output->writeln([
- 'Deployment failed.',
+ 'Processing failed with an error:',
'' . $exception->getMessage() . '',
]);
@@ -264,207 +220,81 @@ protected function execute(InputInterface $input, OutputInterface $output): int
/**
* Assemble a code artifact from your codebase.
*
- * @param string $remote
- * Path to the remote git repository.
- * @param array $options
- * Options.
- *
- * @throws \Exception
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
- protected function processArtifact(string $remote, array $options): void {
+ protected function doExecute(): void {
+ $error = NULL;
+
try {
- $error = NULL;
- $this->logDebug('Debug messages enabled');
- // Let resolve options into properties first.
- $this->resolveOptions($remote, $options);
- $this->setupRemoteForRepository();
+ $this->repo->addRemote($this->remoteName, $this->remoteUrl);
+
$this->showInfo();
- $this->prepareArtifact();
+
+ // Do not optimize this into a chained call to make it easier to debug.
+ $repo = $this->repo;
+ $repo->switchToBranch($this->artifactBranch, TRUE);
+ $repo->removeSubRepositories();
+ $repo->disableLocalExclude();
+ $repo->removeIgnoredFiles();
+ $repo->removeOtherFiles();
+ $changes = $repo->commitAllChanges($this->commitMessage);
+
+ if ($this->showChanges) {
+ $this->output->writeln(sprintf('Added changes: %s', implode("\n", $changes)));
+ $this->logNotice(sprintf('Added changes: %s', implode("\n", $changes)));
+ }
if ($this->isDryRun) {
- $this->output->writeln('Cowardly refusing to push to remote. Use without --dry-run to perform an actual push.');
+ $this->output->writeln('Cowardly refusing to push to remote. Use without --dry-run to perform an actual push.');
}
else {
- $this->doPush();
+ $ref = sprintf('refs/heads/%s:refs/heads/%s', $this->artifactBranch, $this->destinationBranch);
+
+ if ($this->mode === self::MODE_FORCE_PUSH) {
+ $this->repo->push([$this->remoteName, $ref], ['--force']);
+ }
+ else {
+ $this->repo->push([$this->remoteName, $ref]);
+ }
+
+ $this->output->writeln(sprintf('Pushed branch "%s" with commit message "%s"', $this->destinationBranch, $this->commitMessage));
+ }
+ }
+ catch (GitException $exception) {
+ $result = $exception->getRunnerResult();
+ if (!$result) {
+ throw new \Exception('Unknown error occurred', $exception->getCode(), $exception);
+ }
+
+ $error = $result->getOutputAsString();
+ if (!empty($result->hasErrorOutput())) {
+ $error .= PHP_EOL . $result->getErrorOutputAsString();
}
- $this->result = TRUE;
}
catch (\Exception $exception) {
// Capture message and allow to rollback.
$error = $exception->getMessage();
}
- $this->logReport();
+ $this->showReport(is_null($error));
if ($this->needCleanup) {
- $this->cleanup();
+ $this->logNotice('Cleaning up');
+ $this->repo
+ ->restoreLocalExclude()
+ ->switchToBranch($this->originalBranch)
+ ->removeBranch($this->artifactBranch, TRUE)
+ ->removeRemote($this->remoteName);
}
- if (!$this->result) {
- throw new \Exception((string) $error);
- }
- }
-
- /**
- * Get source path git repository.
- *
- * @return string
- * Source path.
- */
- public function getSourcePathGitRepository(): string {
- return $this->sourcePathGitRepository;
- }
-
- /**
- * Branch mode.
- *
- * @return string
- * Branch mode name.
- */
- public static function modeBranch(): string {
- return 'branch';
- }
-
- /**
- * Force-push mode.
- *
- * @return string
- * Force-push mode name.
- */
- public static function modeForcePush(): string {
- return 'force-push';
- }
-
- /**
- * Diff mode.
- *
- * @return string
- * Diff mode name.
- */
- public static function modeDiff(): string {
- return 'diff';
- }
-
- /**
- * Prepare artifact to be then deployed.
- *
- * @throws \Exception
- */
- protected function prepareArtifact(): void {
- // Switch to artifact branch.
- $this->switchToArtifactBranchInGitRepository();
- // Remove sub-repositories.
- $this->removeSubReposInGitRepository();
- // Disable local exclude.
- $this->disableLocalExclude($this->getSourcePathGitRepository());
- // Add files.
- $this->addAllFilesInGitRepository();
- // Remove other files.
- $this->removeOtherFilesInGitRepository();
- // Commit all changes.
- $result = $this->commitAllChangesInGitRepository();
- // Show all changes if needed.
- if ($this->showChanges) {
- $this->output->writeln(sprintf('Added changes: %s', implode("\n", $result)));
- $this->logNotice(sprintf('Added changes: %s', implode("\n", $result)));
- }
- }
-
- /**
- * Switch to artifact branch.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- protected function switchToArtifactBranchInGitRepository(): void {
- $this
- ->gitRepository
- ->switchToBranch($this->artifactBranch, TRUE);
- }
-
- /**
- * Commit all changes.
- *
- * @return string[]
- * The files committed.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- protected function commitAllChangesInGitRepository(): array {
- return $this
- ->gitRepository
- ->commitAllChanges($this->message);
-
- }
-
- /**
- * Add all files in current git repository.
- *
- * @throws \CzProject\GitPhp\GitException
- * @throws \Exception
- */
- protected function addAllFilesInGitRepository(): void {
- if (!empty($this->gitignoreFile)) {
- $this->replaceGitignoreInGitRepository($this->gitignoreFile);
- $this->gitRepository->addAllChanges();
- $this->removeIgnoredFiles($this->getSourcePathGitRepository());
+ // Dump log to a file.
+ if (!empty($this->logFile)) {
+ $this->logDump($this->logFile);
}
- else {
- $this->gitRepository->addAllChanges();
- }
- }
-
- /**
- * Cleanup after build.
- *
- * @throws \Exception
- */
- protected function cleanup(): void {
- $this
- ->restoreLocalExclude($this->getSourcePathGitRepository());
- $this
- ->gitRepository
- ->switchToBranch($this->originalBranch);
-
- $this
- ->gitRepository
- ->removeBranch($this->artifactBranch, TRUE);
-
- $this
- ->gitRepository
- ->removeRemote($this->remoteName);
- }
-
- /**
- * Perform actual push to remote.
- *
- * @throws \Exception
- */
- protected function doPush(): void {
- try {
- $refSpec = sprintf('refs/heads/%s:refs/heads/%s', $this->artifactBranch, $this->destinationBranch);
- if ($this->mode === self::modeForcePush()) {
- $this
- ->gitRepository
- ->pushForce($this->remoteName, $refSpec);
- }
- else {
- $this->gitRepository->push([$this->remoteName, $refSpec]);
- }
-
- $this->output->writeln(sprintf('Pushed branch "%s" with commit message "%s"', $this->destinationBranch, $this->message));
- }
- catch (\Exception $exception) {
- // Re-throw the message with additional context.
- throw new \Exception(
- sprintf(
- 'Error occurred while pushing branch "%s" with commit message "%s"',
- $this->destinationBranch,
- $this->message
- ),
- $exception->getCode(),
- $exception
- );
+ if (!is_null($error)) {
+ $error = empty($error) ? 'Unknown error occurred' : $error;
+ throw new \Exception($error);
}
}
@@ -491,41 +321,40 @@ protected function resolveOptions(string $url, array $options): void {
$this->setMode($options['mode'], $options);
- $srcPath = empty($options['src']) ? $this->fsGetRootDir() : $this->fsGetAbsolutePath($options['src']);
- $this->sourcePathGitRepository = $srcPath;
+ $this->sourceDir = empty($options['src']) ? $this->fsGetRootDir() : $this->fsGetAbsolutePath($options['src']);
// Setup Git repository from source path.
- $this->initGitRepository($srcPath);
+ $this->repo = new ArtifactGitRepository($this->sourceDir, NULL, $this->logger);
+
+ // Set original, destination, artifact branch names.
+ $this->originalBranch = $this->repo->getOriginalBranch();
+
+ $branch = $this->tokenProcess($options['branch']);
+ if (!ArtifactGitRepository::isValidBranchName($branch)) {
+ throw new \RuntimeException(sprintf('Incorrect value "%s" specified for git remote branch', $branch));
+ }
+ $this->destinationBranch = $branch;
- // Set original, destination, artifact branch name.
- $this->originalBranch = $this->resolveOriginalBranch();
- $this->setDstBranch($options['branch']);
$this->artifactBranch = $this->destinationBranch . '-artifact';
- // Set commit message.
- $this->setMessage($options['message']);
+ $this->commitMessage = $this->tokenProcess($options['message']);
if (!empty($options['gitignore'])) {
- $this->setGitignoreFile($options['gitignore']);
- }
- }
+ $gitignore = $this->fsGetAbsolutePath($options['gitignore']);
+ $this->fsAssertPathsExist($gitignore);
- /**
- * Setup git repository.
- *
- * @param string $sourcePath
- * Source path.
- *
- * @return \DrevOps\GitArtifact\Git\ArtifactGitRepository
- * Current git repository.
- *
- * @throws \CzProject\GitPhp\GitException
- * @throws \Exception
- */
- protected function initGitRepository(string $sourcePath): ArtifactGitRepository {
- $this->gitRepository = $this->git->open($sourcePath);
+ $contents = file_get_contents($gitignore);
+ if (!$contents) {
+ throw new \Exception('Unable to load contents of ' . $gitignore);
+ }
+
+ $this->logDebug('-----.gitignore---------');
+ $this->logDebug($contents);
+ $this->logDebug('-----.gitignore---------');
- return $this->gitRepository;
+ $this->gitignoreFile = $gitignore;
+ $this->repo->setGitignoreFile($gitignore);
+ }
}
/**
@@ -537,7 +366,7 @@ protected function showInfo(): void {
$lines[] = ('----------------------------------------------------------------------');
$lines[] = (' Build timestamp: ' . date('Y/m/d H:i:s', $this->now));
$lines[] = (' Mode: ' . $this->mode);
- $lines[] = (' Source repository: ' . $this->getSourcePathGitRepository());
+ $lines[] = (' Source repository: ' . $this->sourceDir);
$lines[] = (' Remote repository: ' . $this->remoteUrl);
$lines[] = (' Remote branch: ' . $this->destinationBranch);
$lines[] = (' Gitignore file: ' . ($this->gitignoreFile ?: 'No'));
@@ -545,6 +374,7 @@ protected function showInfo(): void {
$lines[] = ('----------------------------------------------------------------------');
$this->output->writeln($lines);
+
foreach ($lines as $line) {
$this->logNotice($line);
}
@@ -553,18 +383,18 @@ protected function showInfo(): void {
/**
* Dump artifact report to a file.
*/
- protected function logReport(): void {
+ protected function showReport(bool $result): void {
$lines[] = '----------------------------------------------------------------------';
$lines[] = ' Artifact report';
$lines[] = '----------------------------------------------------------------------';
$lines[] = ' Build timestamp: ' . date('Y/m/d H:i:s', $this->now);
$lines[] = ' Mode: ' . $this->mode;
- $lines[] = ' Source repository: ' . $this->getSourcePathGitRepository();
+ $lines[] = ' Source repository: ' . $this->sourceDir;
$lines[] = ' Remote repository: ' . $this->remoteUrl;
$lines[] = ' Remote branch: ' . $this->destinationBranch;
$lines[] = ' Gitignore file: ' . ($this->gitignoreFile ?: 'No');
- $lines[] = ' Commit message: ' . $this->message;
- $lines[] = ' Push result: ' . ($this->result ? 'Success' : 'Failure');
+ $lines[] = ' Commit message: ' . $this->commitMessage;
+ $lines[] = ' Push result: ' . ($result ? 'Success' : 'Failure');
$lines[] = '----------------------------------------------------------------------';
foreach ($lines as $line) {
@@ -582,11 +412,11 @@ protected function logReport(): void {
*/
protected function setMode(string $mode, array $options): void {
switch ($mode) {
- case self::modeForcePush():
+ case self::MODE_FORCE_PUSH:
// Intentionally empty.
break;
- case self::modeBranch():
+ case self::MODE_BRANCH:
if (is_scalar($options['branch'] ?? NULL) && !self::tokenExists(strval($options['branch']))) {
$this->output->writeln('WARNING! Provided branch name does not have a token.
Pushing of the artifact into this branch will fail on second and follow-up pushes to remote.
@@ -594,300 +424,32 @@ protected function setMode(string $mode, array $options): void {
}
break;
- case self::modeDiff():
+ case self::MODE_DIFF:
throw new \RuntimeException('Diff mode is not yet implemented.');
default:
throw new \RuntimeException(sprintf('Invalid mode provided. Allowed modes are: %s', implode(', ', [
- self::modeForcePush(),
- self::modeBranch(),
- self::modeDiff(),
+ self::MODE_FORCE_PUSH,
+ self::MODE_BRANCH,
+ self::MODE_DIFF,
])));
}
$this->mode = $mode;
}
- /**
- * Resolve original branch to handle detached repositories.
- *
- * Usually, repository become detached when a tag is checked out.
- *
- * @return string
- * Branch or detachment source.
- *
- * @throws \Exception
- * If neither branch nor detachment source is not found.
- */
- protected function resolveOriginalBranch(): string {
- $branch = $this->gitRepository->getCurrentBranchName();
- // Repository could be in detached state. If this the case - we need to
- // capture the source of detachment, if it exists.
- if (str_contains($branch, 'HEAD detached')) {
- $branch = NULL;
- $branchList = $this->gitRepository->getBranches();
- if ($branchList) {
- $branchList = array_filter($branchList);
- foreach ($branchList as $branch) {
- if (preg_match('/\(.*detached .* ([^\)]+)\)/', $branch, $matches)) {
- $branch = $matches[1];
- break;
- }
- }
- }
- if (empty($branch)) {
- throw new \Exception('Unable to determine detachment source');
- }
- }
-
- return $branch;
- }
-
- /**
- * Set the branch in the remote repository where commits will be pushed to.
- *
- * @param string $branch
- * Branch in the remote repository.
- */
- protected function setDstBranch(string $branch): void {
- $branch = (string) $this->tokenProcess($branch);
-
- if (!ArtifactGitRepository::isValidBranchName($branch)) {
- throw new \RuntimeException(sprintf('Incorrect value "%s" specified for git remote branch', $branch));
- }
- $this->destinationBranch = $branch;
- }
-
- /**
- * Set commit message.
- *
- * @param string $message
- * Commit message to set on the deployment commit.
- */
- protected function setMessage(string $message): void {
- $message = (string) $this->tokenProcess($message);
- $this->message = $message;
- }
-
- /**
- * Set replacement gitignore file path location.
- *
- * @param string $path
- * Path to the replacement .gitignore file.
- *
- * @throws \Exception
- */
- protected function setGitignoreFile(string $path): void {
- $path = $this->fsGetAbsolutePath($path);
- $this->fsPathsExist($path);
- $this->gitignoreFile = $path;
- }
-
/**
* Check that there all requirements are met in order to to run this command.
*/
protected function checkRequirements(): void {
// @todo Refactor this into more generic implementation.
$this->logNotice('Checking requirements');
- if (!$this->fsIsCommandAvailable('git')) {
- throw new \RuntimeException('At least one of the script running requirements was not met');
- }
- $this->logNotice('All requirements were met');
- }
-
- /**
- * Replace gitignore file with provided file.
- *
- * @param string $filename
- * Path to new gitignore to replace current file with.
- */
- protected function replaceGitignoreInGitRepository(string $filename): void {
- $path = $this->getSourcePathGitRepository();
- $this->logDebug(sprintf('Replacing .gitignore: %s with %s', $path . DIRECTORY_SEPARATOR . '.gitignore', $filename));
- $this->fs->copy($filename, $path . DIRECTORY_SEPARATOR . '.gitignore', TRUE);
- $this->fs->remove($filename);
- }
- /**
- * Helper to get a file name of the local exclude file.
- *
- * @param string $path
- * Path to directory.
- *
- * @return string
- * Exclude file name path.
- */
- protected function getLocalExcludeFileName(string $path): string {
- return $path . DIRECTORY_SEPARATOR . '.git' . DIRECTORY_SEPARATOR . 'info' . DIRECTORY_SEPARATOR . 'exclude';
- }
-
- /**
- * Check if local exclude (.git/info/exclude) file exists.
- *
- * @param string $path
- * Path to repository.
- *
- * @return bool
- * True if exists, false otherwise.
- */
- protected function localExcludeExists(string $path): bool {
- return $this->fs->exists($this->getLocalExcludeFileName($path));
- }
-
- /**
- * Check if local exclude (.git/info/exclude) file is empty.
- *
- * @param string $path
- * Path to repository.
- * @param bool $strict
- * Flag to check if the file is empty. If false, comments and empty lines
- * are considered as empty.
- *
- * @return bool
- * - true, if $strict is true and file has no records.
- * - false, if $strict is true and file has some records.
- * - true, if $strict is false and file has only empty lines and comments.
- * - false, if $strict is false and file lines other than empty lines or
- * comments.
- *
- * @throws \Exception
- */
- protected function localExcludeEmpty(string $path, bool $strict = FALSE): bool {
- if (!$this->localExcludeExists($path)) {
- throw new \Exception(sprintf('File "%s" does not exist', $path));
- }
-
- $filename = $this->getLocalExcludeFileName($path);
- if ($strict) {
- return empty(file_get_contents($filename));
- }
- $lines = file($filename);
- if ($lines) {
- $lines = array_map(trim(...), $lines);
- $lines = array_filter($lines, static function ($line): bool {
- return strlen($line) > 0;
- });
- $lines = array_filter($lines, static function ($line): bool {
- return !str_starts_with(trim($line), '#');
- });
- }
-
- return empty($lines);
- }
-
- /**
- * Disable local exclude file (.git/info/exclude).
- *
- * @param string $path
- * Path to repository.
- */
- protected function disableLocalExclude(string $path): void {
- $filename = $this->getLocalExcludeFileName($path);
- $filenameDisabled = $filename . '.bak';
- if ($this->fs->exists($filename)) {
- $this->logDebug('Disabling local exclude');
- $this->fs->rename($filename, $filenameDisabled);
- }
- }
-
- /**
- * Restore previously disabled local exclude file.
- *
- * @param string $path
- * Path to repository.
- */
- protected function restoreLocalExclude(string $path): void {
- $filename = $this->getLocalExcludeFileName($path);
- $filenameDisabled = $filename . '.bak';
- if ($this->fs->exists($filenameDisabled)) {
- $this->logDebug('Restoring local exclude');
- $this->fs->rename($filenameDisabled, $filename);
- }
- }
-
- /**
- * Remove ignored files.
- *
- * @param string $location
- * Path to repository.
- * @param string|null $gitignorePath
- * Gitignore file name.
- *
- * @throws \Exception
- * If removal command finished with an error.
- */
- protected function removeIgnoredFiles(string $location, ?string $gitignorePath = NULL): void {
- $location = $this->getSourcePathGitRepository();
- $gitignorePath = $gitignorePath ?: $location . DIRECTORY_SEPARATOR . '.gitignore';
-
- $gitignoreContent = file_get_contents($gitignorePath);
- if (!$gitignoreContent) {
- $this->logDebug('Unable to load ' . $gitignoreContent);
- }
- else {
- $this->logDebug('-----.gitignore---------');
- $this->logDebug($gitignoreContent);
- $this->logDebug('-----.gitignore---------');
- }
-
- $files = $this
- ->gitRepository
- ->listIgnoredFilesFromGitIgnoreFile($gitignorePath);
-
- if (!empty($files)) {
- $files = array_filter($files);
- foreach ($files as $file) {
- $fileName = $location . DIRECTORY_SEPARATOR . $file;
- $this->logDebug(sprintf('Removing excluded file %s', $fileName));
- if ($this->fs->exists($fileName)) {
- $this->fs->remove($fileName);
- }
- }
- }
- }
-
- /**
- * Remove 'other' files.
- *
- * 'Other' files are files that are neither staged nor tracked in git.
- *
- * @throws \Exception
- * If removal command finished with an error.
- */
- protected function removeOtherFilesInGitRepository(): void {
- $files = $this->gitRepository->listOtherFiles();
- if (!empty($files)) {
- $files = array_filter($files);
- foreach ($files as $file) {
- $fileName = $this->getSourcePathGitRepository() . DIRECTORY_SEPARATOR . $file;
- $this->logDebug(sprintf('Removing other file %s', $fileName));
- $this->fs->remove($fileName);
- }
+ if (!$this->fsIsCommandAvailable('git')) {
+ throw new \RuntimeException('Git command is not available');
}
- }
- /**
- * Remove any repositories within current repository.
- */
- protected function removeSubReposInGitRepository(): void {
- $finder = new Finder();
- $dirs = $finder
- ->directories()
- ->name('.git')
- ->ignoreDotFiles(FALSE)
- ->ignoreVCS(FALSE)
- ->depth('>0')
- ->in($this->getSourcePathGitRepository());
-
- $dirs = iterator_to_array($dirs->directories());
-
- foreach ($dirs as $dir) {
- if ($dir instanceof \SplFileInfo) {
- $dir = $dir->getPathname();
- }
- $this->fs->remove($dir);
- $this->logDebug(sprintf('Removing sub-repository "%s"', (string) $dir));
- }
+ $this->logNotice('All requirements were met');
}
/**
@@ -899,30 +461,24 @@ protected function removeSubReposInGitRepository(): void {
* @throws \Exception
*/
protected function getTokenBranch(): string {
- return $this
- ->gitRepository
- ->getCurrentBranchName();
+ return $this->repo->getCurrentBranchName();
}
/**
* Token callback to get tags.
*
* @param string|null $delimiter
- * Token delimiter. Defaults to ', '.
+ * Token delimiter. Defaults to '-'.
*
* @return string
* String of tags.
*
* @throws \Exception
*/
- protected function getTokenTags(?string $delimiter = NULL): string {
- $delimiter = $delimiter ?: '-';
- // We just want to get all tags point to the HEAD.
- $tags = $this
- ->gitRepository
- ->getTagsPointToHead();
-
- return implode($delimiter, $tags);
+ protected function getTokenTags(?string $delimiter): string {
+ $delimiter = $delimiter ?? '-';
+
+ return implode($delimiter, $this->repo->listTagsPointingToHead());
}
/**
@@ -938,30 +494,4 @@ protected function getTokenTimestamp(string $format = 'Y-m-d_H-i-s'): string {
return date($format, $this->now);
}
- /**
- * Write output.
- *
- * @param string $text
- * Text.
- */
- protected function writeln(string $text): void {
- $this->output->writeln($text);
- }
-
- /**
- * Setup remote for current repository.
- *
- * @throws \CzProject\GitPhp\GitException
- * @throws \Exception
- */
- protected function setupRemoteForRepository(): void {
- $remoteName = $this->remoteName;
- $remoteUrl = $this->remoteUrl;
- if (!ArtifactGitRepository::isValidRemoteUrl($remoteUrl)) {
- throw new \Exception(sprintf('Invalid remote URL: %s', $remoteUrl));
- }
-
- $this->gitRepository->addRemote($remoteName, $remoteUrl);
- }
-
}
diff --git a/src/Git/ArtifactGit.php b/src/Git/ArtifactGit.php
deleted file mode 100644
index edba160..0000000
--- a/src/Git/ArtifactGit.php
+++ /dev/null
@@ -1,23 +0,0 @@
-runner);
- }
-
-}
diff --git a/src/Git/ArtifactGitRepository.php b/src/Git/ArtifactGitRepository.php
index 01cb7b4..8707fda 100644
--- a/src/Git/ArtifactGitRepository.php
+++ b/src/Git/ArtifactGitRepository.php
@@ -5,9 +5,13 @@
namespace DrevOps\GitArtifact\Git;
use CzProject\GitPhp\GitRepository;
+use CzProject\GitPhp\IRunner;
use CzProject\GitPhp\RunnerResult;
+use DrevOps\GitArtifact\Traits\FilesystemTrait;
+use DrevOps\GitArtifact\Traits\LogTrait;
use Psr\Log\LoggerInterface;
use Symfony\Component\Filesystem\Filesystem;
+use Symfony\Component\Finder\Finder;
/**
* Artifact git repository.
@@ -16,117 +20,64 @@
*/
class ArtifactGitRepository extends GitRepository {
- /**
- * Filesystem.
- */
- protected Filesystem $fs;
+ use FilesystemTrait;
+ use LogTrait;
/**
- * Logger.
+ * The gitignore file path.
*/
- protected LoggerInterface $logger;
+ protected string $gitignoreFile;
/**
- * Force pushing.
- *
- * @param string $remote
- * Remote name.
- * @param string $refSpec
- * Specify what destination ref to update with what source object.
- *
- * @return ArtifactGitRepository
- * Git repo.
- *
- * @throws \CzProject\GitPhp\GitException
+ * {@inheritdoc}
*/
- public function pushForce(string $remote, string $refSpec): ArtifactGitRepository {
- return parent::push([$remote, $refSpec], ['--force']);
- }
+ public function __construct($repository, ?IRunner $runner = NULL, ?LoggerInterface $logger = NULL) {
+ parent::__construct($repository, $runner);
- /**
- * List ignored files from git ignore file.
- *
- * @param string $gitIgnoreFilePath
- * Git ignore file path.
- *
- * @return string[]
- * Files.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function listIgnoredFilesFromGitIgnoreFile(string $gitIgnoreFilePath): array {
- $files = $this->extractFromCommand(['ls-files', '-i', '-c', '--exclude-from=' . $gitIgnoreFilePath]);
+ $this->fs = new Filesystem();
- if (!$files) {
- return [];
+ if ($logger instanceof LoggerInterface) {
+ $this->logger = $logger;
}
-
- return $files;
}
/**
- * List 'Other' files.
- *
- * 'Other' files are files that are neither staged nor tracked in git.
- *
- * @return string[]
- * Files.
- *
- * @throws \CzProject\GitPhp\GitException
+ * {@inheritdoc}
*/
- public function listOtherFiles(): array {
- $files = $this->extractFromCommand(['ls-files', '--other', '--exclude-standard']);
- if (!$files) {
- return [];
- }
+ public function run(...$args): RunnerResult {
+ $command = array_shift($args);
+ array_unshift($args, '--no-pager', $command);
- return $files;
+ return parent::run(...$args);
}
/**
- * Get commits.
- *
- * @param string $format
- * Commit format.
- *
- * @return string[]
- * Commits.
- *
- * @throws \CzProject\GitPhp\GitException
+ * Set gitignore file.
*/
- public function getCommits(string $format = '%s'): array {
- $commits = $this->extractFromCommand(['log', '--format=' . $format]);
- if (!$commits) {
- return [];
- }
+ public function setGitignoreFile(string $filename): static {
+ $this->gitignoreFile = $filename;
- return $commits;
+ return $this;
}
/**
- * Reset hard.
- *
- * @return $this
- * Git repo.
- *
- * @throws \CzProject\GitPhp\GitException
+ * {@inheritdoc}
*/
- public function resetHard(): ArtifactGitRepository {
- $this->run('reset', ['--hard']);
+ public function addRemote($name, $url, ?array $options = NULL): static {
+ if (!self::isValidRemote($url)) {
+ throw new \InvalidArgumentException(sprintf('Invalid remote URL provided: %s', $url));
+ }
- return $this;
+ return parent::addRemote($name, $url, $options);
}
/**
- * Clean repo.
- *
- * @return $this
- * Git repo.
- *
- * @throws \CzProject\GitPhp\GitException
+ * {@inheritdoc}
*/
- public function cleanForce(): ArtifactGitRepository {
- $this->run('clean', ['-dfx']);
+ public function removeRemote($name): static {
+ if (in_array($name, $this->listRemotes())) {
+ $this->run('remote', 'remove', $name);
+ }
return $this;
}
@@ -134,22 +85,20 @@ public function cleanForce(): ArtifactGitRepository {
/**
* Switch to new branch.
*
- * @param string $branchName
+ * @param string $branch
* Branch name.
- * @param bool $createNew
+ * @param bool $create_new
* Optional flag to also create a branch before switching. Default false.
*
- * @return ArtifactGitRepository
+ * @return static
* The git repository.
- *
- * @throws \CzProject\GitPhp\GitException
*/
- public function switchToBranch(string $branchName, bool $createNew = FALSE): ArtifactGitRepository {
- if (!$createNew) {
- return $this->checkout($branchName);
+ public function switchToBranch(string $branch, bool $create_new = FALSE): static {
+ if (!$create_new) {
+ return $this->checkout($branch);
}
- return $this->createBranch($branchName, TRUE);
+ return $this->createBranch($branch, TRUE);
}
/**
@@ -160,20 +109,20 @@ public function switchToBranch(string $branchName, bool $createNew = FALSE): Art
* @param bool $force
* Force remove or not.
*
- * @return ArtifactGitRepository
+ * @return static
* Git repository
- *
- * @throws \CzProject\GitPhp\GitException
*/
- public function removeBranch($name, bool $force = FALSE): ArtifactGitRepository {
+ public function removeBranch($name, bool $force = FALSE): static {
if (empty($name)) {
return $this;
}
$branches = $this->getBranches();
+
if (empty($branches)) {
return $this;
}
+
if (!in_array($name, $branches)) {
return $this;
}
@@ -195,51 +144,56 @@ public function removeBranch($name, bool $force = FALSE): ArtifactGitRepository
*
* @return array
* The changes.
- *
- * @throws \CzProject\GitPhp\GitException
*/
public function commitAllChanges(string $message): array {
$this->addAllChanges();
- // We do not use commit method because we need return the output.
+ // We do not use the commit method because we need return the output.
return $this->execute('commit', '--allow-empty', [
'-m' => $message,
]);
}
/**
- * List committed files.
- *
- * @return string[]
- * Files.
- *
- * @throws \CzProject\GitPhp\GitException
+ * Disable local exclude file (.git/info/exclude).
*/
- public function listCommittedFiles(): array {
- $files = $this->extractFromCommand(['ls-tree', '--name-only', '-r', 'HEAD']);
- if (!$files) {
- return [];
+ public function disableLocalExclude(): static {
+ $filename = $this->getRepositoryPath() . DIRECTORY_SEPARATOR . '.git' . DIRECTORY_SEPARATOR . 'info' . DIRECTORY_SEPARATOR . 'exclude';
+
+ if ($this->fs->exists($filename)) {
+ $this->logDebug('Disabling local exclude');
+ $this->fs->rename($filename, $filename . '.bak');
}
- return $files;
+ return $this;
}
/**
- * Set config receive.denyCurrentBranch is ignored.
- *
- * @return $this
- * Git repo.
- *
- * @throws \CzProject\GitPhp\GitException
+ * Restore previously disabled local exclude file.
*/
- public function setConfigReceiveDenyCurrentBranchIgnore(): ArtifactGitRepository {
- $this->extractFromCommand(['config', ['receive.denyCurrentBranch', 'ignore']]);
+ public function restoreLocalExclude(): static {
+ $filename = $this->getRepositoryPath() . DIRECTORY_SEPARATOR . '.git' . DIRECTORY_SEPARATOR . 'info' . DIRECTORY_SEPARATOR . 'exclude';
+
+ if ($this->fs->exists($filename . '.bak')) {
+ $this->logDebug('Restoring local exclude');
+ $this->fs->rename($filename . '.bak', $filename);
+ }
return $this;
}
/**
- * Get tag point to HEAD.
+ * List remotes.
+ *
+ * @return array
+ * Remotes.
+ */
+ protected function listRemotes(): array {
+ return $this->extractFromCommand(['remote']) ?: [];
+ }
+
+ /**
+ * Get tag pointing to HEAD.
*
* @return string[]
* Array of tags from the latest commit.
@@ -247,7 +201,7 @@ public function setConfigReceiveDenyCurrentBranchIgnore(): ArtifactGitRepository
* @throws \Exception
* If no tags found in the latest commit.
*/
- public function getTagsPointToHead(): array {
+ public function listTagsPointingToHead(): array {
$tags = $this->extractFromCommand(['tag', ['--points-at', 'HEAD']]);
if (empty($tags)) {
@@ -258,171 +212,169 @@ public function getTagsPointToHead(): array {
}
/**
- * Create an annotated tag.
- *
- * @param string $name
- * Name.
- * @param string $message
- * Message.
- *
- * @return $this
- * Git repo.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function createAnnotatedTag(string $name, string $message): ArtifactGitRepository {
- $this->createTag($name, [
- '--message=' . $message,
- '-a',
- ]);
-
- return $this;
- }
-
- /**
- * Create an annotated tag.
+ * Ger original branch, accounting for detached repository state.
*
- * @param string $name
- * Name.
+ * Usually, repository become detached when a tag is checked out.
*
- * @return $this
- * Git repo.
+ * @return string
+ * Branch or detachment source.
*
- * @throws \CzProject\GitPhp\GitException
+ * @throws \Exception
+ * If neither branch nor detachment source is not found.
*/
- public function createLightweightTag(string $name): ArtifactGitRepository {
- $this->createTag($name);
+ public function getOriginalBranch(): string {
+ $branch = $this->getCurrentBranchName();
+
+ // Repository could be in detached state. If this the case - we need to
+ // capture the source of detachment, if it exists.
+ if (str_contains($branch, 'HEAD detached')) {
+ $branch = NULL;
+ $branch_list = $this->getBranches();
+ if ($branch_list) {
+ $branch_list = array_filter($branch_list);
+ foreach ($branch_list as $branch) {
+ if (preg_match('/\(.*detached .* ([^)]+)\)/', $branch, $matches)) {
+ $branch = $matches[1];
+ break;
+ }
+ }
+ }
+
+ if (empty($branch)) {
+ throw new \Exception('Unable to determine a detachment source');
+ }
+ }
- return $this;
+ return $branch;
}
/**
- * Remove remote by name.
- *
- * We need override this method because parent method does not work.
- *
- * @param string $name
- * Remote name.
- *
- * @return ArtifactGitRepository
- * Git repo.
- *
- * @throws \CzProject\GitPhp\GitException
+ * Remove ignored files.
*/
- public function removeRemote($name): ArtifactGitRepository {
- if ($this->isRemoteExists($name)) {
- $this->run('remote', 'remove', $name);
+ public function removeIgnoredFiles(): static {
+ if (!empty($this->gitignoreFile)) {
+ $gitignore = $this->getRepositoryPath() . DIRECTORY_SEPARATOR . '.gitignore';
+ $this->logDebug(sprintf('Copying custom .gitignore file from %s to %s', $this->gitignoreFile, $gitignore));
+ $this->fs->copy($this->gitignoreFile, $gitignore, TRUE);
+
+ // Remove custom .gitignore file if it is within the repository.
+ // @todo Review if this is "magic" and should be explicitly listed in
+ // the file itself. Alternatively, we could add a check if the custom
+ // gitignore is ignored within itself and add it if it is not.
+ if (str_starts_with($this->gitignoreFile, $this->getRepositoryPath())) {
+ $this->fs->remove($this->gitignoreFile);
+ }
+
+ // Custom .gitignore may contain rules that will change the list of
+ // ignored files. We need to add these files as changes so that they
+ // could be reported as excluded by the command below.
+ $this->addAllChanges();
+
+ $files = $this->extractFromCommand(['ls-files', '-i', '-c', '--exclude-from=' . $gitignore]) ?: [];
+ $files = array_filter($files);
+
+ foreach ($files as $file) {
+ $filename = $this->getRepositoryPath() . DIRECTORY_SEPARATOR . $file;
+ if ($this->fs->exists($filename)) {
+ $this->logDebug(sprintf('Removing ignored file %s', $filename));
+ $this->fs->remove($filename);
+ }
+ }
}
return $this;
}
/**
- * Get remote list.
+ * Remove 'other' files.
*
- * @return array
- * Remotes.
- *
- * @throws \CzProject\GitPhp\GitException
+ * 'Other' files are files that are neither staged nor tracked in git.
*/
- public function getRemotes(): array {
- $remotes = $this->extractFromCommand(['remote']);
- if (!$remotes) {
- return [];
+ public function removeOtherFiles(): static {
+ $files = $this->extractFromCommand(['ls-files', '--other', '--exclude-standard']) ?: [];
+ $files = array_filter($files);
+
+ foreach ($files as $file) {
+ $filename = $this->getRepositoryPath() . DIRECTORY_SEPARATOR . $file;
+ if ($this->fs->exists($filename)) {
+ $this->logDebug(sprintf('Removing other file %s', $filename));
+ $this->fs->remove($filename);
+ }
}
- return $remotes;
+ return $this;
}
/**
- * Check remote is existing or not by remote name.
- *
- * @param string $remoteName
- * Remote name to check.
- *
- * @return bool
- * Exist or not.
- *
- * @throws \CzProject\GitPhp\GitException
+ * Remove any repositories within current repository.
*/
- public function isRemoteExists(string $remoteName): bool {
- $remotes = $this->getRemotes();
- if (empty($remotes)) {
- return FALSE;
+ public function removeSubRepositories(): static {
+ $finder = new Finder();
+ $dirs = $finder
+ ->directories()
+ ->name('.git')
+ ->ignoreDotFiles(FALSE)
+ ->ignoreVCS(FALSE)
+ ->depth('>0')
+ ->in($this->getRepositoryPath());
+
+ $dirs = iterator_to_array($dirs->directories());
+
+ foreach ($dirs as $dir) {
+ if ($dir instanceof \SplFileInfo) {
+ $dir = $dir->getPathname();
+ $this->fs->remove($dir);
+ $this->logDebug(sprintf('Removing sub-repository "%s"', $this->fsGetAbsolutePath((string) $dir)));
+ }
}
- return in_array($remoteName, $remotes);
- }
-
- /**
- * Override run method to add --no-pager option to all command.
- *
- * @param mixed ...$args
- * Command args.
- *
- * @return \CzProject\GitPhp\RunnerResult
- * Runner result.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function run(...$args): RunnerResult {
- $command = array_shift($args);
- array_unshift($args, '--no-pager', $command);
-
- return parent::run(...$args);
- }
+ // After removing sub-repositories, the files that were previously tracked
+ // in those repositories are now become a part of the current repository.
+ // We need to add them as changes.
+ $this->addAllChanges();
- /**
- * Check if provided location is a URI.
- *
- * @param string $location
- * Location to check.
- *
- * @return bool
- * TRUE if location is URI, FALSE otherwise.
- */
- public static function isUri(string $location): bool {
- return (bool) preg_match('/^(?:git|ssh|https?|[\d\w\.\-_]+@[\w\.\-]+):(?:\/\/)?[\w\.@:\/~_-]+\.git(?:\/?|\#[\d\w\.\-_]+?)$/', $location);
+ return $this;
}
/**
- * Check if provided branch name can be used in git.
+ * Check if provided branch name can be used in Git.
*
- * @param string $branchName
- * Branch to check.
+ * @param string $name
+ * Branch name to check.
*
* @return bool
* TRUE if it is a valid Git branch, FALSE otherwise.
*/
- public static function isValidBranchName(string $branchName): bool {
- return preg_match('/^(?!\/|.*(?:[\/\.]\.|\/\/|\\|@\{))[^\040\177\s\~\^\:\?\*\[]+(?exists($pathOrUri);
- $isUri = self::isUri($pathOrUri);
+
+ $is_local = $filesystem->exists($uri);
+ $is_external = (bool) preg_match('/^(?:git|ssh|https?|[\d\w\.\-_]+@[\w\.\-]+):(?:\/\/)?[\w\.@:\/~_-]+\.git(?:\/?|\#[\d\w\.\-_]+?)$/', $uri);
return match ($type) {
- 'any' => $isLocal || $isUri,
- 'local' => $isLocal,
- 'uri' => $isUri,
+ 'any' => $is_local || $is_external,
+ 'local' => $is_local,
+ 'external' => $is_external,
default => throw new \InvalidArgumentException(sprintf('Invalid argument "%s" provided', $type)),
};
}
diff --git a/src/Traits/FilesystemTrait.php b/src/Traits/FilesystemTrait.php
index 6d1c534..9887695 100644
--- a/src/Traits/FilesystemTrait.php
+++ b/src/Traits/FilesystemTrait.php
@@ -44,7 +44,7 @@ trait FilesystemTrait {
*/
protected function fsSetRootDir(?string $path = NULL): static {
$path = empty($path) ? $this->fsGetRootDir() : $this->fsGetAbsolutePath($path);
- $this->fsPathsExist($path);
+ $this->fsAssertPathsExist($path);
$this->fsRootDir = $path;
return $this;
@@ -84,6 +84,7 @@ protected function fsGetRootDir(): string {
protected function fsSetCwd(string $dir): static {
chdir($dir);
$this->fsOriginalCwdStack[] = $dir;
+
return $this;
}
@@ -163,8 +164,9 @@ protected function fsGetAbsolutePath(string $file, ?string $root = NULL): string
* @throws \Exception
* If at least one file does not exist.
*/
- protected function fsPathsExist($paths, bool $strict = TRUE): bool {
+ protected function fsAssertPathsExist($paths, bool $strict = TRUE): bool {
$paths = is_array($paths) ? $paths : [$paths];
+
if (!$this->fs->exists($paths)) {
if ($strict) {
throw new \Exception(sprintf('One of the files or directories does not exist: %s', implode(', ', $paths)));
@@ -177,7 +179,7 @@ protected function fsPathsExist($paths, bool $strict = TRUE): bool {
}
/**
- * Replacement for PHP's `fsRealpath` resolves non-existing paths.
+ * Replacement for PHP's `realpath` resolves non-existing paths.
*
* The main deference is that it does not return FALSE on non-existing
* paths.
@@ -208,8 +210,8 @@ protected function fsRealpath(string $path): string {
// Resolve path parts (single dot, double dot and double delimiters).
$path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
- $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), static function ($part) : bool {
- return strlen($part) > 0;
+ $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), static function ($part): bool {
+ return strlen($part) > 0;
});
$absolutes = [];
@@ -239,7 +241,16 @@ protected function fsRealpath(string $path): string {
// Put initial separator that could have been lost.
$path = $unipath ? $path : '/' . $path;
- return $unc ? '\\\\' . $path : $path;
+ $path = $unc ? '\\\\' . $path : $path;
+
+ if (str_starts_with($path, sys_get_temp_dir())) {
+ $tmp_realpath = realpath(sys_get_temp_dir());
+ if ($tmp_realpath) {
+ $path = str_replace(sys_get_temp_dir(), $tmp_realpath, $path);
+ }
+ }
+
+ return $path;
}
}
diff --git a/src/Traits/LogTrait.php b/src/Traits/LogTrait.php
index 43497b4..5755e10 100644
--- a/src/Traits/LogTrait.php
+++ b/src/Traits/LogTrait.php
@@ -9,6 +9,7 @@
use Monolog\Logger;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Monolog\Handler\ConsoleHandler;
+use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
@@ -22,25 +23,26 @@ trait LogTrait {
protected LoggerInterface $logger;
/**
- * Create Logger.
- *
- * @param string $name
- * Name.
- * @param \Symfony\Component\Console\Output\OutputInterface $output
- * Output.
- * @param string $filepath
- * Filepath to log file.
- *
- * @return \Psr\Log\LoggerInterface
- * Logger.
+ * Path to the temporary log file.
*/
- public static function loggerCreate(string $name, OutputInterface $output, string $filepath): LoggerInterface {
- $logger = new Logger($name);
+ protected string $logDumpFile = '';
+
+ /**
+ * Prepare logger.
+ */
+ protected function logPrepare(string $name, InputInterface $input, OutputInterface $output): void {
+ if ($input->getOption('log')) {
+ $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
+ }
+
+ $this->logDumpFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . time() . '-artifact-log.log';
+
+ $this->logger = new Logger($name);
$handler = new ConsoleHandler($output);
- $logger->pushHandler($handler);
+ $this->logger->pushHandler($handler);
- if (!empty($filepath)) {
+ if (!empty($this->logDumpFile)) {
$map = [
OutputInterface::VERBOSITY_QUIET => Level::Error,
OutputInterface::VERBOSITY_NORMAL => Level::Warning,
@@ -49,12 +51,12 @@ public static function loggerCreate(string $name, OutputInterface $output, strin
OutputInterface::VERBOSITY_DEBUG => Level::Debug,
];
- $handler = new StreamHandler($filepath, $map[$output->getVerbosity()] ?? Level::Debug);
+ $handler = new StreamHandler($this->logDumpFile, $map[$output->getVerbosity()] ?? Level::Debug);
- $logger->pushHandler($handler);
+ $this->logger->pushHandler($handler);
}
- return $logger;
+ $this->logDebug('Debug messages enabled');
}
/**
@@ -93,4 +95,14 @@ public function logError(string|\Stringable $message, array $context = []): void
$this->logger->error($message, $context);
}
+ /**
+ * Dump log to file.
+ */
+ protected function logDump(string $filename): void {
+ if ($this->fs->exists($this->logDumpFile)) {
+ $this->fs->copy($this->logDumpFile, $filename);
+ $this->fs->remove($this->logDumpFile);
+ }
+ }
+
}
diff --git a/src/Traits/TokenTrait.php b/src/Traits/TokenTrait.php
index 68a6325..9e70683 100644
--- a/src/Traits/TokenTrait.php
+++ b/src/Traits/TokenTrait.php
@@ -15,29 +15,33 @@ trait TokenTrait {
* @param string $string
* String that may contain tokens surrounded by '[' and ']'.
*
- * @return string|null
+ * @return string
* String with replaced tokens if replacements are available or
* original string.
*/
- protected function tokenProcess(string $string): ?string {
- return preg_replace_callback('/(?:\[([^\]]+)\])/', function (array $match): string {
+ protected function tokenProcess(string $string): string {
+ $processed = preg_replace_callback('/(?:\[([^\]]+)\])/', function (array $match): string {
+ $replacement = strval($match[0]);
+
if (!empty($match[1])) {
$parts = explode(':', $match[1], 2);
- $token = $parts[0] ?? NULL;
+ $token = $parts[0];
$argument = $parts[1] ?? NULL;
- if ($token) {
+ if ($token !== '' && $token !== '0') {
$method = 'getToken' . ucfirst($token);
if (method_exists($this, $method) && is_callable([$this, $method])) {
- $match[0] = (string) $this->$method($argument);
+ $replacement = (string) $this->$method($argument);
}
}
}
- return strval($match[0]);
+ return $replacement;
}, $string);
+
+ return $processed ?? $string;
}
/**
diff --git a/tests/phpunit/AbstractTestCase.php b/tests/phpunit/AbstractTestCase.php
deleted file mode 100644
index 0af80ea..0000000
--- a/tests/phpunit/AbstractTestCase.php
+++ /dev/null
@@ -1,73 +0,0 @@
-fs = new Filesystem();
- $this->git = new ArtifactGit();
-
- $this->fixtureDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'git_artifact';
- $this->fs->mkdir($this->fixtureDir);
-
- $this->commandTraitSetUp(
- $this->fixtureDir . DIRECTORY_SEPARATOR . 'git_src',
- $this->fixtureDir . DIRECTORY_SEPARATOR . 'git_remote',
- );
- }
-
- /**
- * {@inheritdoc}
- */
- protected function tearDown(): void {
- $this->commandTraitTearDown();
-
- if ($this->fs->exists($this->fixtureDir)) {
- $this->fs->remove($this->fixtureDir);
- }
- }
-
-}
diff --git a/tests/phpunit/Exception/ErrorException.php b/tests/phpunit/Exception/ErrorException.php
deleted file mode 100644
index f4e7e43..0000000
--- a/tests/phpunit/Exception/ErrorException.php
+++ /dev/null
@@ -1,24 +0,0 @@
-file = $file;
- $this->line = $line;
- }
-
-}
diff --git a/tests/phpunit/Functional/AbstractFunctionalTestCase.php b/tests/phpunit/Functional/AbstractFunctionalTestCase.php
index a5c21b1..b434b44 100644
--- a/tests/phpunit/Functional/AbstractFunctionalTestCase.php
+++ b/tests/phpunit/Functional/AbstractFunctionalTestCase.php
@@ -4,12 +4,48 @@
namespace DrevOps\GitArtifact\Tests\Functional;
-use DrevOps\GitArtifact\Tests\AbstractTestCase;
+use DrevOps\GitArtifact\Commands\ArtifactCommand;
+use DrevOps\GitArtifact\Tests\Traits\FixtureTrait;
+use DrevOps\GitArtifact\Tests\Traits\GitTrait;
+use DrevOps\GitArtifact\Tests\Unit\AbstractUnitTestCase;
+use DrevOps\GitArtifact\Traits\FilesystemTrait;
+use PHPUnit\Framework\AssertionFailedError;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Output\ConsoleOutput;
+use Symfony\Component\Console\Tester\CommandTester;
+use Symfony\Component\Filesystem\Filesystem;
-/**
- * Class AbstractTestCase.
- */
-abstract class AbstractFunctionalTestCase extends AbstractTestCase {
+abstract class AbstractFunctionalTestCase extends AbstractUnitTestCase {
+
+ use FilesystemTrait;
+ use FixtureTrait;
+ use GitTrait;
+
+ /**
+ * Fixture source repository directory.
+ *
+ * @var string
+ */
+ protected $src;
+
+ /**
+ * Fixture remote repository directory.
+ *
+ * @var string
+ */
+ protected $dst;
+
+ /**
+ * Artifact command.
+ */
+ protected ArtifactCommand $command;
+
+ /**
+ * Fixture directory.
+ *
+ * @var string
+ */
+ protected $fixtureDir;
/**
* Current branch.
@@ -30,7 +66,7 @@ abstract class AbstractFunctionalTestCase extends AbstractTestCase {
*
* @var string
*/
- protected $remote;
+ protected $remoteName;
/**
* Mode in which the build will run.
@@ -56,31 +92,55 @@ abstract class AbstractFunctionalTestCase extends AbstractTestCase {
protected function setUp(): void {
parent::setUp();
+ $this->fs = new Filesystem();
+
+ $this->fixtureDir = $this->fsGetAbsolutePath(sys_get_temp_dir() . DIRECTORY_SEPARATOR . date('U') . DIRECTORY_SEPARATOR . 'git_artifact');
+
+ $this->src = $this->fsGetAbsolutePath($this->fixtureDir . DIRECTORY_SEPARATOR . 'src');
+ $this->gitInitRepo($this->src);
+
+ $this->dst = $this->fixtureDir . DIRECTORY_SEPARATOR . 'dst';
+ $this->gitInitRepo($this->dst)
+ // Allow pushing into already checked out branch. We need this to
+ // avoid additional management of fixture repository.
+ ->run('config', ['receive.denyCurrentBranch', 'ignore']);
+
$this->now = time();
- $this->currentBranch = 'master';
- $this->artifactBranch = 'master-artifact';
- $this->remote = 'dst';
+ $this->currentBranch = $this->gitGetGlobalDefaultBranch();
+ $this->artifactBranch = $this->currentBranch . '-artifact';
+ $this->remoteName = 'dst';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function tearDown(): void {
+ if ($this->fs->exists($this->fixtureDir)) {
+ $this->fs->remove($this->fixtureDir);
+ }
}
/**
* Build the artifact and assert success.
*
- * @param string $args
- * Optional string of arguments to pass to the build.
+ * @param array $args
+ * Array of arguments to pass to the build.
* @param string $branch
- * Optional --branch value. Defaults to 'testbranch'.
+ * Expected branch name.
* @param string $commit
* Optional commit string. Defaults to 'Deployment commit'.
*
* @return string
* Command output.
*/
- protected function assertBuildSuccess(string $args = '', string $branch = 'testbranch', string $commit = 'Deployment commit'): string {
- $output = $this->runBuild(sprintf('--branch=%s %s', $branch, $args));
+ protected function assertCommandSuccess(?array $args = [], string $branch = 'testbranch', string $commit = 'Deployment commit'): string {
+ $args += ['--branch' => 'testbranch'];
+ $output = $this->runCommand($args);
+
$this->assertStringNotContainsString('[error]', $output);
$this->assertStringContainsString(sprintf('Pushed branch "%s" with commit message "%s"', $branch, $commit), $output);
$this->assertStringContainsString('Deployment finished successfully.', $output);
- $this->assertStringNotContainsString('Deployment failed.', $output);
+ $this->assertStringNotContainsString('Processing failed with an error:', $output);
return $output;
}
@@ -88,21 +148,23 @@ protected function assertBuildSuccess(string $args = '', string $branch = 'testb
/**
* Build the artifact and assert failure.
*
- * @param string $args
- * Optional string of arguments to pass to the build.
- * @param string $branch
- * Optional --branch value. Defaults to 'testbranch'.
+ * @param array $args
+ * * Array of arguments to pass to the build.
+ * * @param string $branch
+ * * Expected branch name.
* @param string $commit
* Optional commit string. Defaults to 'Deployment commit'.
*
* @return string
* Command output.
*/
- protected function assertBuildFailure(string $args = '', string $branch = 'testbranch', string $commit = 'Deployment commit'): string {
- $output = $this->runBuild(sprintf('--branch=%s %s', $branch, $args), TRUE);
- $this->assertStringNotContainsString(sprintf('Pushed branch "%s" with commit message "%s"', $branch, $commit), $output);
+ protected function assertCommandFailure(?array $args = [], string $commit = 'Deployment commit'): string {
+ $args += ['--branch' => 'testbranch'];
+ $output = $this->runCommand($args, TRUE);
+
+ $this->assertStringNotContainsString(sprintf('Pushed branch "%s" with commit message "%s"', $args['--branch'], $commit), $output);
$this->assertStringNotContainsString('Deployment finished successfully.', $output);
- $this->assertStringContainsString('Deployment failed.', $output);
+ $this->assertStringContainsString('Processing failed with an error:', $output);
return $output;
}
@@ -110,72 +172,101 @@ protected function assertBuildFailure(string $args = '', string $branch = 'testb
/**
* Run artifact build.
*
- * @param string $args
- * Additional arguments or options as a string.
- * @param bool $expectFail
+ * @param array $args
+ * Additional arguments or options as an associative array.
+ * @param bool $expect_fail
* Expect on fail.
*
* @return string
* Output string.
*/
- protected function runBuild(string $args = '', bool $expectFail = FALSE): string {
- if ($this->mode) {
- $args .= ' --mode=' . $this->mode;
- }
+ protected function runCommand(?array $args = [], bool $expect_fail = FALSE): string {
+ try {
- $output = $this->runGitArtifactCommandTimestamped(sprintf('--src=%s %s %s', $this->src, $this->dst, $args), $expectFail);
+ if (is_null($args)) {
+ $input = [];
+ }
+ else {
+ $input = [
+ '--root' => $this->fixtureDir,
+ '--now' => $this->now,
+ '--src' => $this->src,
+ 'remote' => $this->dst,
+ ];
- return implode(PHP_EOL, $output);
- }
+ if ($this->mode) {
+ $input['--mode'] = $this->mode;
+ }
- /**
- * Run command with current timestamp attached to artifact commands.
- *
- * @param string $command
- * Command string to run.
- * @param bool $expectFail
- * Flag to state that the command should fail.
- *
- * @return array
- * Array of output lines.
- */
- protected function runGitArtifactCommandTimestamped(string $command, bool $expectFail = FALSE): array {
- // Add --now option to all 'artifact' commands.
- $command .= ' --now=' . $this->now;
+ $input += $args;
+ }
+
+ $this->runExecute(ArtifactCommand::class, $input);
+ $output = $this->commandTester->getDisplay();
+
+ if ($this->commandTester->getStatusCode() !== 0) {
+ throw new \Exception(sprintf("Command exited with non-zero code.\nThe output was:\n%s\nThe error output was:\n%s", $this->commandTester->getDisplay(), $this->commandTester->getErrorOutput()));
+ }
- return $this->commandRunGitArtifactCommand($command, $expectFail);
+ if ($expect_fail) {
+ throw new AssertionFailedError(sprintf("Command exited successfully but should not.\nThe output was:\n%s\nThe error output was:\n%s", $this->commandTester->getDisplay(), $this->commandTester->getErrorOutput()));
+ }
+
+ }
+ catch (\RuntimeException $exception) {
+ if (!$expect_fail) {
+ throw new AssertionFailedError('Command exited with an error:' . PHP_EOL . $exception->getMessage());
+ }
+ $output = $exception->getMessage();
+ }
+ catch (\Exception $exception) {
+ if (!$expect_fail) {
+ throw new AssertionFailedError('Command exited with an error:' . PHP_EOL . $exception->getMessage());
+ }
+ }
+
+ return $output;
}
/**
- * Assert current git branch.
+ * CommandTester instance.
*
- * @param string $path
- * Path to repository.
- * @param string $branch
- * Branch name to assert.
+ * @var \Symfony\Component\Console\Tester\CommandTester
*/
- protected function assertGitCurrentBranch(string $path, string $branch): void {
- $currentBranch = $this->git->open($path)->getCurrentBranchName();
-
- $this->assertStringContainsString($branch, $currentBranch, sprintf('Current branch is "%s"', $branch));
- }
+ protected $commandTester;
/**
- * Assert that there is no remote specified in git repository.
+ * Run main() with optional arguments.
*
- * @param string $path
- * Path to repository.
- * @param string $remote
- * Remote name to assert.
+ * @param string|object $object_or_class
+ * Object or class name.
+ * @param array $input
+ * Optional array of input arguments.
+ * @param array $options
+ * Optional array of options. See CommandTester::execute() for details.
*/
- protected function assertGitNoRemote(string $path, string $remote): void {
- $remotes = $this->git->open($path)->getRemotes();
- if (empty($remotes)) {
- $this->assertEmpty($remotes);
+ protected function runExecute(string|object $object_or_class, array $input = [], array $options = []): void {
+ $application = new Application();
+ /** @var \Symfony\Component\Console\Command\Command $instance */
+ $instance = is_object($object_or_class) ? $object_or_class : new $object_or_class();
+ $application->add($instance);
+
+ $name = $instance->getName();
+ if (empty($name)) {
+ /** @var string $name */
+ $name = $this->getProtectedValue($instance, 'defaultName');
}
- else {
- $this->assertStringNotContainsString($remote, implode("\n", $remotes), sprintf('Remote "%s" is not present"', $remote));
+
+ $command = $application->find($name);
+ $this->commandTester = new CommandTester($command);
+
+ $options['capture_stderr_separately'] = TRUE;
+ if (array_key_exists('-vvv', $input)) {
+ $options['verbosity'] = ConsoleOutput::VERBOSITY_DEBUG;
+ unset($input['-vvv']);
}
+
+ $this->commandTester->execute($input, $options);
}
}
diff --git a/tests/phpunit/Functional/BranchModeTest.php b/tests/phpunit/Functional/BranchModeTest.php
new file mode 100644
index 0000000..dbba544
--- /dev/null
+++ b/tests/phpunit/Functional/BranchModeTest.php
@@ -0,0 +1,151 @@
+mode = 'branch';
+ parent::setUp();
+ }
+
+ public function testBuild(): void {
+ $this->gitCreateFixtureCommits(2);
+
+ $output = $this->assertCommandSuccess();
+ $this->assertStringContainsString('WARNING! Provided branch name does not have a token', $output);
+ $this->assertStringContainsString('Mode: branch', $output);
+ $this->assertStringContainsString('Will push: Yes', $output);
+
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+ }
+
+ public function testBuildMoreCommitsSameBranch(): void {
+ $this->gitCreateFixtureCommits(2);
+
+ $this->assertCommandSuccess();
+
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+
+ $this->gitCreateFixtureCommits(3, 2);
+ $this->assertCommandFailure();
+
+ // Make sure that broken artifact was not pushed.
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+ }
+
+ public function testBuildMoreCommits(): void {
+ $this->gitCreateFixtureCommits(2);
+
+ $this->now = time() - rand(1, 10 * 60);
+ $branch1 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now);
+ $output = $this->assertCommandSuccess(['--branch' => 'testbranch-[timestamp:Y-m-d_H-i-s]'], $branch1);
+ $this->assertStringContainsString('Remote branch: ' . $branch1, $output);
+ $this->assertStringNotContainsString('WARNING! Provided branch name does not have a token', $output);
+
+ $this->gitAssertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']);
+
+ $this->gitCreateFixtureCommits(3, 2);
+
+ $this->now = time() - rand(1, 10 * 60);
+ $branch2 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now);
+ $output = $this->assertCommandSuccess(['--branch' => 'testbranch-[timestamp:Y-m-d_H-i-s]'], $branch2);
+ $this->assertStringContainsString('Remote branch: ' . $branch2, $output);
+ $this->gitAssertFixtureCommits(5, $this->dst, $branch2, ['Deployment commit']);
+
+ // Also, check that no changes were done to branch1.
+ $this->gitAssertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']);
+ }
+
+ public function testCleanupAfterSuccess(): void {
+ $this->gitCreateFixtureCommits(2);
+
+ $this->assertCommandSuccess();
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+
+ $this->gitAssertCurrentBranch($this->src, $this->currentBranch);
+ $this->gitAssertRemoteNotExists($this->src, $this->remoteName);
+ }
+
+ public function testCleanupAfterFailure(): void {
+ $this->gitCreateFixtureCommits(2);
+
+ $this->assertCommandSuccess();
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+
+ $this->gitCreateFixtureCommits(3, 2);
+ // Trigger erroneous build by pushing to the same branch.
+ $this->assertCommandFailure();
+
+ $this->gitAssertCurrentBranch($this->src, $this->currentBranch);
+ $this->gitAssertRemoteNotExists($this->src, $this->remoteName);
+ }
+
+ public function testGitignore(): void {
+ $this->fixtureCreateFile($this->src, '.gitignore', 'f3');
+ $this->gitCreateFixtureCommits(2);
+ $this->fixtureCreateFile($this->src, 'f3');
+
+ $this->now = time() - rand(1, 10 * 60);
+ $branch1 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now);
+ $this->assertCommandSuccess(['--branch' => 'testbranch-[timestamp:Y-m-d_H-i-s]'], $branch1);
+
+ $this->gitAssertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']);
+ $this->assertFileDoesNotExist($this->dst . DIRECTORY_SEPARATOR . 'f3');
+
+ // Now, remove the .gitignore and push again.
+ $this->fixtureRemoveFile($this->src, '.gitignore');
+ $this->gitCommitAll($this->src, 'Commit number 3');
+ $this->now = time() - rand(1, 10 * 60);
+ $branch2 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now);
+ $this->assertCommandSuccess(['--branch' => 'testbranch-[timestamp:Y-m-d_H-i-s]'], $branch2);
+
+ $this->gitAssertFixtureCommits(3, $this->dst, $branch2, ['Deployment commit']);
+
+ // Assert that branch from previous deployment was not affected.
+ $this->gitAssertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']);
+ $this->assertFileDoesNotExist($this->dst . DIRECTORY_SEPARATOR . 'f3');
+ }
+
+ public function testGitignoreCustom(): void {
+ $this->fixtureCreateFile($this->src, 'mygitignore', 'f3');
+ $this->gitCreateFixtureCommits(2);
+ $this->fixtureCreateFile($this->src, 'f3');
+
+ $this->now = time() - rand(1, 10 * 60);
+ $branch1 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now);
+ $this->assertCommandSuccess([
+ '--branch' => 'testbranch-[timestamp:Y-m-d_H-i-s]',
+ '--gitignore' => $this->src . DIRECTORY_SEPARATOR . 'mygitignore',
+ ], $branch1);
+
+ $this->gitAssertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']);
+ $this->assertFileDoesNotExist($this->dst . DIRECTORY_SEPARATOR . 'f3');
+
+ // Now, remove the .gitignore and push again.
+ $this->fixtureCreateFile($this->src, 'f3');
+ $this->fixtureRemoveFile($this->src, 'mygitignore');
+ $this->gitCommitAll($this->src, 'Commit number 3');
+ $this->now = time() - rand(1, 10 * 60);
+ $branch2 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now);
+ $this->assertCommandSuccess(['--branch' => 'testbranch-[timestamp:Y-m-d_H-i-s]'], $branch2);
+
+ $this->gitAssertFixtureCommits(3, $this->dst, $branch2, ['Deployment commit']);
+
+ // Assert that branch from previous deployment was not affected.
+ $this->gitAssertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']);
+ $this->assertFileDoesNotExist($this->dst . DIRECTORY_SEPARATOR . 'f3');
+ }
+
+}
diff --git a/tests/phpunit/Functional/BranchTest.php b/tests/phpunit/Functional/BranchTest.php
deleted file mode 100644
index ffca788..0000000
--- a/tests/phpunit/Functional/BranchTest.php
+++ /dev/null
@@ -1,150 +0,0 @@
-mode = 'branch';
- parent::setUp();
- }
-
- public function testBuild(): void {
- $this->gitCreateFixtureCommits(2);
-
- $output = $this->assertBuildSuccess();
- $this->assertStringContainsString('WARNING! Provided branch name does not have a token', $output);
- $this->assertStringContainsString('Mode: branch', $output);
- $this->assertStringContainsString('Will push: Yes', $output);
-
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
- }
-
- public function testBuildMoreCommitsSameBranch(): void {
- $this->gitCreateFixtureCommits(2);
-
- $this->assertBuildSuccess();
-
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
-
- $this->gitCreateFixtureCommits(3, 2);
- $this->assertBuildFailure();
-
- // Make sure that broken artifact was not pushed.
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
- }
-
- public function testBuildMoreCommits(): void {
- $this->gitCreateFixtureCommits(2);
-
- $this->now = time() - rand(1, 10 * 60);
- $branch1 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now);
- $output = $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch1);
- $this->assertStringContainsString('Remote branch: ' . $branch1, $output);
- $this->assertStringNotContainsString('WARNING! Provided branch name does not have a token', $output);
-
- $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']);
-
- $this->gitCreateFixtureCommits(3, 2);
-
- $this->now = time() - rand(1, 10 * 60);
- $branch2 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now);
- $output = $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch2);
- $this->assertStringContainsString('Remote branch: ' . $branch2, $output);
- $this->assertFixtureCommits(5, $this->dst, $branch2, ['Deployment commit']);
-
- // Also, check that no changes were done to branch1.
- $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']);
- }
-
- public function testCleanupAfterSuccess(): void {
- $this->gitCreateFixtureCommits(2);
-
- $this->assertBuildSuccess();
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
-
- $this->assertGitCurrentBranch($this->src, $this->currentBranch);
- $this->assertGitNoRemote($this->src, $this->remote);
- }
-
- public function testCleanupAfterFailure(): void {
- $this->gitCreateFixtureCommits(2);
-
- $this->assertBuildSuccess('', 'testbranch');
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
-
- $this->gitCreateFixtureCommits(3, 2);
- // Trigger erroneous build by pushing to the same branch.
- $this->assertBuildFailure('', 'testbranch');
-
- $this->assertGitCurrentBranch($this->src, $this->currentBranch);
- $this->assertGitNoRemote($this->src, $this->remote);
- }
-
- public function testGitignore(): void {
- $this->gitCreateFixtureFile($this->src, '.gitignore', 'f3');
- $this->gitCreateFixtureCommits(2);
- $this->gitCreateFixtureFile($this->src, 'f3');
-
- $this->now = time() - rand(1, 10 * 60);
- $branch1 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now);
- $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch1);
-
- $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']);
- $this->gitAssertFilesNotExist($this->dst, 'f3');
-
- // Now, remove the .gitignore and push again.
- $this->gitRemoveFixtureFile($this->src, '.gitignore');
- $this->gitCommitAll($this->src, 'Commit number 3');
- $this->now = time() - rand(1, 10 * 60);
- $branch2 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now);
- $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch2);
-
- $this->assertFixtureCommits(3, $this->dst, $branch2, ['Deployment commit']);
-
- // Assert that branch from previous deployment was not affected.
- $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']);
- $this->gitAssertFilesNotExist($this->dst, 'f3');
- }
-
- public function testGitignoreCustom(): void {
- $this->gitCreateFixtureFile($this->src, 'mygitignore', 'f3');
- $this->gitCreateFixtureCommits(2);
- $this->gitCreateFixtureFile($this->src, 'f3');
-
- $this->now = time() - rand(1, 10 * 60);
- $branch1 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now);
- $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s] --gitignore=' . $this->src . DIRECTORY_SEPARATOR . 'mygitignore', $branch1);
-
- $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']);
- $this->gitAssertFilesNotExist($this->dst, 'f3');
-
- // Now, remove the .gitignore and push again.
- $this->gitCreateFixtureFile($this->src, 'f3');
- $this->gitRemoveFixtureFile($this->src, 'mygitignore');
- $this->gitCommitAll($this->src, 'Commit number 3');
- $this->now = time() - rand(1, 10 * 60);
- $branch2 = 'testbranch-' . date('Y-m-d_H-i-s', $this->now);
- $this->assertBuildSuccess('--branch=testbranch-[timestamp:Y-m-d_H-i-s]', $branch2);
-
- $this->assertFixtureCommits(3, $this->dst, $branch2, ['Deployment commit']);
-
- // Assert that branch from previous deployment was not affected.
- $this->assertFixtureCommits(2, $this->dst, $branch1, ['Deployment commit']);
- $this->gitAssertFilesNotExist($this->dst, 'f3');
- }
-
-}
diff --git a/tests/phpunit/Functional/ForcePushModeTest.php b/tests/phpunit/Functional/ForcePushModeTest.php
new file mode 100644
index 0000000..b7e5a77
--- /dev/null
+++ b/tests/phpunit/Functional/ForcePushModeTest.php
@@ -0,0 +1,311 @@
+mode = 'force-push';
+ parent::setUp();
+ }
+
+ public function testBuild(): void {
+ $this->gitCreateFixtureCommits(2);
+
+ $output = $this->assertCommandSuccess();
+ $this->assertStringContainsString('Mode: force-push', $output);
+ $this->assertStringContainsString('Will push: Yes', $output);
+
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+ }
+
+ public function testBuildMoreCommits(): void {
+ $this->gitCreateFixtureCommits(2);
+
+ $this->assertCommandSuccess();
+
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+
+ $this->gitCreateFixtureCommits(3, 2);
+ $this->assertCommandSuccess();
+
+ $this->gitAssertFixtureCommits(5, $this->dst, 'testbranch', ['Deployment commit']);
+ }
+
+ public function testIdempotence(): void {
+ $this->gitCreateFixtureCommits(2);
+
+ $this->assertCommandSuccess();
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+
+ $this->assertCommandSuccess();
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+ }
+
+ public function testSubRepos(): void {
+ $this->gitCreateFixtureCommits(2);
+
+ $this->fixtureCreateFile($this->src, 'c');
+ $this->gitCommitAll($this->src, 'Commit number 3');
+
+ $this->gitInitRepo($this->src . DIRECTORY_SEPARATOR . 'r1/');
+ $this->fixtureCreateFile($this->src, 'r1/c');
+
+ $this->gitInitRepo($this->src . DIRECTORY_SEPARATOR . 'r2/r21');
+ $this->fixtureCreateFile($this->src, 'r2/r21/c');
+
+ $this->gitInitRepo($this->src . DIRECTORY_SEPARATOR . 'r3/r31/r311');
+ $this->fixtureCreateFile($this->src, 'r3/r31/r311/c');
+
+ $this->gitAssertFilesExist($this->src, ['r1/c']);
+ $this->gitAssertFilesNotExist($this->src, ['r1/.git/index']);
+ $this->gitAssertFilesNotExist($this->src, ['r2/r21.git/index']);
+ $this->gitAssertFilesNotExist($this->src, ['r3/r31/r311/.git/index']);
+
+ $output = $this->assertCommandSuccess(['-vvv' => TRUE]);
+ $this->assertStringContainsString(sprintf('Removing sub-repository "%s"', $this->fsGetAbsolutePath($this->src . DIRECTORY_SEPARATOR . 'r1/.git')), $output);
+ $this->assertStringContainsString(sprintf('Removing sub-repository "%s"', $this->fsGetAbsolutePath($this->src . DIRECTORY_SEPARATOR . 'r2/r21/.git')), $output);
+ $this->assertStringContainsString(sprintf('Removing sub-repository "%s"', $this->fsGetAbsolutePath($this->src . DIRECTORY_SEPARATOR . 'r3/r31/r311/.git')), $output);
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Commit number 3', 'Deployment commit']);
+
+ $this->gitAssertFilesExist($this->dst, ['r1/c']);
+ $this->gitAssertFilesExist($this->dst, ['r2/r21/c']);
+ $this->gitAssertFilesExist($this->dst, ['r3/r31/r311/c']);
+ $this->gitAssertFilesNotExist($this->dst, ['r1/.git/index']);
+ $this->gitAssertFilesNotExist($this->dst, ['r1/.git']);
+ $this->gitAssertFilesNotExist($this->dst, ['r2/r21/.git/index']);
+ $this->gitAssertFilesNotExist($this->dst, ['r2/r21/.git']);
+ $this->gitAssertFilesNotExist($this->dst, ['r3/r31/311/.git/index']);
+ $this->gitAssertFilesNotExist($this->dst, ['r3/r31/311/.git']);
+ }
+
+ public function testCleanupAfterSuccess(): void {
+ $this->gitCreateFixtureCommits(2);
+
+ $this->assertCommandSuccess();
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+
+ $this->gitAssertCurrentBranch($this->src, $this->currentBranch);
+ $this->gitAssertRemoteNotExists($this->src, $this->remoteName);
+ }
+
+ public function testCleanupAfterFailure(): void {
+ $this->gitCreateFixtureCommits(1);
+
+ $output = $this->assertCommandFailure(['--branch' => '*invalid']);
+
+ $this->assertStringContainsString('Incorrect value "*invalid" specified for git remote branch', $output);
+ $this->gitAssertCurrentBranch($this->src, $this->currentBranch);
+ $this->gitAssertRemoteNotExists($this->src, $this->remoteName);
+ }
+
+ public function testGitignore(): void {
+ $this->fixtureCreateFile($this->src, '.gitignore', 'f3');
+ $this->gitCreateFixtureCommits(2);
+ $this->fixtureCreateFile($this->src, 'f3');
+
+ $this->assertCommandSuccess();
+
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+ $this->gitAssertFilesNotExist($this->dst, 'f3');
+
+ // Now, remove the .gitignore and push again.
+ $this->fixtureRemoveFile($this->src, '.gitignore');
+ $this->gitCommitAll($this->src, 'Commit number 3');
+ $this->assertCommandSuccess();
+ $this->gitAssertFixtureCommits(3, $this->dst, 'testbranch', ['Deployment commit']);
+ }
+
+ public function testGitignoreCustom(): void {
+ $this->gitCreateFixtureCommits(2);
+ $this->fixtureCreateFile($this->src, 'uic');
+ $this->fixtureCreateFile($this->src, 'uc');
+
+ $this->fixtureCreateFile($this->src, 'mygitignore', 'uic');
+
+ $this->assertCommandSuccess(['--gitignore' => $this->src . DIRECTORY_SEPARATOR . 'mygitignore']);
+
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+ $this->gitAssertFilesNotExist($this->dst, 'uic');
+ $this->gitAssertFilesExist($this->dst, 'uc');
+
+ // Now, remove the .gitignore and push again.
+ // We have to create 'uic' file since it was rightfully
+ // removed during previous build run and the source repo branch was not
+ // reset (uncommitted files would be removed, unless they are excluded
+ // in .gitignore).
+ $this->fixtureCreateFile($this->src, 'uic');
+ $this->fixtureRemoveFile($this->src, 'mygitignore');
+ $this->gitCommitAll($this->src, 'Commit number 3');
+ $this->assertCommandSuccess();
+
+ $this->gitAssertFixtureCommits(3, $this->dst, 'testbranch', ['Deployment commit'], FALSE);
+ $this->gitAssertFilesCommitted($this->dst, ['f1', 'f2', 'uic'], 'testbranch');
+ $this->gitAssertFilesExist($this->dst, ['f1', 'f2', 'uic'], 'testbranch');
+ $this->gitAssertFilesNotCommitted($this->dst, ['uc'], 'testbranch');
+ }
+
+ public function testGitignoreCustomRemoveCommittedFiles(): void {
+ $this->fixtureCreateFile($this->src, '.gitignore', ['ii', 'ic']);
+
+ $this->fixtureCreateFile($this->src, 'ii');
+ $this->fixtureCreateFile($this->src, 'ic');
+ $this->fixtureCreateFile($this->src, 'd/cc');
+ $this->fixtureCreateFile($this->src, 'd/ci');
+ $this->gitCreateFixtureCommits(2);
+ $this->gitCommitAll($this->src, 'Custom third commit');
+ $this->fixtureCreateFile($this->src, 'ui');
+ $this->fixtureCreateFile($this->src, 'uc');
+ $this->gitAssertFilesCommitted($this->src, ['.gitignore', 'f1', 'f2', 'd/cc', 'd/ci']);
+ $this->gitAssertFilesNotCommitted($this->src, ['ii', 'ic', 'ui', 'uc']);
+
+ $this->fixtureCreateFile($this->src, 'mygitignore', ['f1', 'ii', 'ci', 'ui']);
+
+ $this->assertCommandSuccess(['--gitignore' => $this->src . DIRECTORY_SEPARATOR . 'mygitignore']);
+
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Custom third commit', 'Deployment commit'], FALSE);
+ $this->gitAssertFilesCommitted($this->dst, ['.gitignore', 'f2', 'ic', 'd/cc', 'uc'], 'testbranch');
+ $this->gitAssertFilesNotCommitted($this->dst, ['f1', 'ii', 'd/ci', 'ui'], 'testbranch');
+ $this->gitAssertFilesExist($this->dst, ['f2', 'ic', 'd/cc', 'uc'], 'testbranch');
+ $this->gitAssertFilesNotExist($this->dst, ['f1', 'ii', 'd/ci', 'ui'], 'testbranch');
+ }
+
+ public function testGitignoreCustomAllowlisting(): void {
+ $this->fixtureCreateFile($this->src, '.gitignore', ['ii', 'ic', 'd_ic', 'd_ii', '/vendor']);
+
+ $this->fixtureCreateFile($this->src, 'ii');
+ $this->fixtureCreateFile($this->src, 'ic');
+ $this->fixtureCreateFile($this->src, 'cc');
+ $this->fixtureCreateFile($this->src, 'ci');
+
+ $this->fixtureCreateFile($this->src, 'd_cc/sub_cc');
+ $this->fixtureCreateFile($this->src, 'd_ci/sub_ci');
+ $this->fixtureCreateFile($this->src, 'd_ic/sub_ic');
+ $this->fixtureCreateFile($this->src, 'd_ii/sub_ii');
+
+ $this->fixtureCreateFile($this->src, 'vendor/ve_ii');
+ $this->fixtureCreateFile($this->src, 'vendor_cc');
+ $this->fixtureCreateFile($this->src, 'vendor_com with space com.txt');
+ $this->fixtureCreateFile($this->src, 'dir_other/vendor/ve_cc');
+
+ $this->gitCreateFixtureCommits(2);
+
+ $this->gitCommitAll($this->src, 'Custom third commit');
+
+ $this->gitAssertFilesCommitted($this->src, [
+ '.gitignore', 'f1', 'f2',
+ 'cc', 'ci',
+ 'd_cc/sub_cc', 'd_ci/sub_ci',
+ 'vendor_cc', 'dir_other/vendor/ve_cc', 'vendor_com with space com.txt',
+ ]);
+
+ $this->gitAssertFilesNotCommitted($this->src, [
+ 'ii', 'ic', 'ui', 'uc', 'ud',
+ 'd_ic/sub_ic', 'd_ii/sub_ii',
+ 'vendor/ve_ii',
+ ]);
+
+ $this->fixtureCreateFile($this->src, 'ui');
+ $this->fixtureCreateFile($this->src, 'uc');
+ $this->fixtureCreateFile($this->src, 'ud');
+ $this->fixtureCreateFile($this->src, 'd_ui/sub_ui');
+ $this->fixtureCreateFile($this->src, 'd_uc/sub_uc');
+ $this->fixtureCreateFile($this->src, 'd_ud/sub_ud');
+
+ // Now, create a custom .gitignore and add non-ignored files
+ // (allowlisting).
+ $this->fixtureCreateFile($this->src, 'mygitignore', [
+ '/*',
+ '!f2', '!ic', '!cc', '!uc',
+ '!d_cc', '!d_ic', '!d_uc',
+ '!vendor',
+ ]);
+
+ // Run the build.
+ $this->assertCommandSuccess([
+ '-vvv' => TRUE,
+ '--gitignore' => $this->src . DIRECTORY_SEPARATOR . 'mygitignore',
+ ]);
+
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Custom third commit', 'Deployment commit'], FALSE);
+
+ $this->gitAssertFilesCommitted($this->dst, [
+ 'f2', 'ic', 'cc', 'uc',
+ 'd_cc/sub_cc', 'd_ic/sub_ic', 'd_uc/sub_uc',
+ 'vendor/ve_ii',
+ ], 'testbranch');
+
+ $this->gitAssertFilesNotCommitted($this->dst, [
+ 'f1', 'ii', 'ci', 'ui', 'ud',
+ 'd_ci/sub_ci', 'd_ii/sub_ii', 'd_ui/sub_ui', 'd_ud/sub_ud',
+ 'vendor_cc', 'dir_other/vendor/ve_cc', 'vendor_com with space com.txt',
+ ], 'testbranch');
+
+ $this->gitAssertFilesExist($this->dst, [
+ 'f2', 'ic', 'cc', 'uc',
+ 'd_cc/sub_cc', 'd_ic/sub_ic', 'd_uc/sub_uc',
+ 'vendor/ve_ii',
+ ], 'testbranch');
+ $this->gitAssertFilesNotExist($this->dst, [
+ 'f1', 'ii', 'ci', 'ui', 'ud',
+ 'd_ci/sub_ci',
+ 'd_ii/sub_ii', 'd_ui/sub_ui', 'd_ud/sub_ud',
+ 'vendor_cc', 'dir_other/vendor/ve_cc', 'vendor_com with space com.txt',
+ ], 'testbranch');
+ }
+
+ public function testBuildTag(): void {
+ $this->gitCreateFixtureCommits(2);
+ $this->gitAddTag($this->src, 'tag1');
+
+ $this->assertCommandSuccess(['--branch' => '[tags]'], 'tag1');
+
+ $this->gitAssertFixtureCommits(2, $this->dst, 'tag1', ['Deployment commit']);
+ }
+
+ public function testBuildMultipleTags(): void {
+ $this->gitCreateFixtureCommits(2);
+ $this->gitAddTag($this->src, 'tag1');
+ $this->gitAddTag($this->src, 'tag2');
+
+ $this->assertCommandSuccess(['--branch' => '[tags]'], 'tag1-tag2');
+ $this->gitAssertFixtureCommits(2, $this->dst, 'tag1-tag2', ['Deployment commit']);
+
+ $this->gitCreateFixtureCommit(3);
+ $this->gitAddTag($this->src, 'tag3');
+ $this->assertCommandSuccess(['--branch' => '[tags]'], 'tag3');
+ $this->gitAssertFixtureCommits(3, $this->dst, 'tag3', ['Deployment commit']);
+ }
+
+ public function testBuildMultipleTagsMissingTags(): void {
+ $this->gitCreateFixtureCommits(2);
+ $this->gitAddTag($this->src, 'tag1');
+ $this->gitCreateFixtureCommit(3);
+
+ $this->assertCommandFailure(['--branch' => '[tags]']);
+ }
+
+ public function testBuildMultipleTagsDelimiter(): void {
+ $this->gitCreateFixtureCommits(2);
+ $this->gitAddTag($this->src, 'tag1');
+ $this->gitAddTag($this->src, 'tag2');
+
+ $this->assertCommandSuccess(['--branch' => '[tags:__]'], 'tag1__tag2');
+
+ $this->gitAssertFixtureCommits(2, $this->dst, 'tag1__tag2', ['Deployment commit']);
+ }
+
+}
diff --git a/tests/phpunit/Functional/ForcePushTest.php b/tests/phpunit/Functional/ForcePushTest.php
deleted file mode 100644
index 3ea5eeb..0000000
--- a/tests/phpunit/Functional/ForcePushTest.php
+++ /dev/null
@@ -1,306 +0,0 @@
-mode = 'force-push';
- parent::setUp();
- }
-
- public function testBuild(): void {
- $this->gitCreateFixtureCommits(2);
-
- $output = $this->assertBuildSuccess();
- $this->assertStringContainsString('Mode: force-push', $output);
- $this->assertStringContainsString('Will push: Yes', $output);
-
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
- }
-
- public function testBuildMoreCommits(): void {
- $this->gitCreateFixtureCommits(2);
-
- $this->assertBuildSuccess();
-
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
-
- $this->gitCreateFixtureCommits(3, 2);
- $this->assertBuildSuccess();
-
- $this->assertFixtureCommits(5, $this->dst, 'testbranch', ['Deployment commit']);
- }
-
- public function testIdempotence(): void {
- $this->gitCreateFixtureCommits(2);
-
- $this->assertBuildSuccess();
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
-
- $this->assertBuildSuccess();
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
- }
-
- public function testSubRepos(): void {
- $this->gitCreateFixtureCommits(2);
-
- $this->gitCreateFixtureFile($this->src, 'c');
- $this->gitCommitAll($this->src, 'Commit number 3');
-
- $this->gitInitRepo($this->src . DIRECTORY_SEPARATOR . 'r1/');
- $this->gitCreateFixtureFile($this->src, 'r1/c');
-
- $this->gitInitRepo($this->src . DIRECTORY_SEPARATOR . 'r2/r21');
- $this->gitCreateFixtureFile($this->src, 'r2/r21/c');
-
- $this->gitInitRepo($this->src . DIRECTORY_SEPARATOR . 'r3/r31/r311');
- $this->gitCreateFixtureFile($this->src, 'r3/r31/r311/c');
-
- $this->gitAssertFilesExist($this->src, ['r1/c']);
- $this->gitAssertFilesNotExist($this->src, ['r1/.git/index']);
- $this->gitAssertFilesNotExist($this->src, ['r2/r21.git/index']);
- $this->gitAssertFilesNotExist($this->src, ['r3/r31/r311/.git/index']);
-
- $output = $this->assertBuildSuccess('-vvv');
- $this->assertStringContainsString(sprintf('Removing sub-repository "%s"', $this->src . DIRECTORY_SEPARATOR . 'r1/.git'), $output);
- $this->assertStringContainsString(sprintf('Removing sub-repository "%s"', $this->src . DIRECTORY_SEPARATOR . 'r2/r21/.git'), $output);
- $this->assertStringContainsString(sprintf('Removing sub-repository "%s"', $this->src . DIRECTORY_SEPARATOR . 'r3/r31/r311/.git'), $output);
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Commit number 3', 'Deployment commit']);
-
- $this->gitAssertFilesExist($this->dst, ['r1/c']);
- $this->gitAssertFilesExist($this->dst, ['r2/r21/c']);
- $this->gitAssertFilesExist($this->dst, ['r3/r31/r311/c']);
- $this->gitAssertFilesNotExist($this->dst, ['r1/.git/index']);
- $this->gitAssertFilesNotExist($this->dst, ['r1/.git']);
- $this->gitAssertFilesNotExist($this->dst, ['r2/r21/.git/index']);
- $this->gitAssertFilesNotExist($this->dst, ['r2/r21/.git']);
- $this->gitAssertFilesNotExist($this->dst, ['r3/r31/311/.git/index']);
- $this->gitAssertFilesNotExist($this->dst, ['r3/r31/311/.git']);
- }
-
- public function testCleanupAfterSuccess(): void {
- $this->gitCreateFixtureCommits(2);
-
- $this->assertBuildSuccess();
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
-
- $this->assertGitCurrentBranch($this->src, $this->currentBranch);
- $this->assertGitNoRemote($this->src, $this->remote);
- }
-
- public function testCleanupAfterFailure(): void {
- $this->gitCreateFixtureCommits(1);
-
- $output = $this->assertBuildFailure('--branch=*invalid');
-
- $this->assertStringContainsString('Incorrect value "*invalid" specified for git remote branch', $output);
- $this->assertGitCurrentBranch($this->src, $this->currentBranch);
- $this->assertGitNoRemote($this->src, $this->remote);
- }
-
- public function testGitignore(): void {
- $this->gitCreateFixtureFile($this->src, '.gitignore', 'f3');
- $this->gitCreateFixtureCommits(2);
- $this->gitCreateFixtureFile($this->src, 'f3');
-
- $this->assertBuildSuccess();
-
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
- $this->gitAssertFilesNotExist($this->dst, 'f3');
-
- // Now, remove the .gitignore and push again.
- $this->gitRemoveFixtureFile($this->src, '.gitignore');
- $this->gitCommitAll($this->src, 'Commit number 3');
- $this->assertBuildSuccess();
- $this->assertFixtureCommits(3, $this->dst, 'testbranch', ['Deployment commit']);
- }
-
- public function testGitignoreCustom(): void {
- $this->gitCreateFixtureCommits(2);
- $this->gitCreateFixtureFile($this->src, 'uic');
- $this->gitCreateFixtureFile($this->src, 'uc');
-
- $this->gitCreateFixtureFile($this->src, 'mygitignore', 'uic');
-
- $this->assertBuildSuccess('--gitignore=' . $this->src . DIRECTORY_SEPARATOR . 'mygitignore');
-
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
- $this->gitAssertFilesNotExist($this->dst, 'uic');
- $this->gitAssertFilesExist($this->dst, 'uc');
-
- // Now, remove the .gitignore and push again.
- // We have to create 'uic' file since it was rightfully
- // removed during previous build run and the source repo branch was not
- // reset (uncommitted files would be removed, unless they are excluded
- // in .gitignore).
- $this->gitCreateFixtureFile($this->src, 'uic');
- $this->gitRemoveFixtureFile($this->src, 'mygitignore');
- $this->gitCommitAll($this->src, 'Commit number 3');
- $this->assertBuildSuccess();
-
- $this->assertFixtureCommits(3, $this->dst, 'testbranch', ['Deployment commit'], FALSE);
- $this->gitAssertFilesCommitted($this->dst, ['f1', 'f2', 'uic'], 'testbranch');
- $this->gitAssertFilesExist($this->dst, ['f1', 'f2', 'uic'], 'testbranch');
- $this->gitAssertNoFilesCommitted($this->dst, ['uc'], 'testbranch');
- }
-
- public function testGitignoreCustomRemoveCommittedFiles(): void {
- $this->gitCreateFixtureFile($this->src, '.gitignore', ['ii', 'ic']);
-
- $this->gitCreateFixtureFile($this->src, 'ii');
- $this->gitCreateFixtureFile($this->src, 'ic');
- $this->gitCreateFixtureFile($this->src, 'd/cc');
- $this->gitCreateFixtureFile($this->src, 'd/ci');
- $this->gitCreateFixtureCommits(2);
- $this->gitCommitAll($this->src, 'Custom third commit');
- $this->gitCreateFixtureFile($this->src, 'ui');
- $this->gitCreateFixtureFile($this->src, 'uc');
- $this->gitAssertFilesCommitted($this->src, ['.gitignore', 'f1', 'f2', 'd/cc', 'd/ci']);
- $this->gitAssertNoFilesCommitted($this->src, ['ii', 'ic', 'ui', 'uc']);
-
- $this->gitCreateFixtureFile($this->src, 'mygitignore', ['f1', 'ii', 'ci', 'ui']);
-
- $this->assertBuildSuccess('--gitignore=' . $this->src . DIRECTORY_SEPARATOR . 'mygitignore');
-
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Custom third commit', 'Deployment commit'], FALSE);
- $this->gitAssertFilesCommitted($this->dst, ['.gitignore', 'f2', 'ic', 'd/cc', 'uc'], 'testbranch');
- $this->gitAssertNoFilesCommitted($this->dst, ['f1', 'ii', 'd/ci', 'ui'], 'testbranch');
- $this->gitAssertFilesExist($this->dst, ['f2', 'ic', 'd/cc', 'uc'], 'testbranch');
- $this->gitAssertFilesNotExist($this->dst, ['f1', 'ii', 'd/ci', 'ui'], 'testbranch');
- }
-
- public function testGitignoreCustomWhitelisting(): void {
- $this->gitCreateFixtureFile($this->src, '.gitignore', ['ii', 'ic', 'd_ic', 'd_ii', '/vendor']);
-
- $this->gitCreateFixtureFile($this->src, 'ii');
- $this->gitCreateFixtureFile($this->src, 'ic');
- $this->gitCreateFixtureFile($this->src, 'cc');
- $this->gitCreateFixtureFile($this->src, 'ci');
-
- $this->gitCreateFixtureFile($this->src, 'd_cc/sub_cc');
- $this->gitCreateFixtureFile($this->src, 'd_ci/sub_ci');
- $this->gitCreateFixtureFile($this->src, 'd_ic/sub_ic');
- $this->gitCreateFixtureFile($this->src, 'd_ii/sub_ii');
-
- $this->gitCreateFixtureFile($this->src, 'vendor/ve_ii');
- $this->gitCreateFixtureFile($this->src, 'vendor_cc');
- $this->gitCreateFixtureFile($this->src, 'vendor_com with space com.txt');
- $this->gitCreateFixtureFile($this->src, 'dir_other/vendor/ve_cc');
-
- $this->gitCreateFixtureCommits(2);
-
- $this->gitCommitAll($this->src, 'Custom third commit');
-
- $this->gitAssertFilesCommitted($this->src, [
- '.gitignore', 'f1', 'f2',
- 'cc', 'ci',
- 'd_cc/sub_cc', 'd_ci/sub_ci',
- 'vendor_cc', 'dir_other/vendor/ve_cc', 'vendor_com with space com.txt',
- ]);
-
- $this->gitAssertNoFilesCommitted($this->src, [
- 'ii', 'ic', 'ui', 'uc', 'ud',
- 'd_ic/sub_ic', 'd_ii/sub_ii',
- 'vendor/ve_ii',
- ]);
-
- $this->gitCreateFixtureFile($this->src, 'ui');
- $this->gitCreateFixtureFile($this->src, 'uc');
- $this->gitCreateFixtureFile($this->src, 'ud');
- $this->gitCreateFixtureFile($this->src, 'd_ui/sub_ui');
- $this->gitCreateFixtureFile($this->src, 'd_uc/sub_uc');
- $this->gitCreateFixtureFile($this->src, 'd_ud/sub_ud');
-
- // Now, create a custom .gitignore and add non-ignored files
- // (whitelisting).
- $this->gitCreateFixtureFile($this->src, 'mygitignore', [
- '/*', '!f2', '!ic', '!cc', '!uc',
- '!d_cc', '!d_ic', '!d_uc',
- '!vendor',
- ]);
-
- // Run the build.
- $this->assertBuildSuccess('-vvv --gitignore=' . $this->src . DIRECTORY_SEPARATOR . 'mygitignore');
-
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Custom third commit', 'Deployment commit'], FALSE);
-
- $this->gitAssertFilesCommitted($this->dst, [
- 'f2', 'ic', 'cc', 'uc',
- 'd_cc/sub_cc', 'd_ic/sub_ic', 'd_uc/sub_uc',
- 'vendor/ve_ii',
- ], 'testbranch');
-
- $this->gitAssertNoFilesCommitted($this->dst, [
- 'f1', 'ii', 'ci', 'ui', 'ud',
- 'd_ci/sub_ci', 'd_ii/sub_ii', 'd_ui/sub_ui', 'd_ud/sub_ud',
- 'vendor_cc', 'dir_other/vendor/ve_cc', 'vendor_com with space com.txt',
- ], 'testbranch');
-
- $this->gitAssertFilesExist($this->dst, [
- 'f2', 'ic', 'cc', 'uc',
- 'd_cc/sub_cc', 'd_ic/sub_ic', 'd_uc/sub_uc',
- 'vendor/ve_ii',
- ], 'testbranch');
- $this->gitAssertFilesNotExist($this->dst, [
- 'f1', 'ii', 'ci', 'ui', 'ud',
- 'd_ci/sub_ci',
- 'd_ii/sub_ii', 'd_ui/sub_ui', 'd_ud/sub_ud',
- 'vendor_cc', 'dir_other/vendor/ve_cc', 'vendor_com with space com.txt',
- ], 'testbranch');
- }
-
- public function testBuildTag(): void {
- $this->gitCreateFixtureCommits(2);
- $this->gitAddTag($this->src, 'tag1');
-
- $this->assertBuildSuccess('--branch=[tags]', 'tag1');
-
- $this->assertFixtureCommits(2, $this->dst, 'tag1', ['Deployment commit']);
- }
-
- public function testBuildMultipleTags(): void {
- $this->gitCreateFixtureCommits(2);
- $this->gitAddTag($this->src, 'tag1');
- $this->gitAddTag($this->src, 'tag2');
-
- $this->assertBuildSuccess('--branch=[tags]', 'tag1-tag2');
- $this->assertFixtureCommits(2, $this->dst, 'tag1-tag2', ['Deployment commit']);
-
- $this->gitCreateFixtureCommit(3);
- $this->gitAddTag($this->src, 'tag3');
- $this->assertBuildSuccess('--branch=[tags]', 'tag3');
- $this->assertFixtureCommits(3, $this->dst, 'tag3', ['Deployment commit']);
- }
-
- public function testBuildMultipleTagsMissingTags(): void {
- $this->gitCreateFixtureCommits(2);
- $this->gitAddTag($this->src, 'tag1');
- $this->gitCreateFixtureCommit(3);
-
- $this->assertBuildFailure('--branch=[tags]');
- }
-
- public function testBuildMultipleTagsDelimiter(): void {
- $this->gitCreateFixtureCommits(2);
- $this->gitAddTag($this->src, 'tag1');
- $this->gitAddTag($this->src, 'tag2');
-
- $this->assertBuildSuccess('--branch=[tags:__]', 'tag1__tag2');
-
- $this->assertFixtureCommits(2, $this->dst, 'tag1__tag2', ['Deployment commit']);
- }
-
-}
diff --git a/tests/phpunit/Functional/GeneralTest.php b/tests/phpunit/Functional/GeneralTest.php
index fb86fbb..ba9e46e 100644
--- a/tests/phpunit/Functional/GeneralTest.php
+++ b/tests/phpunit/Functional/GeneralTest.php
@@ -4,31 +4,24 @@
namespace DrevOps\GitArtifact\Tests\Functional;
-/**
- * Class GeneralTest.
- *
- * @group integration
- *
- * @covers \DrevOps\GitArtifact\Commands\ArtifactCommand
- * @covers \DrevOps\GitArtifact\Traits\FilesystemTrait
- */
-class GeneralTest extends AbstractFunctionalTestCase {
+use DrevOps\GitArtifact\Commands\ArtifactCommand;
+use DrevOps\GitArtifact\Git\ArtifactGitRepository;
+use PHPUnit\Framework\Attributes\CoversClass;
- public function testHelp(): void {
- $output = $this->runGitArtifactCommand('--help');
- $this->assertStringContainsString('artifact [options] [--] ', implode(PHP_EOL, $output));
- $this->assertStringContainsString('Assemble a code artifact from your codebase, remove unnecessary files, and push it into a separate Git repository.', implode(PHP_EOL, $output));
- }
+#[CoversClass(ArtifactCommand::class)]
+#[CoversClass(ArtifactGitRepository::class)]
+class GeneralTest extends AbstractFunctionalTestCase {
public function testCompulsoryParameter(): void {
- $output = $this->runGitArtifactCommand('', TRUE);
+ $this->dst = '';
+ $output = $this->runCommand(['remote' => ' '], TRUE);
- $this->assertStringContainsString('Not enough arguments (missing: "remote")', implode(PHP_EOL, $output));
+ $this->assertStringContainsString('Remote argument must be a non-empty string', $output);
}
public function testInfo(): void {
$this->gitCreateFixtureCommits(1);
- $output = $this->runBuild('--dry-run');
+ $output = $this->runCommand(['--dry-run' => TRUE]);
$this->assertStringContainsString('Artifact information', $output);
$this->assertStringContainsString('Mode: force-push', $output);
$this->assertStringContainsString('Source repository: ' . $this->src, $output);
@@ -45,7 +38,10 @@ public function testInfo(): void {
public function testShowChanges(): void {
$this->gitCreateFixtureCommits(1);
- $output = $this->runBuild('--show-changes --dry-run');
+ $output = $this->runCommand([
+ '--show-changes' => TRUE,
+ '--dry-run' => TRUE,
+ ]);
$this->assertStringContainsString('Added changes:', $output);
@@ -55,16 +51,22 @@ public function testShowChanges(): void {
public function testNoCleanup(): void {
$this->gitCreateFixtureCommits(1);
- $output = $this->runBuild('--no-cleanup --dry-run');
+ $output = $this->runCommand([
+ '--no-cleanup' => TRUE,
+ '--dry-run' => TRUE,
+ ]);
- $this->assertGitCurrentBranch($this->src, $this->artifactBranch);
+ $this->gitAssertCurrentBranch($this->src, $this->artifactBranch);
$this->assertStringContainsString('Cowardly refusing to push to remote. Use without --dry-run to perform an actual push.', $output);
$this->gitAssertFilesNotExist($this->dst, 'f1', $this->currentBranch);
}
public function testDebug(): void {
$this->gitCreateFixtureCommits(1);
- $output = $this->runBuild('-vvv --dry-run');
+ $output = $this->runCommand([
+ '-vvv' => TRUE,
+ '--dry-run' => TRUE,
+ ]);
$this->assertStringContainsString('Debug messages enabled', $output);
$this->assertStringContainsString('Artifact information', $output);
@@ -90,7 +92,10 @@ public function testDebugLogFile(): void {
$report = $this->src . DIRECTORY_SEPARATOR . 'report.txt';
$this->gitCreateFixtureCommits(1);
- $commandOutput = $this->runBuild(sprintf('--dry-run --log=%s', $report));
+ $commandOutput = $this->runCommand([
+ '--dry-run' => TRUE,
+ '--log' => $report,
+ ]);
$this->assertStringContainsString('Debug messages enabled', $commandOutput);
$this->assertStringContainsString('Artifact information', $commandOutput);
@@ -131,7 +136,7 @@ public function testDebugLogFile(): void {
public function testDebugDisabled(): void {
$this->gitCreateFixtureCommits(1);
- $output = $this->runBuild('--dry-run');
+ $output = $this->runCommand(['--dry-run' => TRUE]);
$this->assertStringNotContainsString('Debug messages enabled', $output);
diff --git a/tests/phpunit/Functional/README.md b/tests/phpunit/Functional/README.md
index e223d10..86dc40a 100644
--- a/tests/phpunit/Functional/README.md
+++ b/tests/phpunit/Functional/README.md
@@ -6,12 +6,12 @@
- `u` - uncommitted
- `d` - deleted
- `d_` - directory
-- `sub_` - sub directory or file; used for testing wildcard name matching (i.e. `d_1/f1` vs `d_1/sub_f1`)
+- `sub_` - sub-directory or file; used for testing wildcard name matching (i.e. `d_1/f1` vs `d_1/sub_f1`)
## Examples
- `f1`, `f2` - files `f1` and `f2`
- `d_1` - directory `d_1`
- `d_1/f1` - file `f1` in directory `d_1`.
-- `d_ui/sub_ui` - file `sub_ui` is uncommitted and ignored and is located
-in uncommitted and ignored directory `d_ui`.
+- `d_ui/sub_ui` - file `sub_ui` is uncommitted and ignored and is located
+in uncommitted and ignored directory `d_ui`.
diff --git a/tests/phpunit/Functional/TagTest.php b/tests/phpunit/Functional/TagTest.php
index d7498e3..7f09366 100644
--- a/tests/phpunit/Functional/TagTest.php
+++ b/tests/phpunit/Functional/TagTest.php
@@ -4,14 +4,13 @@
namespace DrevOps\GitArtifact\Tests\Functional;
-/**
- * Class TagTest.
- *
- * @group integration
- *
- * @covers \DrevOps\GitArtifact\Commands\ArtifactCommand
- * @covers \DrevOps\GitArtifact\Traits\FilesystemTrait
- */
+use CzProject\GitPhp\Git;
+use DrevOps\GitArtifact\Commands\ArtifactCommand;
+use DrevOps\GitArtifact\Git\ArtifactGitRepository;
+use PHPUnit\Framework\Attributes\CoversClass;
+
+#[CoversClass(ArtifactCommand::class)]
+#[CoversClass(ArtifactGitRepository::class)]
class TagTest extends AbstractFunctionalTestCase {
/**
@@ -26,15 +25,16 @@ public function testDetachedTag(): void {
$this->gitCreateFixtureCommits(2);
$this->gitAddTag($this->src, 'tag1');
$this->gitCheckout($this->src, 'tag1');
- $gitRepo = $this->git->open($this->src);
- $srcBranches = $gitRepo->getBranches();
- $output = $this->assertBuildSuccess();
+ $repo = (new Git())->open($this->src);
+ $branches = $repo->getBranches();
+
+ $output = $this->assertCommandSuccess();
$this->assertStringContainsString('Mode: force-push', $output);
$this->assertStringContainsString('Will push: Yes', $output);
- $this->assertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
- $this->assertEquals($srcBranches, $gitRepo->getBranches(), 'Cleanup has correctly returned to the previous branch.');
+ $this->gitAssertFixtureCommits(2, $this->dst, 'testbranch', ['Deployment commit']);
+ $this->assertEquals($branches, $repo->getBranches(), 'Cleanup has correctly returned to the previous branch.');
}
}
diff --git a/tests/phpunit/Functional/TokenTest.php b/tests/phpunit/Functional/TokenTest.php
deleted file mode 100644
index 08b78be..0000000
--- a/tests/phpunit/Functional/TokenTest.php
+++ /dev/null
@@ -1,103 +0,0 @@
-prepareMock(TokenTrait::class, [
- 'getToken' . ucfirst($name) => static function (?string $prop) use ($replacement): string {
- return empty($prop) ? $replacement : $replacement . ' with property ' . $prop;
- },
- ]);
-
- $actual = $this->callProtectedMethod($mock, 'tokenProcess', [$string]);
- $this->assertEquals($expectedString, $actual);
- }
-
- /**
- * @return array>
- * Data provider.
- */
- public static function dataProviderTokenProcess(): array {
- return [
- [
- '',
- '',
- '',
- '',
- ],
- [
- '',
- 'sometoken',
- 'somevalue',
- '',
- ],
- [
- 'string without a token',
- 'sometoken',
- 'somevalue',
- 'string without a token',
- ],
- [
- 'string with sometoken without delimiters',
- 'sometoken',
- 'somevalue',
- 'string with sometoken without delimiters',
- ],
- [
- 'string with [sometoken broken delimiters',
- 'sometoken',
- 'somevalue',
- 'string with [sometoken broken delimiters',
- ],
- [
- 'string with sometoken] broken delimiters',
- 'sometoken',
- 'somevalue',
- 'string with sometoken] broken delimiters',
- ],
- // Proper token.
- [
- '[sometoken]',
- 'sometoken',
- 'somevalue',
- 'somevalue',
- ],
- [
- 'string with [sometoken] present',
- 'sometoken',
- 'somevalue',
- 'string with somevalue present',
- ],
- // Token with properties.
- [
- 'string with [sometoken:prop] present',
- 'sometoken',
- 'somevalue',
- 'string with somevalue with property prop present',
- ],
- [
- 'string with [sometoken:prop:otherprop] present',
- 'sometoken',
- 'somevalue',
- 'string with somevalue with property prop:otherprop present',
- ],
- ];
- }
-
-}
diff --git a/tests/phpunit/Traits/CommandTrait.php b/tests/phpunit/Traits/CommandTrait.php
deleted file mode 100644
index 2063818..0000000
--- a/tests/phpunit/Traits/CommandTrait.php
+++ /dev/null
@@ -1,538 +0,0 @@
-fs = new Filesystem();
- $this->src = $src;
- $this->gitInitRepo($this->src);
- $this->dst = $remote;
- $remoteRepo = $this->gitInitRepo($this->dst);
- // Allow pushing into already checked out branch. We need this to
- // avoid additional management of fixture repository.
- $remoteRepo->setConfigReceiveDenyCurrentBranchIgnore();
- }
-
- /**
- * Tear down test.
- *
- * To be called by test's tearDown() method.
- */
- protected function tearDown(): void {
- if ($this->fs->exists($this->src)) {
- $this->fs->remove($this->src);
- }
- if ($this->fs->exists($this->dst)) {
- $this->fs->remove($this->dst);
- }
- }
-
- /**
- * Init git repository.
- *
- * @param string $path
- * Path to the repository directory.
- */
- protected function gitInitRepo(string $path): ArtifactGitRepository {
- if ($this->fs->exists($path)) {
- $this->fs->remove($path);
- }
- $this->fs->mkdir($path);
- /** @var \DrevOps\GitArtifact\Git\ArtifactGitRepository $repo */
- $repo = $this->git->init($path, ['-b' => 'master']);
-
- return $repo;
- }
-
- /**
- * Get all commit hashes in the repository.
- *
- * @param string $path
- * Path to the repository directory.
- * @param string $format
- * Format of commits.
- *
- * @return array
- * Array of commit hashes, sorted from the earliest to the latest commit.
- *
- * @throws \Exception
- */
- protected function gitGetAllCommits(string $path, string $format = '%s'): array {
- $commits = [];
- try {
- $commits = $this->git->open($path)->getCommits($format);
- }
- catch (\Exception $exception) {
- $output = ($exception->getPrevious() instanceof \Throwable) ? $exception->getPrevious()->getMessage() : '';
- $output = trim($output);
- // Different versions of Git may produce these expected messages.
- $expectedErrorMessages = [
- "fatal: bad default revision 'HEAD'",
- "fatal: your current branch 'master' does not have any commits yet",
- ];
- if (!in_array($output, $expectedErrorMessages)) {
- throw $exception;
- }
- }
-
- return array_reverse(array_filter($commits));
- }
-
- /**
- * Get a range of commits.
- *
- * @param array $range
- * Array of commit indexes, stating from 1.
- * @param string $path
- * Path to the repository directory.
- *
- * @return array
- * Array of commit hashes, ordered by keys in the $range.
- *
- * @throws \Exception
- */
- protected function gitGetCommitsHashesFromRange(array $range, string $path): array {
- $commits = $this->gitGetAllCommits($path);
-
- array_walk($range, static function (&$v) : void {
- --$v;
- });
-
- $ret = [];
- foreach ($range as $key) {
- $ret[] = $commits[$key];
- }
-
- return $ret;
- }
-
- /**
- * Get all committed files.
- *
- * @param string $path
- * Path to the repository directory.
- *
- * @return array
- * Array of commit committed files.
- */
- protected function gitGetCommittedFiles(string $path): array {
- return $this
- ->git
- ->open($path)
- ->listCommittedFiles();
- }
-
- /**
- * Create multiple fixture commits.
- *
- * @param int $count
- * Number of commits to create.
- * @param int $offset
- * Number of commit indices to offset.
- * @param string|null $path
- * Optional path to the repository directory. If not provided, fixture
- * directory is used.
- */
- protected function gitCreateFixtureCommits(int $count, int $offset = 0, ?string $path = NULL): void {
- $path = $path ? $path : $this->src;
- for ($i = $offset; $i < $count + $offset; $i++) {
- $this->gitCreateFixtureCommit($i + 1, $path);
- }
- }
-
- /**
- * Create fixture commit with specified index.
- *
- * @param int $index
- * Index of the commit to be used in the message.
- * @param string|null $path
- * Optional path to the repository directory. If not provided, fixture
- * directory is used.
- *
- * @return string
- * Hash of created commit.
- */
- protected function gitCreateFixtureCommit(int $index, ?string $path = NULL): string {
- $path = $path ? $path : $this->src;
- $filename = 'f' . $index;
- $this->gitCreateFixtureFile($path, $filename);
- $repo = $this->git->open($path);
- $repo->addFile($filename);
- $message = 'Commit number ' . $index;
- $repo->commitAllChanges($message);
- $lastCommit = $repo->getLastCommit();
-
- return $lastCommit->getId()->toString();
- }
-
- /**
- * Commit all uncommitted files.
- *
- * @param string $path
- * Path to repository.
- * @param string $message
- * Commit message.
- */
- protected function gitCommitAll(string $path, string $message): void {
- $repo = $this->git->open($path);
- $repo->commitAllChanges($message);
- }
-
- /**
- * Checkout branch.
- *
- * @param string $path
- * Path to repository.
- * @param string $branch
- * Branch name.
- */
- protected function gitCheckout(string $path, string $branch): void {
- try {
- $repo = $this->git->open($path);
- $repo->checkout($branch);
- }
- catch (GitException $gitException) {
- $allowedFails = [
- sprintf("error: pathspec '%s' did not match any file(s) known to git", $branch),
- ];
-
- if ($gitException->getRunnerResult()) {
- $output = $gitException->getRunnerResult()->getErrorOutput();
- }
-
- // Re-throw exception if it is not one of the allowed ones.
- if (!isset($output) || empty(array_intersect($output, $allowedFails))) {
- throw $gitException;
- }
- }
- }
-
- /**
- * Reset git repo at path.
- *
- * @param string $path
- * Path to the repo.
- */
- protected function gitReset($path): void {
- $repo = $this->git->open($path);
- $repo->resetHard();
- $repo->cleanForce();
- }
-
- /**
- * Create fixture file at provided path.
- *
- * @param string $path
- * File path.
- * @param string $name
- * Optional file name.
- * @param string|array $content
- * Optional file content.
- *
- * @return string
- * Created file name.
- */
- protected function gitCreateFixtureFile(string $path, string $name = '', $content = ''): string {
- $name = $name !== '' && $name !== '0' ? $name : 'tmp' . rand(1000, 100000);
- $path = $path . DIRECTORY_SEPARATOR . $name;
- $dir = dirname($path);
- if (!empty($dir)) {
- $this->fs->mkdir($dir);
- }
- $this->fs->touch($path);
- if (!empty($content)) {
- $content = is_array($content) ? implode(PHP_EOL, $content) : $content;
- $this->fs->dumpFile($path, $content);
- }
-
- return $path;
- }
-
- /**
- * Remove fixture file at provided path.
- *
- * @param string $path
- * File path.
- * @param string $name
- * File name.
- */
- protected function gitRemoveFixtureFile(string $path, string $name): void {
- $path = $path . DIRECTORY_SEPARATOR . $name;
- $this->fs->remove($path);
- }
-
- /**
- * Create fixture tag with specified name and optional annotation.
- *
- * Annotated tags and lightweight tags have a different object
- * representation in git, therefore may need to be created explicitly for
- * some tests.
- *
- * @param string $path
- * Optional path to the repository directory.
- * @param string $name
- * Tag name.
- * @param bool $annotate
- * Optional flag to add random annotation to the tag. Defaults to FALSE.
- */
- protected function gitAddTag(string $path, string $name, bool $annotate = FALSE): void {
- $repo = $this->git->open($path);
- if ($annotate) {
- $message = 'Annotation for tag ' . $name;
- $repo->createAnnotatedTag($name, $message);
- }
- else {
- $repo->createLightweightTag($name);
- }
- }
-
- /**
- * Assert that files exist in repository in specified branch.
- *
- * @param string $path
- * Repository location.
- * @param array|string $files
- * File or array of files.
- * @param string|null $branch
- * Optional branch. If set, will be checked out before assertion.
- *
- * @todo Update arguments order and add assertion message.
- */
- protected function gitAssertFilesExist(string $path, $files, ?string $branch = NULL): void {
- $files = is_array($files) ? $files : [$files];
- if ($branch) {
- $this->gitCheckout($path, $branch);
- }
- foreach ($files as $file) {
- $this->assertFileExists($path . DIRECTORY_SEPARATOR . $file);
- }
- }
-
- /**
- * Assert that files do not exist in repository in specified branch.
- *
- * @param string $path
- * Repository location.
- * @param array|string $files
- * File or array of files.
- * @param string|null $branch
- * Optional branch. If set, will be checked out before assertion.
- */
- protected function gitAssertFilesNotExist(string $path, $files, ?string $branch = NULL): void {
- $files = is_array($files) ? $files : [$files];
- if ($branch) {
- $this->gitCheckout($path, $branch);
- }
- foreach ($files as $file) {
- $this->assertFileDoesNotExist($path . DIRECTORY_SEPARATOR . $file);
- }
- }
-
- /**
- * Assert git files are present and were committed.
- *
- * @param string $path
- * Path to repo.
- * @param array|string $expectedFiles
- * Array of files or a single file.
- * @param string $branch
- * Optional branch name.
- */
- protected function gitAssertFilesCommitted(string $path, $expectedFiles, string $branch = ''): void {
- if ($branch !== '' && $branch !== '0') {
- $this->gitCheckout($path, $branch);
- }
- $expectedFiles = is_array($expectedFiles) ? $expectedFiles : [$expectedFiles];
- $committedFiles = $this->gitGetCommittedFiles($path);
- $this->assertArraySimilar($expectedFiles, $committedFiles);
- }
-
- /**
- * Assert git files were not committed.
- *
- * @param string $path
- * Path to repo.
- * @param array|string $expectedFiles
- * Array of files or a single file.
- * @param string $branch
- * Optional branch name.
- */
- protected function gitAssertNoFilesCommitted(string $path, $expectedFiles, string $branch = ''): void {
- if ($branch !== '' && $branch !== '0') {
- $this->gitCheckout($path, $branch);
- }
- $expectedFiles = is_array($expectedFiles) ? $expectedFiles : [$expectedFiles];
- $committedFiles = $this->gitGetCommittedFiles($path);
- $intersectedFiles = array_intersect($committedFiles, $expectedFiles);
- $this->assertArraySimilar([], $intersectedFiles);
- }
-
- /**
- * Assert which git commits are present.
- *
- * @param int $count
- * Number of commits.
- * @param string $path
- * Path to the repo.
- * @param string $branch
- * Branch name.
- * @param array $additionalCommits
- * Array of additional commits.
- * @param bool $assertFiles
- * Assert files or not.
- *
- * @throws \Exception
- */
- protected function assertFixtureCommits(int $count, string $path, string $branch, array $additionalCommits = [], bool $assertFiles = TRUE): void {
- $this->gitCheckout($path, $branch);
- $this->gitReset($path);
-
- $expectedCommits = [];
- $expectedFiles = [];
- for ($i = 1; $i <= $count; $i++) {
- $expectedCommits[] = sprintf('Commit number %s', $i);
- $expectedFiles[] = sprintf('f%s', $i);
- }
- $expectedCommits = array_merge($expectedCommits, $additionalCommits);
-
- $commits = $this->gitGetAllCommits($path);
- $this->assertEquals($expectedCommits, $commits, 'All fixture commits are present');
-
- if ($assertFiles) {
- $this->gitAssertFilesExist($this->dst, $expectedFiles, $branch);
- }
- }
-
- /**
- * Run command.
- *
- * @param string $argsAndOptions
- * Args and options.
- * @param bool $expectFail
- * Flag to state that the command should fail.
- * @param string $gitArtifactBin
- * Git artifact bin.
- *
- * @return array
- * Array of output lines.
- */
- public function runGitArtifactCommand(string $argsAndOptions, bool $expectFail = FALSE, string $gitArtifactBin = './git-artifact'): array {
- if (!file_exists($gitArtifactBin)) {
- throw new \RuntimeException(sprintf('git-artifact binary is not available at path "%s"', $gitArtifactBin));
- }
-
- try {
- $output = $this->runCliCommand($gitArtifactBin . ' ' . $argsAndOptions);
- if ($expectFail) {
- throw new AssertionFailedError('Command exited successfully but should not');
- }
- }
- catch (ErrorException $errorException) {
- if (!$expectFail) {
- throw $errorException;
- }
- $output = explode(PHP_EOL, ($errorException->getPrevious() instanceof \Throwable) ? $errorException->getPrevious()->getMessage() : '');
- }
-
- return $output;
- }
-
- /**
- * Run CLI command.
- *
- * @param string $command
- * Command string to run.
- *
- * @return array
- * Array of output lines.
- *
- * @throws \DrevOps\GitArtifact\Tests\Exception\ErrorException
- * If commands exists with non-zero status.
- */
- protected function runCliCommand(string $command): array {
- exec($command . ' 2>&1', $output, $code);
-
- if ($code !== 0) {
- throw new ErrorException(sprintf('Command "%s" exited with non-zero status', $command), $code, '', -1, new ErrorException(implode(PHP_EOL, $output), $code, '', -1));
- }
-
- return $output;
- }
-
- /**
- * Asserts that two associative arrays are similar.
- *
- * Both arrays must have the same indexes with identical values
- * without respect to key ordering.
- *
- * @param array $expected
- * Expected assert.
- * @param array $array
- * The array want to assert.
- *
- * @phpstan-ignore-next-line
- */
- protected function assertArraySimilar(array $expected, array $array): void {
- $this->assertEquals([], array_diff($array, $expected));
- $this->assertEquals([], array_diff_key($array, $expected));
- foreach ($expected as $key => $value) {
- if (is_array($value)) {
- $this->assertArraySimilar($value, $array[$key]);
- }
- else {
- $this->assertContains($value, $array);
- }
- }
- }
-
-}
diff --git a/tests/phpunit/Traits/FixtureTrait.php b/tests/phpunit/Traits/FixtureTrait.php
new file mode 100644
index 0000000..201008c
--- /dev/null
+++ b/tests/phpunit/Traits/FixtureTrait.php
@@ -0,0 +1,61 @@
+ $content
+ * Optional file content.
+ *
+ * @return string
+ * Created file name.
+ */
+ protected function fixtureCreateFile(string $path, string $name = '', string|array $content = ''): string {
+ $fs = new Filesystem();
+
+ $name = $name !== '' && $name !== '0' ? $name : 'tmp' . rand(1000, 100000);
+ $path = $path . DIRECTORY_SEPARATOR . $name;
+
+ $dir = dirname($path);
+ if (!empty($dir)) {
+ $fs->mkdir($dir);
+ }
+
+ $fs->touch($path);
+ if (!empty($content)) {
+ $content = is_array($content) ? implode(PHP_EOL, $content) : $content;
+ $fs->dumpFile($path, $content);
+ }
+
+ return $path;
+ }
+
+ /**
+ * Remove fixture file at provided path.
+ *
+ * @param string $path
+ * File path.
+ * @param string $name
+ * File name.
+ */
+ protected function fixtureRemoveFile(string $path, string $name): void {
+ (new Filesystem())->remove($path . DIRECTORY_SEPARATOR . $name);
+ }
+
+}
diff --git a/tests/phpunit/Traits/GitTrait.php b/tests/phpunit/Traits/GitTrait.php
new file mode 100644
index 0000000..c583147
--- /dev/null
+++ b/tests/phpunit/Traits/GitTrait.php
@@ -0,0 +1,384 @@
+mkdir($path);
+
+ return (new Git())->init($path);
+ }
+
+ /**
+ * Checkout branch.
+ *
+ * @param string $path
+ * Path to repository.
+ * @param string $branch
+ * Branch name.
+ */
+ protected function gitCheckout(string $path, string $branch): void {
+ try {
+ (new Git())->open($path)->checkout($branch);
+ }
+ catch (GitException $exception) {
+ $allowed_fails = [
+ sprintf("error: pathspec '%s' did not match any file(s) known to git", $branch),
+ ];
+
+ if ($exception->getRunnerResult()) {
+ $output = $exception->getRunnerResult()->getErrorOutput();
+ }
+
+ // Re-throw exception if it is not one of the allowed ones.
+ if (!isset($output) || empty(array_intersect($output, $allowed_fails))) {
+ throw $exception;
+ }
+ }
+ }
+
+ /**
+ * Reset git repo at path.
+ *
+ * @param string $path
+ * Path to the repo.
+ */
+ protected function gitReset($path): void {
+ $repo = (new Git())->open($path);
+ $repo->run('reset', ['--hard']);
+ $repo->run('clean', ['-dfx']);
+ }
+
+ /**
+ * Get all commit hashes in the repository.
+ *
+ * @param string $path
+ * Path to the repository directory.
+ * @param string $format
+ * Format of commits.
+ *
+ * @return array
+ * Array of commit hashes, sorted from the earliest to the latest commit.
+ *
+ * @throws \Exception
+ */
+ protected function gitGetAllCommits(string $path, string $format = '%s'): array {
+ $commits = [];
+
+ try {
+ $commits = (new Git())->open($path)->run(['log', '--format=' . $format])->getOutput();
+ }
+ catch (\Exception $exception) {
+ // Different versions of Git may produce these expected messages.
+ $expected_error_messages = [
+ "fatal: bad default revision 'HEAD'",
+ "fatal: your current branch 'master' does not have any commits yet",
+ ];
+
+ if (!in_array(trim($exception->getMessage()), $expected_error_messages)) {
+ throw $exception;
+ }
+ }
+
+ return array_reverse(array_filter($commits));
+ }
+
+ /**
+ * Get a range of commits.
+ *
+ * @param array $range
+ * Array of commit indexes, stating from 1.
+ * @param string $path
+ * Path to the repository directory.
+ *
+ * @return array
+ * Array of commit hashes, ordered by keys in the $range.
+ *
+ * @throws \Exception
+ */
+ protected function gitGetCommitsRange(array $range, string $path): array {
+ $ret = [];
+
+ $commits = $this->gitGetAllCommits($path);
+
+ array_walk($range, static function (&$v): void {
+ --$v;
+ });
+
+ foreach ($range as $key) {
+ $ret[] = $commits[$key];
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Create fixture tag with specified name and optional annotation.
+ *
+ * Annotated tags and lightweight tags have a different object
+ * representation in git, therefore may need to be created explicitly for
+ * some tests.
+ *
+ * @param string $path
+ * Optional path to the repository directory.
+ * @param string $name
+ * Tag name.
+ * @param bool $annotate
+ * Optional flag to add random annotation to the tag. Defaults to FALSE.
+ */
+ protected function gitAddTag(string $path, string $name, bool $annotate = FALSE): void {
+ $repo = (new Git())->open($path);
+
+ if ($annotate) {
+ $repo->createTag($name, ['--message="Annotation for tag ' . $name . '"', '-a']);
+ }
+ else {
+ $repo->createTag($name);
+ }
+ }
+
+ /**
+ * Assert current Git branch.
+ *
+ * @param string $path
+ * Path to repository.
+ * @param string $branch
+ * Branch name to assert.
+ */
+ protected function gitAssertCurrentBranch(string $path, string $branch): void {
+ $current = (new Git())->open($path)->getCurrentBranchName();
+ $this->assertStringContainsString($branch, $current, sprintf('Current branch is "%s"', $branch));
+ }
+
+ /**
+ * Create multiple fixture commits.
+ *
+ * @param int $count
+ * Number of commits to create.
+ * @param int $offset
+ * Number of commit indices to offset.
+ * @param string|null $path
+ * Optional path to the repository directory. If not provided, fixture
+ * directory is used.
+ */
+ protected function gitCreateFixtureCommits(int $count, int $offset = 0, ?string $path = NULL): void {
+ $path = $path ? $path : $this->src;
+
+ for ($i = $offset; $i < $count + $offset; $i++) {
+ $this->gitCreateFixtureCommit($i + 1, $path);
+ }
+ }
+
+ /**
+ * Create fixture commit with specified index.
+ *
+ * @param int $index
+ * Index of the commit to be used in the message.
+ * @param string|null $path
+ * Optional path to the repository directory. If not provided, fixture
+ * directory is used.
+ *
+ * @return string
+ * Hash of created commit.
+ */
+ protected function gitCreateFixtureCommit(int $index, ?string $path = NULL): string {
+ $path = $path ? $path : $this->src;
+
+ $filename = 'f' . $index;
+
+ $fs = new Filesystem();
+ $filepath = $path . DIRECTORY_SEPARATOR . $filename;
+ $fs->mkdir(dirname($path));
+ $fs->touch($filepath);
+
+ return (new Git())->open($path)
+ ->addFile($filename)
+ ->commit('Commit number ' . $index)
+ ->getLastCommit()->getId()->toString();
+ }
+
+ /**
+ * Commit all uncommitted files.
+ *
+ * @param string $path
+ * Path to repository.
+ * @param string $message
+ * Commit message.
+ */
+ protected function gitCommitAll(string $path, string $message): void {
+ (new Git())->open($path)
+ ->addAllChanges()
+ ->commit($message);
+ }
+
+ /**
+ * Assert that Git remote specified by name does not exist.
+ *
+ * @param string $path
+ * Path to repository.
+ * @param string $remote
+ * Remote name to assert.
+ */
+ protected function gitAssertRemoteNotExists(string $path, string $remote): void {
+ $remotes = (new Git())->open($path)->run(['remote'])->getErrorOutputAsString() ?: '';
+ $this->assertStringNotContainsString($remote, $remotes, sprintf('Remote "%s" is not present"', $remote));
+ }
+
+ /**
+ * Assert which git commits are present.
+ *
+ * @param int $count
+ * Number of commits.
+ * @param string $path
+ * Path to the repo.
+ * @param string $branch
+ * Branch name.
+ * @param array $additional_commits
+ * Array of additional commits.
+ * @param bool $should_assert_files
+ * Should assert if files are present.
+ *
+ * @throws \Exception
+ */
+ protected function gitAssertFixtureCommits(int $count, string $path, string $branch, array $additional_commits = [], bool $should_assert_files = TRUE): void {
+ $this->gitCheckout($path, $branch);
+ $this->gitReset($path);
+
+ $expected_commits = [];
+ $expected_files = [];
+ for ($i = 1; $i <= $count; $i++) {
+ $expected_commits[] = sprintf('Commit number %s', $i);
+ $expected_files[] = sprintf('f%s', $i);
+ }
+ $expected_commits = array_merge($expected_commits, $additional_commits);
+
+ $commits = $this->gitGetAllCommits($path);
+ $this->assertEquals($expected_commits, $commits, 'All fixture commits are present');
+
+ if ($should_assert_files) {
+ $this->gitAssertFilesExist($this->dst, $expected_files, $branch);
+ }
+ }
+
+ /**
+ * Assert that files exist in repository in specified branch.
+ *
+ * @param string $path
+ * Repository location.
+ * @param array|string $files
+ * File or array of files.
+ * @param string|null $branch
+ * Optional branch. If set, will be checked out before assertion.
+ */
+ protected function gitAssertFilesExist(string $path, array|string $files, ?string $branch = NULL): void {
+ $files = is_array($files) ? $files : [$files];
+
+ if ($branch) {
+ $this->gitCheckout($path, $branch);
+ }
+
+ foreach ($files as $file) {
+ $this->assertFileExists($path . DIRECTORY_SEPARATOR . $file);
+ }
+ }
+
+ /**
+ * Assert that files do not exist in repository in specified branch.
+ *
+ * @param string $path
+ * Repository location.
+ * @param array|string $files
+ * File or array of files.
+ * @param string|null $branch
+ * Optional branch. If set, will be checked out before assertion.
+ */
+ protected function gitAssertFilesNotExist(string $path, array|string $files, ?string $branch = NULL): void {
+ $files = is_array($files) ? $files : [$files];
+
+ if ($branch) {
+ $this->gitCheckout($path, $branch);
+ }
+
+ foreach ($files as $file) {
+ $this->assertFileDoesNotExist($path . DIRECTORY_SEPARATOR . $file);
+ }
+ }
+
+ /**
+ * Assert git files are present and were committed.
+ *
+ * @param string $path
+ * Path to repo.
+ * @param array|string $expected_files
+ * Array of files or a single file.
+ * @param string $branch
+ * Optional branch name.
+ */
+ protected function gitAssertFilesCommitted(string $path, array|string $expected_files, ?string $branch = NULL): void {
+ if ($branch) {
+ $this->gitCheckout($path, $branch);
+ }
+
+ $expected_files = is_array($expected_files) ? $expected_files : [$expected_files];
+
+ $files = (new Git())->open($path)->run(['ls-tree', '--name-only', '-r', 'HEAD'])->getOutput();
+ $files = array_filter($files);
+
+ $this->assertArraySimilar($expected_files, $files);
+ }
+
+ /**
+ * Assert git files were not committed.
+ *
+ * @param string $path
+ * Path to repo.
+ * @param array|string $expected_files
+ * Array of files or a single file.
+ * @param string $branch
+ * Optional branch name.
+ */
+ protected function gitAssertFilesNotCommitted(string $path, array|string $expected_files, ?string $branch = NULL): void {
+ if ($branch) {
+ $this->gitCheckout($path, $branch);
+ }
+
+ $expected_files = is_array($expected_files) ? $expected_files : [$expected_files];
+
+ $files = (new Git())->open($path)->run(['ls-tree', '--name-only', '-r', 'HEAD'])->getOutput();
+ $files = array_filter($files);
+
+ $intersected_files = array_intersect($files, $expected_files);
+
+ $this->assertArraySimilar([], $intersected_files);
+ }
+
+ /**
+ * Get global default branch.
+ *
+ * @return string
+ * Default branch name.
+ */
+ protected function gitGetGlobalDefaultBranch(): string {
+ return trim(shell_exec('git config --global init.defaultBranch') ?: 'master');
+ }
+
+}
diff --git a/tests/phpunit/Traits/MockTrait.php b/tests/phpunit/Traits/MockTrait.php
index 0dddfe4..67df8d0 100644
--- a/tests/phpunit/Traits/MockTrait.php
+++ b/tests/phpunit/Traits/MockTrait.php
@@ -4,6 +4,8 @@
namespace DrevOps\GitArtifact\Tests\Traits;
+use PHPUnit\Framework\MockObject\MockObject;
+
/**
* Trait MockTrait.
*
@@ -16,61 +18,50 @@ trait MockTrait {
*
* @param class-string $class
* Class or trait name to generate the mock.
- * @param array $methodsMap
+ * @param array $methods
* Optional array of methods and values, keyed by method name. Array
* elements can be return values, callbacks created with
- * $this->returnCallback(), or closures.
- * @param array $args
- * Optional array of constructor arguments. If omitted, a constructor
- * will not be called.
+ * $this->willReturnCallback(), or closures.
+ * @param bool|array $args
+ * Optional array of constructor arguments or FALSE to disable the original
+ * constructor. If omitted, an original constructor will be called.
*
- * @return object
+ * @return \PHPUnit\Framework\MockObject\MockObject
* Mocked class.
*
* @throws \ReflectionException
+ *
+ * @SuppressWarnings(CyclomaticComplexity)
*/
- protected function prepareMock(string $class, array $methodsMap = [], array $args = []) {
- $methods = array_keys($methodsMap);
+ protected function prepareMock(string $class, array $methods = [], array|bool $args = []): MockObject {
+ $methods = array_filter($methods, fn($value, $key): bool => is_string($key), ARRAY_FILTER_USE_BOTH);
- $reflectionClass = new \ReflectionClass($class);
+ if (!class_exists($class)) {
+ throw new \InvalidArgumentException(sprintf('Class %s does not exist', $class));
+ }
- if ($reflectionClass->isAbstract()) {
- $mock = $this->getMockForAbstractClass($class, $args, '', !empty($args), TRUE, TRUE, $methods);
+ $builder = $this->getMockBuilder($class);
+
+ if (is_array($args) && !empty($args)) {
+ $builder->enableOriginalConstructor()->setConstructorArgs($args);
}
- elseif ($reflectionClass->isTrait()) {
- $mock = $this->getMockForTrait($class, [], '', TRUE, TRUE, TRUE, array_keys($methodsMap));
- }
- else {
- $mockBuilder = $this->getMockBuilder($class);
- if (!empty($args)) {
- $mockBuilder = $mockBuilder->enableOriginalConstructor()
- ->setConstructorArgs($args);
- }
- else {
- $mockBuilder = $mockBuilder->disableOriginalConstructor();
- }
- /* @todo setMethods method is not found on MockBuilder */
- /* @phpstan-ignore-next-line */
- $mock = $mockBuilder->setMethods($methods)
- ->getMock();
+ elseif ($args === FALSE) {
+ $builder->disableOriginalConstructor();
}
- foreach ($methodsMap as $method => $value) {
- // Handle callback values differently.
+ $method_names = array_filter(array_keys($methods), fn($method): bool => is_string($method) && !empty($method));
+ $mock = $builder->onlyMethods($method_names)->getMock();
+
+ foreach ($methods as $method => $value) {
+ // Handle callback value differently based on its type.
if (is_object($value) && str_contains($value::class, 'Callback')) {
- $mock->expects($this->any())
- ->method($method)
- ->will($value);
+ $mock->expects($this->any())->method($method)->willReturnCallback($value);
}
elseif (is_object($value) && str_contains($value::class, 'Closure')) {
- $mock->expects($this->any())
- ->method($method)
- ->will($this->returnCallback($value));
+ $mock->expects($this->any())->method($method)->willReturnCallback($value);
}
else {
- $mock->expects($this->any())
- ->method($method)
- ->willReturn($value);
+ $mock->expects($this->any())->method($method)->willReturn($value);
}
}
diff --git a/tests/phpunit/Unit/AbstractUnitTestCase.php b/tests/phpunit/Unit/AbstractUnitTestCase.php
index fdbd87a..05c4e70 100644
--- a/tests/phpunit/Unit/AbstractUnitTestCase.php
+++ b/tests/phpunit/Unit/AbstractUnitTestCase.php
@@ -4,24 +4,38 @@
namespace DrevOps\GitArtifact\Tests\Unit;
-use DrevOps\GitArtifact\Commands\ArtifactCommand;
-use DrevOps\GitArtifact\Tests\AbstractTestCase;
+use DrevOps\GitArtifact\Tests\Traits\MockTrait;
+use DrevOps\GitArtifact\Tests\Traits\ReflectionTrait;
+use PHPUnit\Framework\TestCase;
-/**
- * Class AbstractUnitTestCase.
- */
-abstract class AbstractUnitTestCase extends AbstractTestCase {
+abstract class AbstractUnitTestCase extends TestCase {
+
+ use MockTrait;
+ use ReflectionTrait;
/**
- * Artifact command.
+ * Asserts that two associative arrays are similar.
+ *
+ * Both arrays must have the same indexes with identical values
+ * without respect to key ordering.
+ *
+ * @param array $expected
+ * Expected assert.
+ * @param array $array
+ * The array want to assert.
*/
- protected ArtifactCommand $command;
-
- protected function setUp(): void {
- parent::setUp();
-
- $this->command = new ArtifactCommand();
- $this->callProtectedMethod($this->command, 'fsSetRootDir', [$this->fixtureDir]);
+ protected function assertArraySimilar(array $expected, array $array): void {
+ $this->assertEquals([], array_diff($array, $expected));
+ $this->assertEquals([], array_diff_key($array, $expected));
+
+ foreach ($expected as $key => $value) {
+ if (is_array($value)) {
+ $this->assertArraySimilar($value, $array[$key]);
+ }
+ else {
+ $this->assertContains($value, $array);
+ }
+ }
}
}
diff --git a/tests/phpunit/Unit/ArtifactGitRepositoryTest.php b/tests/phpunit/Unit/ArtifactGitRepositoryTest.php
index 376cf56..6e5073b 100644
--- a/tests/phpunit/Unit/ArtifactGitRepositoryTest.php
+++ b/tests/phpunit/Unit/ArtifactGitRepositoryTest.php
@@ -4,298 +4,121 @@
namespace DrevOps\GitArtifact\Tests\Unit;
-use CzProject\GitPhp\GitException;
use DrevOps\GitArtifact\Git\ArtifactGitRepository;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
+use Symfony\Component\Filesystem\Filesystem;
-/**
- * Test ArtifactGitRepository class.
- *
- * @SuppressWarnings(PHPMD.TooManyPublicMethods)
- */
#[CoversClass(ArtifactGitRepository::class)]
class ArtifactGitRepositoryTest extends AbstractUnitTestCase {
- /**
- * Test push force.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function testPushForce(): void {
- $sourceRepo = $this->git->open($this->src);
- $sourceRepo->commit('Source commit 1', ['--allow-empty']);
-
- $destinationRepo = $this->git->open($this->dst);
- $destinationRepo->commit('Destination commit 1', ['--allow-empty']);
- $lastCommit = $destinationRepo->getLastCommit();
- $this->assertEquals('Destination commit 1', $lastCommit->getSubject());
-
- $sourceRepo->addRemote('dst', $this->dst);
- $sourceRepo->pushForce('dst', 'refs/heads/master:refs/heads/master');
- $lastCommit = $destinationRepo->getLastCommit();
- $this->assertEquals('Source commit 1', $lastCommit->getSubject());
- }
-
- /**
- * Test list files.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function testListFiles(): void {
- $sourceRepo = $this->git->open($this->src);
- // Test list ignored files.
- $gitIgnoreFile = $this->src . DIRECTORY_SEPARATOR . '.gitignore';
- file_put_contents($gitIgnoreFile, '');
- $this->assertFileExists($gitIgnoreFile);
- $files = $sourceRepo->listIgnoredFilesFromGitIgnoreFile($gitIgnoreFile);
- $this->assertEquals([], $files);
-
- $this->gitCreateFixtureFile($this->src, 'test-ignore-1');
- $this->gitCreateFixtureFile($this->src, 'test-ignore-2');
- $sourceRepo->commitAllChanges('Test list ignored files.');
-
- file_put_contents($gitIgnoreFile, "test-ignore-1\ntest-ignore-2");
- $files = $sourceRepo->listIgnoredFilesFromGitIgnoreFile($gitIgnoreFile);
- $this->assertEquals(['test-ignore-1', 'test-ignore-2'], $files);
-
- // Test list other files.
- $otherFiles = $sourceRepo->listOtherFiles();
- $this->assertEquals([], $otherFiles);
- $this->gitCreateFixtureFile($this->src, 'other-file-1');
- $this->gitCreateFixtureFile($this->src, 'other-file-2');
- $otherFiles = $sourceRepo->listOtherFiles();
- $this->assertEquals(['other-file-1', 'other-file-2'], $otherFiles);
- }
-
- /**
- * Test get commits.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function testGetCommits(): void {
- $sourceRepo = $this->git->open($this->src);
-
- $this->gitCreateFixtureFile($this->src, 'test-commit-file1');
- $sourceRepo->commitAllChanges('Add file 1');
- $commits = $sourceRepo->getCommits();
-
- $this->assertEquals(['Add file 1'], $commits);
-
- $this->gitCreateFixtureFile($this->src, 'test-commit-file2');
- $sourceRepo->commitAllChanges('Add file 2');
- $commits = $sourceRepo->getCommits();
-
- $this->assertEquals(['Add file 2', 'Add file 1'], $commits);
- }
-
- /**
- * Test reset hard command.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function testResetHard(): void {
- $sourceRepo = $this->git->open($this->src);
- $file = $this->gitCreateFixtureFile($this->src, 'test-file1');
- file_put_contents($file, 'Content example');
- $sourceRepo->commitAllChanges('Add file 1');
- $this->assertEquals('Content example', file_get_contents($file));
-
- file_put_contents($file, 'New content');
- $this->assertEquals('New content', file_get_contents($file));
-
- $sourceRepo->resetHard();
- $this->assertEquals('Content example', file_get_contents($file));
- }
-
- /**
- * Test clean force command.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function testCleanForce(): void {
- $sourceRepo = $this->git->open($this->src);
- $this->gitCreateFixtureFile($this->src, 'test-file1');
- $sourceRepo->commitAllChanges('Add file 1');
- $file = $this->gitCreateFixtureFile($this->src, 'test-file2');
- $this->assertFileExists($file);
-
- $sourceRepo->cleanForce();
- $this->assertFileDoesNotExist($file);
- }
-
- /**
- * Test branch command.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function testBranch(): void {
- $sourceRepo = $this->git->open($this->src);
- $this->gitCreateFixtureFile($this->src, 'test-file1');
- $sourceRepo->commitAllChanges('Add file 1');
- // Test switch.
- $sourceRepo->switchToBranch('branch1', TRUE);
- $this->assertEquals('branch1', $sourceRepo->getCurrentBranchName());
- $sourceRepo->switchToBranch('branch2', TRUE);
- $this->assertEquals('branch2', $sourceRepo->getCurrentBranchName());
- $sourceRepo->switchToBranch('branch1');
- $this->assertEquals('branch1', $sourceRepo->getCurrentBranchName());
- // Test remove branch.
- $this->assertEquals(['branch1', 'branch2', 'master'], $sourceRepo->getBranches());
- $sourceRepo->removeBranch('master');
- $this->assertEquals(['branch1', 'branch2'], $sourceRepo->getBranches());
- $sourceRepo->removeBranch('branch2', TRUE);
- $this->assertEquals(['branch1'], $sourceRepo->getBranches());
-
- $sourceRepo->removeBranch('', TRUE);
- $this->assertEquals(['branch1'], $sourceRepo->getBranches());
- }
-
- /**
- * Test commit all changes.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function testCommitAllChanges(): void {
- $sourceRepo = $this->git->open($this->src);
- $file = $this->gitCreateFixtureFile($this->src, 'test-file1');
- $sourceRepo->addFile($file);
- $sourceRepo->commit('Add file 1');
- $this->assertEquals(['Add file 1'], $sourceRepo->getCommits());
-
- $this->gitCreateFixtureFile($this->src, 'test-file2');
- $sourceRepo->commitAllChanges('Commit all changes.');
- $this->assertEquals(['Commit all changes.', 'Add file 1'], $sourceRepo->getCommits());
- }
-
- /**
- * Test list commited files.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function testListCommittedFiles(): void {
- $sourceRepo = $this->git->open($this->src);
- $sourceRepo->commit('Commit 1', ['--allow-empty']);
- $this->assertEquals([], $sourceRepo->listCommittedFiles());
-
- $file = $this->gitCreateFixtureFile($this->src, 'file-1');
- $this->assertEquals([], $sourceRepo->listCommittedFiles());
-
- $sourceRepo->addFile($file);
- $sourceRepo->commit('Add file 1');
- $this->assertEquals(['file-1'], $sourceRepo->listCommittedFiles());
- }
-
- /**
- * Test set config.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function testSetConfigReceiveDenyCurrentBranchIgnore(): void {
- $sourceRepo = $this->git->open($this->src);
- try {
- $receiveDenyCurrentBranch = $sourceRepo->execute('config', 'receive.denyCurrentBranch');
- }
- catch (GitException) {
- $receiveDenyCurrentBranch = '';
+ #[DataProvider('dataProviderIsValidRemote')]
+ public function testIsValidRemote(string $url, string $type, bool $expect_exception, bool $expected): void {
+ if ($expect_exception) {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage(sprintf('Invalid argument "%s" provided', $type));
}
- $this->assertEquals('', $receiveDenyCurrentBranch);
- $sourceRepo->setConfigReceiveDenyCurrentBranchIgnore();
- $receiveDenyCurrentBranch = $sourceRepo->execute('config', 'receive.denyCurrentBranch');
- $this->assertEquals(['ignore'], $receiveDenyCurrentBranch);
- }
-
- /**
- * Test create tag commands.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function testCreateTag(): void {
- $sourceRepo = $this->git->open($this->src);
- $sourceRepo->commit('Commit 1', ['--allow-empty']);
- $this->assertEquals(NULL, $sourceRepo->getTags());
-
- $sourceRepo->createAnnotatedTag('tag1', 'Hello tag 1');
- $this->assertEquals(['tag1'], $sourceRepo->getTags());
-
- $sourceRepo->createLightweightTag('tag2');
- $this->assertEquals(['tag1', 'tag2'], $sourceRepo->getTags());
- }
-
- /**
- * Test remote commands.
- *
- * @throws \CzProject\GitPhp\GitException
- */
- public function testRemote(): void {
- $sourceRepo = $this->git->open($this->src);
- $this->assertEquals([], $sourceRepo->getRemotes());
- $sourceRepo->addRemote('dst', $this->dst);
- $this->assertEquals(['dst'], $sourceRepo->getRemotes());
- $this->assertTrue($sourceRepo->isRemoteExists('dst'));
- $sourceRepo->removeRemote('dst');
- $this->assertEquals([], $sourceRepo->getRemotes());
- $this->assertFalse($sourceRepo->isRemoteExists('dst'));
+ $url = $url === '' ? (new Filesystem())->tempnam(sys_get_temp_dir(), 'test') : $url;
- $sourceRepo->removeRemote('dummy');
- $this->assertEquals([], $sourceRepo->getRemotes());
- }
-
- /**
- * Test is valid remote url.
- *
- * @throws \Exception
- */
- #[DataProvider('dataProviderIsValidRemoteUrl')]
- public function testIsValidRemoteUrl(?bool $expected, string $pathOrUri, string $type, bool $pass): void {
- if (!$pass) {
- $this->expectException(\InvalidArgumentException::class);
- ArtifactGitRepository::isValidRemoteUrl($pathOrUri, $type);
- }
- else {
- $this->assertEquals($expected, ArtifactGitRepository::isValidRemoteUrl($pathOrUri, $type));
- }
+ $actual = ArtifactGitRepository::isValidRemote($url, $type);
+ $this->assertEquals($expected, $actual);
}
- /**
- * Data provider.
- *
- * @return array
- * Data provider.
- */
- public static function dataProviderIsValidRemoteUrl(): array {
+ public static function dataProviderIsValidRemote(): array {
return [
- [TRUE, 'git@github.com:foo/git-foo.git', 'uri', TRUE],
- [FALSE, 'git@github.com:foo/git-foo.git', 'local', TRUE],
- [TRUE, 'git@github.com:foo/git-foo.git', 'any', TRUE],
- [FALSE, '/no-existing/path', 'any', TRUE],
- [FALSE, '/no-existing/path', 'local', TRUE],
- [NULL, '/no-existing/path', 'custom', FALSE],
+ ['', 'any', FALSE, TRUE],
+ ['', 'local', FALSE, TRUE],
+ ['', 'external', FALSE, FALSE],
+ ['', 'custom_type', TRUE, FALSE],
+ // Negative tests.
+ ['/path/non-existing', 'any', FALSE, FALSE],
+ ['/path/non-existing', 'local', FALSE, FALSE],
+ ['/path/non-existing', 'external', FALSE, FALSE],
+ ['/path/non-existing', 'custom_type', TRUE, FALSE],
+
+ ['git@github.com:user/repo.git', 'any', FALSE, TRUE],
+ ['git@github.com:user/repo.git', 'external', FALSE, TRUE],
+ ['git@github.com:user/repo.git', 'local', FALSE, FALSE],
+ ['git@github.com:user/repo.git', 'custom_type', TRUE, FALSE],
+ // Negative tests.
+ ['git@github.com:user/repo', 'any', FALSE, FALSE],
+ ['git@github.com:user/repo', 'external', FALSE, FALSE],
+ ['git@github.com:user/repo', 'local', FALSE, FALSE],
+ ['git@github.com:user/repo', 'custom_type', TRUE, FALSE],
+
+ ['https://github.com/user/repo.git', 'any', FALSE, TRUE],
+ ['https://github.com/user/repo.git', 'external', FALSE, TRUE],
+ ['https://github.com/user/repo.git', 'local', FALSE, FALSE],
+ ['https://github.com/user/repo.git', 'custom_type', TRUE, FALSE],
+ // Negative tests.
+ ['https://github.com/user/repo', 'any', FALSE, FALSE],
+ ['https://github.com/user/repo', 'external', FALSE, FALSE],
+ ['https://github.com/user/repo', 'local', FALSE, FALSE],
+ ['https://github.com/user/repo', 'custom_type', TRUE, FALSE],
+
+ ['http://github.com/user/repo.git', 'any', FALSE, TRUE],
+ ['http://github.com/user/repo.git', 'external', FALSE, TRUE],
+ ['http://github.com/user/repo.git', 'local', FALSE, FALSE],
+ ['http://github.com/user/repo.git', 'custom_type', TRUE, FALSE],
+ // Negative tests.
+ ['http://github.com/user/repo', 'any', FALSE, FALSE],
+ ['http://github.com/user/repo', 'external', FALSE, FALSE],
+ ['http://github.com/user/repo', 'local', FALSE, FALSE],
+ ['http://github.com/user/repo', 'custom_type', TRUE, FALSE],
+
+ ['git://user/repo.git', 'any', FALSE, TRUE],
+ ['git://user/repo.git', 'external', FALSE, TRUE],
+ ['git://user/repo.git', 'local', FALSE, FALSE],
+ ['git://user/repo.git', 'custom_type', TRUE, FALSE],
+ // Negative tests.
+ ['git://user/repo', 'any', FALSE, FALSE],
+ ['git://user/repo', 'external', FALSE, FALSE],
+ ['git://user/repo', 'local', FALSE, FALSE],
+ ['git://user/repo', 'custom_type', TRUE, FALSE],
+
+ ['ssh://git@github.com/user/repo.git', 'any', FALSE, TRUE],
+ ['ssh://git@github.com/user/repo.git', 'external', FALSE, TRUE],
+ ['ssh://git@github.com/user/repo.git', 'local', FALSE, FALSE],
+ ['ssh://git@github.com/user/repo.git', 'custom_type', TRUE, FALSE],
+ // Negative tests.
+ ['ssh://git@github.com/user/repo', 'any', FALSE, FALSE],
+ ['ssh://git@github.com/user/repo', 'external', FALSE, FALSE],
+ ['ssh://git@github.com/user/repo', 'local', FALSE, FALSE],
+ ['ssh://git@github.com/user/repo', 'custom_type', TRUE, FALSE],
];
}
- /**
- * Test is valid remote url.
- *
- * @throws \Exception
- */
#[DataProvider('dataProviderIsValidBranchName')]
- public function testIsValidBranchName(bool $expected, string $branchName): void {
- $this->assertEquals($expected, ArtifactGitRepository::isValidBranchName($branchName));
+ public function testIsValidBranchName(string $name, bool $expected): void {
+ $this->assertEquals($expected, ArtifactGitRepository::isValidBranchName($name));
}
- /**
- * Data provider.
- *
- * @return array
- * Data provider.
- */
public static function dataProviderIsValidBranchName(): array {
return [
- [TRUE, 'branch'],
- [FALSE, '*/branch'],
- [FALSE, '*.branch'],
+ ['', FALSE],
+ [' ', FALSE],
+ ["\n", FALSE],
+ ['branch', TRUE],
+ ['branch/sub', TRUE],
+ ['branch/sub/subsub', TRUE],
+ ['branch/*', FALSE],
+ ['branch/sub/*', FALSE],
+ ['branch/sub/subsub/*', FALSE],
+ ['*/branch', FALSE],
+ ['*.branch', FALSE],
+ [':branch', FALSE],
+ ['~branch', FALSE],
+ ['?branch', FALSE],
+ ['branch?', FALSE],
+ ['branch/?', FALSE],
+ ['branch//', FALSE],
+ ['/branch', FALSE],
+ ['//branch', FALSE],
+ // Long branch names.
+ [str_repeat('a', 254), TRUE],
+ [str_repeat('a', 255), FALSE],
+ ['branch' . str_repeat('/sub', 255), FALSE],
];
}
diff --git a/tests/phpunit/Unit/ArtifactGitTest.php b/tests/phpunit/Unit/ArtifactGitTest.php
deleted file mode 100644
index 3a39a7a..0000000
--- a/tests/phpunit/Unit/ArtifactGitTest.php
+++ /dev/null
@@ -1,27 +0,0 @@
-git->open($this->src);
- $this->assertEquals(ArtifactGitRepository::class, $repo::class);
- }
-
-}
diff --git a/tests/phpunit/Unit/ExcludeTest.php b/tests/phpunit/Unit/ExcludeTest.php
deleted file mode 100644
index dc6f174..0000000
--- a/tests/phpunit/Unit/ExcludeTest.php
+++ /dev/null
@@ -1,182 +0,0 @@
-createFixtureExcludeFile();
-
- $actual = $this->callProtectedMethod($this->command, 'localExcludeExists', [$this->fixtureDir]);
-
- $this->assertTrue($actual);
- }
-
- /**
- * @param array $lines
- * Lines.
- * @param bool $strict
- * Strict.
- * @param bool $expected
- * Expected.
- *
- *
- * @dataProvider dataProviderExcludeEmpty
- *
- * @throws \ReflectionException
- */
- public function testExcludeEmpty(array $lines, bool $strict, bool $expected): void {
- $this->createFixtureExcludeFile(implode(PHP_EOL, $lines));
-
- $actual = $this->callProtectedMethod($this->command, 'localExcludeEmpty', [$this->fixtureDir, $strict]);
-
- $this->assertEquals($expected, $actual);
- }
-
- /**
- * @return array
- * Data provider.
- *
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- public static function dataProviderExcludeEmpty(): array {
- return [
- // Empty file.
- [
- [], TRUE, TRUE,
- ],
- [
- [], FALSE, TRUE,
- ],
-
- // Spaces single line.
- [
- [
- ' ',
- ], TRUE, FALSE,
- ],
-
- [
- [
- ' ',
- ], FALSE, TRUE,
- ],
-
- // Spaces.
- [
- [
- ' ',
- ' ',
- ], TRUE, FALSE,
- ],
-
- [
- [
- ' ',
- ' ',
- ], FALSE, TRUE,
- ],
-
- // Spaces, comments.
- [
- [
- ' ',
- '#comment ',
- ' ',
- ], TRUE, FALSE,
- ],
-
- [
- [
- ' ',
- '#comment ',
- ' ',
- ], FALSE, TRUE,
- ],
-
- // Spaces, padded comments.
- [
- [
- ' ',
- ' #comment ',
- ' ',
- ], TRUE, FALSE,
- ],
-
- [
- [
- ' ',
- ' #comment ',
- ' ',
- ], FALSE, TRUE,
- ],
-
- // Spaces, comments and valid content.
- [
- [
- ' ',
- '#comment ',
- 'valid',
- ' ',
- ], TRUE, FALSE,
- ],
-
- [
- [
- ' ',
- '#comment ',
- 'valid',
- ' ',
- ], FALSE, FALSE,
- ],
-
- // Spaces, inline comments and valid content.
- [
- [
- ' ',
- '#comment ',
- 'valid',
- 'valid # other comment',
- ' ',
- ], TRUE, FALSE,
- ],
-
- [
- [
- ' ',
- '#comment ',
- 'valid',
- 'valid # other comment',
- ' ',
- ], FALSE, FALSE,
- ],
-
- ];
- }
-
- /**
- * Helper to create an exclude file.
- *
- * @param string $contents
- * Optional file contents.
- *
- * @return string
- * Created file name.
- */
- protected function createFixtureExcludeFile(string $contents = ''): string {
- return $this->gitCreateFixtureFile($this->fixtureDir . DIRECTORY_SEPARATOR . '.git' . DIRECTORY_SEPARATOR . 'info', 'exclude', $contents);
- }
-
-}
diff --git a/tests/phpunit/Unit/TokenTest.php b/tests/phpunit/Unit/TokenTest.php
index 21be4e9..ff391d7 100644
--- a/tests/phpunit/Unit/TokenTest.php
+++ b/tests/phpunit/Unit/TokenTest.php
@@ -5,30 +5,86 @@
namespace DrevOps\GitArtifact\Tests\Unit;
use DrevOps\GitArtifact\Traits\TokenTrait;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
-/**
- * Class ForcePushTest.
- *
- * @group integration
- *
- * @covers \DrevOps\GitArtifact\Traits\TokenTrait
- */
+#[CoversClass(TokenTrait::class)]
class TokenTest extends AbstractUnitTestCase {
- /**
- * @dataProvider dataProviderTokenExists
- */
+ #[DataProvider('dataProviderTokenProcess')]
+ public function testTokenProcess(string $string, string $expected): void {
+ $class = new class() {
+
+ use TokenTrait;
+
+ public function getTokenSomeToken(?string $prop = NULL): string {
+ return empty($prop) ? 'somevalue' : 'somevalue with property ' . $prop;
+ }
+
+ };
+
+ $actual = $this->callProtectedMethod($class, 'tokenProcess', [$string]);
+ $this->assertEquals($expected, $actual);
+ }
+
+ public static function dataProviderTokenProcess(): array {
+ return [
+ [
+ '',
+ '',
+ ],
+ [
+ '',
+ '',
+ ],
+ [
+ 'string without a token',
+ 'string without a token',
+ ],
+ [
+ 'string with sometoken without delimiters',
+ 'string with sometoken without delimiters',
+ ],
+ [
+ 'string with [sometoken broken delimiters',
+ 'string with [sometoken broken delimiters',
+ ],
+ [
+ 'string with sometoken] broken delimiters',
+ 'string with sometoken] broken delimiters',
+ ],
+ // Proper token.
+ [
+ '[sometoken]',
+ 'somevalue',
+ ],
+ [
+ 'string with [sometoken] present',
+ 'string with somevalue present',
+ ],
+ // Token with properties.
+ [
+ 'string with [sometoken:prop] present',
+ 'string with somevalue with property prop present',
+ ],
+ [
+ 'string with [sometoken:prop:otherprop] present',
+ 'string with somevalue with property prop:otherprop present',
+ ],
+ ];
+ }
+
+ #[DataProvider('dataProviderTokenExists')]
public function testTokenExists(string $string, bool $expected): void {
- $mock = $this->prepareMock(TokenTrait::class);
+ $class = new class() {
+
+ use TokenTrait;
+ };
- $actual = $this->callProtectedMethod($mock, 'tokenExists', [$string]);
+ $actual = $this->callProtectedMethod($class, 'tokenExists', [$string]);
$this->assertEquals($expected, $actual);
}
- /**
- * @return array
- * Data provider.
- */
public static function dataProviderTokenExists(): array {
return [
['notoken', FALSE],