diff --git a/packages/vcs/src/VCS/Adapter/Git/Bitbucket.php b/packages/vcs/src/VCS/Adapter/Git/Bitbucket.php index 9082c36e..26386086 100644 --- a/packages/vcs/src/VCS/Adapter/Git/Bitbucket.php +++ b/packages/vcs/src/VCS/Adapter/Git/Bitbucket.php @@ -1505,7 +1505,8 @@ public function getRepositoryPresignedUrl(string $owner, string $repositoryName, public function generateCloneCommand(string $owner, string $repositoryName, string $version, string $versionType, string $directory, string $rootDirectory): string { - if (\in_array($rootDirectory, ['', '0', '/'], true)) { + $rootDirectory = $this->normalizeRepositoryPath($rootDirectory); + if ($rootDirectory === '') { $rootDirectory = '*'; } diff --git a/packages/vcs/src/VCS/Adapter/Git/GitHub.php b/packages/vcs/src/VCS/Adapter/Git/GitHub.php index 70870186..88f56829 100644 --- a/packages/vcs/src/VCS/Adapter/Git/GitHub.php +++ b/packages/vcs/src/VCS/Adapter/Git/GitHub.php @@ -1189,7 +1189,8 @@ public function updateCheckRun( */ public function generateCloneCommand(string $owner, string $repositoryName, string $version, string $versionType, string $directory, string $rootDirectory): string { - if ($rootDirectory === '' || $rootDirectory === '0') { + $rootDirectory = $this->normalizeRepositoryPath($rootDirectory); + if ($rootDirectory === '') { $rootDirectory = '*'; } diff --git a/packages/vcs/src/VCS/Adapter/Git/GitLab.php b/packages/vcs/src/VCS/Adapter/Git/GitLab.php index 4eba6018..73f4c50d 100644 --- a/packages/vcs/src/VCS/Adapter/Git/GitLab.php +++ b/packages/vcs/src/VCS/Adapter/Git/GitLab.php @@ -978,7 +978,8 @@ public function updateCommitStatus(string $repositoryName, string $commitHash, s public function generateCloneCommand(string $owner, string $repositoryName, string $version, string $versionType, string $directory, string $rootDirectory): string { - if (\in_array($rootDirectory, ['', '0', '/'], true)) { + $rootDirectory = $this->normalizeRepositoryPath($rootDirectory); + if ($rootDirectory === '') { $rootDirectory = '*'; } diff --git a/packages/vcs/src/VCS/Adapter/Git/Gitea.php b/packages/vcs/src/VCS/Adapter/Git/Gitea.php index 8a26b402..99fe2d57 100644 --- a/packages/vcs/src/VCS/Adapter/Git/Gitea.php +++ b/packages/vcs/src/VCS/Adapter/Git/Gitea.php @@ -1057,7 +1057,8 @@ public function updateCommitStatus(string $repositoryName, string $commitHash, s */ public function generateCloneCommand(string $owner, string $repositoryName, string $version, string $versionType, string $directory, string $rootDirectory): string { - if ($rootDirectory === '' || $rootDirectory === '0') { + $rootDirectory = $this->normalizeRepositoryPath($rootDirectory); + if ($rootDirectory === '') { $rootDirectory = '*'; } $cloneUrl = "{$this->giteaUrl}/{$owner}/{$repositoryName}"; diff --git a/packages/vcs/src/VCS/Adapter/Git/Origin.php b/packages/vcs/src/VCS/Adapter/Git/Origin.php index 9dfba399..e5c1ed93 100644 --- a/packages/vcs/src/VCS/Adapter/Git/Origin.php +++ b/packages/vcs/src/VCS/Adapter/Git/Origin.php @@ -891,7 +891,8 @@ public function updateComment(string $owner, string $repositoryName, string $com */ public function generateCloneCommand(string $owner, string $repositoryName, string $version, string $versionType, string $directory, string $rootDirectory): string { - if ($rootDirectory === '' || $rootDirectory === '0') { + $rootDirectory = $this->normalizeRepositoryPath($rootDirectory); + if ($rootDirectory === '') { $rootDirectory = '*'; } diff --git a/packages/vcs/tests/Unit/Base.php b/packages/vcs/tests/Unit/Base.php index bcf780f7..8fd6eb67 100644 --- a/packages/vcs/tests/Unit/Base.php +++ b/packages/vcs/tests/Unit/Base.php @@ -5,13 +5,15 @@ namespace Utopia\Tests\Unit; use Exception; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Utopia\VCS\Adapter\Git; /** * The half of the adapter contract that needs no provider: reading a webhook - * delivery and verifying its signature. Every provider answers it from a - * payload the subclass builds, so the suite runs on a bare host. + * delivery and verifying its signature, and building a clone command. Every + * provider answers it from a payload the subclass builds, so the suite runs on + * a bare host. */ abstract class Base extends TestCase { @@ -221,4 +223,31 @@ public function testGetEventUnsupportedEvent(): void $this->assertEmpty($result); } + + #[DataProvider('rootDirectories')] + public function testGenerateCloneCommandSelectsTheRootDirectory(string $rootDirectory, string $pattern): void + { + $command = $this->vcsAdapter->generateCloneCommand('owner', 'repo', 'main', Git::CLONE_TYPE_BRANCH, '/tmp/clone', $rootDirectory); + + $this->assertStringContainsString(escapeshellarg($pattern), $command); + } + + /** + * Git matches a sparse-checkout pattern gitignore-style, so a './' prefix + * looks for a directory literally named '.' and checks out nothing. + */ + public static function rootDirectories(): \Iterator + { + yield 'repository root' => ['', '*']; + yield 'dot' => ['.', '*']; + yield 'dot slash' => ['./', '*']; + yield 'slash' => ['/', '*']; + yield 'bare' => ['docs', 'docs']; + yield 'trailing slash' => ['docs/', 'docs']; + yield 'dot slash prefix' => ['./docs', 'docs']; + yield 'dot slash prefix and trailing slash' => ['./docs/', 'docs']; + yield 'nested' => ['./astro/starter', 'astro/starter']; + // A directory named '0' is a real path, not a root sentinel. + yield 'zero' => ['0', '0']; + } } diff --git a/packages/vcs/tests/Unit/ForgejoTest.php b/packages/vcs/tests/Unit/ForgejoTest.php index aec65a4f..5dfe2c9a 100644 --- a/packages/vcs/tests/Unit/ForgejoTest.php +++ b/packages/vcs/tests/Unit/ForgejoTest.php @@ -15,6 +15,9 @@ final class ForgejoTest extends GiteaBase protected function createAdapter(): Forgejo { - return new Forgejo(new Cache(new None())); + $adapter = new Forgejo(new Cache(new None())); + $adapter->setEndpoint('http://gitea:3000'); + + return $adapter; } } diff --git a/packages/vcs/tests/Unit/GiteaTest.php b/packages/vcs/tests/Unit/GiteaTest.php index 57d9e93e..ddf445b1 100644 --- a/packages/vcs/tests/Unit/GiteaTest.php +++ b/packages/vcs/tests/Unit/GiteaTest.php @@ -15,6 +15,9 @@ final class GiteaTest extends GiteaBase protected function createAdapter(): Gitea { - return new Gitea(new Cache(new None())); + $adapter = new Gitea(new Cache(new None())); + $adapter->setEndpoint('http://gitea:3000'); + + return $adapter; } } diff --git a/packages/vcs/tests/Unit/GogsTest.php b/packages/vcs/tests/Unit/GogsTest.php index 5d62f461..9384e784 100644 --- a/packages/vcs/tests/Unit/GogsTest.php +++ b/packages/vcs/tests/Unit/GogsTest.php @@ -17,6 +17,9 @@ final class GogsTest extends GiteaBase protected function createAdapter(): Gogs { - return new Gogs(new Cache(new None())); + $adapter = new Gogs(new Cache(new None())); + $adapter->setEndpoint('http://gitea:3000'); + + return $adapter; } }