diff --git a/composer.json b/composer.json index 22e64c7..20e1093 100644 --- a/composer.json +++ b/composer.json @@ -23,14 +23,14 @@ "require": { "php": "^8.0.2", "albertofem/rsync-lib": "^1.0.0", - "symplify/git-wrapper": "^10.0", "craftcms/cms": "^4.0.0", "craftcms/plugin-installer": "^1.5.6", + "fortrabbit/craft-auto-migrate": "^2.5.0", + "gitonomy/gitlib": "^1.3", "ostark/yii2-artisan-bridge": "^1.3.1", "symfony/process": "^5.0 | ^6.0", - "vlucas/phpdotenv": "^3.4.0 | ^5.4", "symfony/yaml": "^4.2 | ^5.0", - "fortrabbit/craft-auto-migrate":"^2.5.0" + "vlucas/phpdotenv": "^3.4.0 | ^5.4" }, "require-dev": { "craftcms/phpstan": "dev-main", diff --git a/src/Actions/CodeDownAction.php b/src/Actions/CodeDownAction.php index 17d0fdc..ece68f5 100644 --- a/src/Actions/CodeDownAction.php +++ b/src/Actions/CodeDownAction.php @@ -4,7 +4,7 @@ namespace fortrabbit\Copy\Actions; -use Symplify\GitWrapper\Exception\GitException; +use fortrabbit\Copy\Exceptions\GitException; use yii\console\ExitCode; class CodeDownAction extends StageAwareBaseAction @@ -22,7 +22,7 @@ public function run(?string $stage = null): int ); $git = $this->plugin->git; - $git->getWorkingCopy()->init(); + $git->getClient()->init(); $localBranches = $git->getLocalBranches(); $branch = $git->getLocalHead(); @@ -30,7 +30,7 @@ public function run(?string $stage = null): int if (count($localBranches) > 1) { $question = 'Select a local branch (checkout):'; $branch = str_replace('* ', '', $this->choice($question, $localBranches, $branch)); - $git->run('checkout', [$branch]); + $git->getClient()->checkout($branch); } // Run 'before' commands and stop on error @@ -44,7 +44,6 @@ public function run(?string $stage = null): int try { $this->section("git pull ({$upstream}/{$branch})"); - $git->getWorkingCopy()->getWrapper()->streamOutput(); $git->pull($upstream, $branch); } catch (GitException $gitException) { $lines = count(explode(PHP_EOL, $gitException->getMessage())); diff --git a/src/Actions/CodeUpAction.php b/src/Actions/CodeUpAction.php index 1693500..93a17a0 100644 --- a/src/Actions/CodeUpAction.php +++ b/src/Actions/CodeUpAction.php @@ -9,9 +9,9 @@ use craft\helpers\App; use craft\helpers\FileHelper; use Exception; +use fortrabbit\Copy\Exceptions\GitException; use fortrabbit\Copy\Services\Git; use fortrabbit\Copy\Services\LocalFilesystem; -use Symplify\GitWrapper\Exception\GitException; use ostark\Yii2ArtisanBridge\base\Commands; use Throwable; use yii\console\ExitCode; @@ -53,7 +53,7 @@ public function run(?string $stage = null): int } $git = $this->plugin->git; - $git->getWorkingCopy()->init(); + $git->getClient()->init(); // Project .gitignore $git->assureDotGitignore(); @@ -64,7 +64,7 @@ public function run(?string $stage = null): int if (count($localBranches) > 1) { $question = 'Select a local branch (checkout):'; $branch = str_replace('* ', '', $this->choice($question, $localBranches, $branch)); - $git->run('checkout', [$branch]); + $git->getClient()->checkout($branch); } // Ask for remote @@ -78,7 +78,7 @@ public function run(?string $stage = null): int $this->assureVolumesAreIgnored(); try { - if ($log = $git->getWorkingCopy()->log( + if ($log = $git->getClient()->log( '--format=(%h) %cr: %s ', "{$upstream}/master..HEAD" )) { @@ -87,11 +87,11 @@ public function run(?string $stage = null): int } catch (Throwable) { } - if (! $git->getWorkingCopy()->hasChanges() && ! $this->confirm('About to push latest commits, proceed?', true)) { + if (! $git->getClient()->hasChanges() && ! $this->confirm('About to push latest commits, proceed?', true)) { return ExitCode::OK; } - if ($status = $git->getWorkingCopy()->getStatus()) { + if ($status = $git->getClient()->getStatus()) { // Changed files $this->noteBlock('Uncommitted changes:' . PHP_EOL . $status); @@ -112,8 +112,8 @@ public function run(?string $stage = null): int } // Add and commit - $git->getWorkingCopy()->add('.'); - $git->getWorkingCopy()->commit($msg); + $git->getClient()->add('.'); + $git->getClient()->commit($msg); // Ask for the branch name again if this is the first commit in the repo if ($branch === NULL) { @@ -130,7 +130,6 @@ public function run(?string $stage = null): int try { $this->section("git push ({$msg})"); - $git->getWorkingCopy()->getWrapper()->streamOutput(); $git->push($upstream, "{$branch}:master"); } catch (GitException $gitException) { $lines = count(explode(PHP_EOL, $gitException->getMessage())); diff --git a/src/Exceptions/GitException.php b/src/Exceptions/GitException.php new file mode 100644 index 0000000..fb3efa1 --- /dev/null +++ b/src/Exceptions/GitException.php @@ -0,0 +1,11 @@ +setTimeout(300); + $client = new GitonomyClient(); + $client->setDirectory($directory); - return new self($wrapper->workingCopy($directory)); + return new self($client); } /** @@ -47,31 +45,25 @@ public static function fromClone( array $options = [ ] ): \fortrabbit\Copy\Services\Git { - $wrapper = new GitWrapper('git'); - $wrapper->setTimeout(300); + $client = new GitonomyClient(); + $client->clone($repository, $directory, $options); - return new self($wrapper->cloneRepository($repository, $directory, $options)); + return new self($client); } public function push(string $upstream, string $branch = 'master'): string { - return $this->gitWorkingCopy->push($upstream, $branch); + return $this->gitClient->push($upstream, $branch); } public function pull(string $upstream, string $branch = 'master'): string { - return $this->gitWorkingCopy->pull($upstream, $branch); + return $this->gitClient->pull($upstream, $branch); } public function getLocalHead(): ?string { - foreach ($this->getLocalBranches() as $key => $name) { - if (stristr($name, '*')) { - return $key; - } - } - - return null; + return $this->gitClient->getLocalHead(); } /** @@ -79,12 +71,7 @@ public function getLocalHead(): ?string */ public function getLocalBranches(): array { - $localBranches = []; - foreach (explode(PHP_EOL, trim($this->gitWorkingCopy->run('branch'))) as $branch) { - $localBranches[trim(ltrim($branch, '*'))] = $branch; - } - - return $localBranches; + return $this->gitClient->getLocalBranches(); } /** @@ -93,27 +80,7 @@ public function getLocalBranches(): array */ public function getRemotes(?string $for = 'push'): array { - if (! in_array($for, ['push', 'pull'], true)) { - throw new LogicException( - sprintf( - 'Argument 1 passed to %s must be "pull" or "push", %s given.', - 'fortrabbit\Copy\Services\Git::getRemotes()', - $for - ) - ); - } - - try { - $remotes = $this->gitWorkingCopy->getRemotes(); - } catch (GitException) { - return []; - } - - foreach ($remotes as $name => $upstreams) { - $remotes[$name] = $upstreams[$for]; - } - - return $remotes; + return $this->gitClient->getRemotes($for); } /** @@ -121,26 +88,7 @@ public function getRemotes(?string $for = 'push'): array */ public function getTracking(bool $includeBranch = false): ?string { - try { - $result = $this->run('rev-parse', ['@{u}', [ - 'abbrev-ref' => true, - 'symbolic-full-name' => true, - ]]); - } catch (GitException) { - return null; - } - - if ($includeBranch) { - return $result; - } - - // Split upstream/branch and return upstream only - return explode('/', $result)[0]; - } - - public function run(string $command, array $argsAndOptions = []): string - { - return $this->gitWorkingCopy->run($command, $argsAndOptions); + return $this->gitClient->getTracking($includeBranch); } /** @@ -158,14 +106,14 @@ public function addRemote(string $sshRemote): string } $app = explode('@', $sshRemote)[0]; - $this->getWorkingCopy()->addRemote($app, "{$sshRemote}:{$app}.git"); + $this->gitClient->addRemote($app, "{$sshRemote}:{$app}.git"); return $app; } - public function getWorkingCopy(): GitWorkingCopy + public function getClient(): Client { - return $this->gitWorkingCopy; + return $this->gitClient; } /** @@ -175,7 +123,7 @@ public function getWorkingCopy(): GitWorkingCopy */ public function assureDotGitignore(): bool { - $path = $this->getWorkingCopy()->getDirectory(); + $path = $this->gitClient->getDirectory(); $gitignoreFile = "{$path}/.gitignore"; $gitignoreExampleFile = Plugin::PLUGIN_ROOT_PATH . '/.gitignore.example'; diff --git a/src/Services/Git/Client.php b/src/Services/Git/Client.php new file mode 100644 index 0000000..3c4776c --- /dev/null +++ b/src/Services/Git/Client.php @@ -0,0 +1,44 @@ +directory = $directory; + $this->repository = Admin::cloneTo($directory, $repository, true, $options); + } + + public function push(string $upstream, string $branch = 'master'): string + { + return $this->run(GitCommand::PUSH, [$upstream, $branch]); + } + + public function pull(string $upstream, string $branch = 'master'): string + { + return $this->run(GitCommand::PULL, [$upstream, $branch]); + } + + public function getLocalHead(): ?string + { + $head = $this->repository->getHead(); + if ($head instanceof Branch) { + return $head->getName(); + } + return $head->getFullName(); + } + + public function getLocalBranches(): array + { + $localBranches = array_filter( + $this->repository->getReferences()->getBranches(), + fn ($branch) => $branch->isLocal() + ); + + return array_map(function ($branch) { + return $branch->getName(); + }, $localBranches); + } + + public function getRemotes(?string $for = 'push'): array + { + if (!in_array($for, ['push', 'pull'], true)) { + throw new LogicException( + sprintf( + 'Argument 1 passed to %s must be "pull" or "push", %s given.', + 'fortrabbit\Copy\Services\Git\GitonomyClient::getRemotes()', + $for + ) + ); + } + + $remotes = explode(PHP_EOL, rtrim($this->run(GitCommand::REMOTE, ['-v']))); + + $map = []; + foreach ($remotes as $mixed) { + [$key, $value] = explode("\t", $mixed); + $map[$key] = strtok($value, " "); + } + $remotes = $map; + + return $remotes; + } + + public function getTracking(bool $includeBranch = false): ?string + { + try { + $result = $this->repository->run(GitCommand::REV_PARSE, [ + '--symbolic-full-name', + '@{u}', + '--abbrev-ref', + ]); + } catch (ProcessException) { + return null; + } + + if ($includeBranch) { + return $result; + } + + // Split upstream/branch and return upstream only + return explode('/', $result)[0]; + } + + public function checkout(string $branch) + { + $this->repository->run(GitCommand::CHECKOUT, [$branch]); + } + + public function addRemote(string $name, ?string $url) + { + $this->repository->run(GitCommand::REMOTE, ['add', $name, $url]); + } + + public function setDirectory(string $directory) + { + $this->directory = $directory; + $this->repository = new Repository($directory); + } + + public function getDirectory(): string + { + return $this->directory; + } + + public function init() + { + Admin::init($this->directory, $this->repository->isBare()); + } + + public function log(...$argsOrOptions): string + { + return $this->run(GitCommand::LOG, $argsOrOptions); + } + + public function hasChanges(): bool + { + return $this->getStatus() !== ''; + } + + public function getStatus(): string + { + return $this->run(GitCommand::STATUS, ['-s']); + } + + public function add(string $filepattern, array $options = []): string + { + return $this->run(GitCommand::ADD, [$filepattern]); + } + + public function commit(...$argsOrOptions): string + { + if (isset($argsOrOptions[0]) && is_string($argsOrOptions[0]) && !isset($argsOrOptions[1])) { + $argsOrOptions = [ + '-a', + '-m ' . $argsOrOptions[0], + ]; + } + + return $this->run(GitCommand::COMMIT, $argsOrOptions); + } + + private function run(string $command, array $args): string + { + try { + return $this->repository->run($command, $args); + } catch (RuntimeException $exception) { + throw new GitException($exception->getMessage(), $exception->getCode(), $exception); + } + } +}