fix(git): parse generic scp-style SSH URLs with custom users
Centralize scp-style Git URL parsing so user@host:path (including custom usernames and embedded ports) is accepted and converted to HTTPS for public clones, API create, webhooks, validation, and commit/branch links.
This commit is contained in:
parent
e9bf2551ed
commit
83714ea395
15 changed files with 361 additions and 41 deletions
|
|
@ -1460,7 +1460,13 @@ private function create_application(Request $request, $type)
|
|||
$application->docker_compose_domains = json_encode($dockerComposeDomainsJson);
|
||||
$application->domain_port_overrides = $domainPortOverrides;
|
||||
}
|
||||
$repository_url_parsed = Url::fromString($request->git_repository);
|
||||
$gitRepository = $application->git_repository;
|
||||
$httpsRepository = scpStyleGitUrlToHttps($gitRepository);
|
||||
if (is_string($httpsRepository)) {
|
||||
$gitRepository = $httpsRepository;
|
||||
$application->git_repository = $httpsRepository;
|
||||
}
|
||||
$repository_url_parsed = Url::fromString($gitRepository);
|
||||
$git_host = $repository_url_parsed->getHost();
|
||||
if ($git_host === 'github.com') {
|
||||
$application->source_type = GithubApp::class;
|
||||
|
|
@ -1622,11 +1628,7 @@ private function create_application(Request $request, $type)
|
|||
return response()->json(['message' => 'Failed to generate Github App token.'], 400);
|
||||
}
|
||||
|
||||
$gitRepository = $request->git_repository;
|
||||
if (str($gitRepository)->startsWith('http') || str($gitRepository)->contains('github.com')) {
|
||||
$gitRepository = str($gitRepository)->replace('https://', '')->replace('http://', '')->replace('github.com/', '');
|
||||
}
|
||||
$gitRepository = str($gitRepository)->trim('/')->replaceEnd('.git', '')->toString();
|
||||
$gitRepository = gitRepositorySlug($request->git_repository);
|
||||
|
||||
// Use direct API call to verify repository access instead of loading all repositories
|
||||
// This is much faster and avoids timeouts for GitHub Apps with many repositories
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
use App\Models\Application;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait MatchesManualWebhookApplications
|
||||
{
|
||||
|
|
@ -79,12 +78,8 @@ protected function canonicalManualWebhookRepository(?string $gitRepository): ?st
|
|||
|
||||
if (is_array($parts) && isset($parts['scheme'])) {
|
||||
$path = data_get($parts, 'path');
|
||||
} elseif (preg_match('/^[A-Za-z0-9._-]+@[^:]+:/', $gitRepository) === 1) {
|
||||
$path = Str::after($gitRepository, ':');
|
||||
// scp-style SSH URLs embed a custom port as "user@host:2222/owner/repo".
|
||||
// Strip the leading numeric port segment so the path matches the webhook
|
||||
// payload's owner/repo, consistent with convertGitUrl() in shared.php.
|
||||
$path = preg_replace('#^\d+/#', '', $path) ?? $path;
|
||||
} elseif (($scp = parseScpStyleGitUrl($gitRepository)) !== null) {
|
||||
$path = $scp['path'];
|
||||
} else {
|
||||
$path = $gitRepository;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,6 +216,14 @@ private function get_git_source()
|
|||
throw new \RuntimeException('Invalid repository URL: '.$validator->errors()->first('repository_url'));
|
||||
}
|
||||
|
||||
if (($scp = parseScpStyleGitUrl($this->repository_url)) !== null) {
|
||||
$this->git_host = $scp['host'];
|
||||
$this->git_repository = $this->repository_url;
|
||||
$this->git_source = 'other';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->repository_url_parsed = Url::fromString($this->repository_url);
|
||||
$this->git_host = $this->repository_url_parsed->getHost();
|
||||
$this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2);
|
||||
|
|
|
|||
|
|
@ -137,8 +137,9 @@ public function loadBranch()
|
|||
throw new \RuntimeException('Invalid repository URL: '.$validator->errors()->first('repository_url'));
|
||||
}
|
||||
|
||||
if (preg_match('/^(?<user>[A-Za-z0-9._-]+)@(?<host>[^:]+):(?<repository>.+)$/', $this->repository_url, $matches) === 1) {
|
||||
$this->repository_url = 'https://'.$matches['host'].'/'.$matches['repository'];
|
||||
$httpsRepositoryUrl = scpStyleGitUrlToHttps($this->repository_url);
|
||||
if (is_string($httpsRepositoryUrl)) {
|
||||
$this->repository_url = $httpsRepositoryUrl;
|
||||
}
|
||||
if (
|
||||
(str($this->repository_url)->startsWith('https://') ||
|
||||
|
|
|
|||
|
|
@ -663,15 +663,13 @@ public function gitBranchLocation(): Attribute
|
|||
|
||||
return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}{$base_dir}";
|
||||
}
|
||||
// Convert the SSH URL to HTTPS URL
|
||||
if (strpos($this->git_repository, 'git@') === 0) {
|
||||
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
|
||||
|
||||
$httpsRepository = $this->httpsUrlFromScpStyleGitRepository();
|
||||
if (is_string($httpsRepository)) {
|
||||
if (str($this->git_repository)->contains('bitbucket')) {
|
||||
return "https://{$git_repository}/src/{$this->git_branch}{$base_dir}";
|
||||
return "{$httpsRepository}/src/{$this->git_branch}{$base_dir}";
|
||||
}
|
||||
|
||||
return "https://{$git_repository}/tree/{$this->git_branch}{$base_dir}";
|
||||
return "{$httpsRepository}/tree/{$this->git_branch}{$base_dir}";
|
||||
}
|
||||
|
||||
return $this->git_repository;
|
||||
|
|
@ -686,11 +684,9 @@ public function gitWebhook(): Attribute
|
|||
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||
return "{$this->source->html_url}/{$this->git_repository}/settings/hooks";
|
||||
}
|
||||
// Convert the SSH URL to HTTPS URL
|
||||
if (strpos($this->git_repository, 'git@') === 0) {
|
||||
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
|
||||
|
||||
return "https://{$git_repository}/settings/hooks";
|
||||
$httpsRepository = $this->httpsUrlFromScpStyleGitRepository();
|
||||
if (is_string($httpsRepository)) {
|
||||
return "{$httpsRepository}/settings/hooks";
|
||||
}
|
||||
|
||||
return $this->git_repository;
|
||||
|
|
@ -705,11 +701,9 @@ public function gitCommits(): Attribute
|
|||
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||
return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}";
|
||||
}
|
||||
// Convert the SSH URL to HTTPS URL
|
||||
if (strpos($this->git_repository, 'git@') === 0) {
|
||||
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
|
||||
|
||||
return "https://{$git_repository}/commits/{$this->git_branch}";
|
||||
$httpsRepository = $this->httpsUrlFromScpStyleGitRepository();
|
||||
if (is_string($httpsRepository)) {
|
||||
return "{$httpsRepository}/commits/{$this->git_branch}";
|
||||
}
|
||||
|
||||
return $this->git_repository;
|
||||
|
|
@ -728,8 +722,9 @@ public function gitCommitLink($link): ?string
|
|||
}
|
||||
|
||||
$git_repository = $this->git_repository;
|
||||
if (strpos($this->git_repository, 'git@') === 0) {
|
||||
$git_repository = preg_replace('/^git@([^:]+):/', 'https://$1/', $git_repository);
|
||||
$httpsRepository = scpStyleGitUrlToHttps($git_repository);
|
||||
if (is_string($httpsRepository)) {
|
||||
$git_repository = $httpsRepository;
|
||||
} elseif (str($this->git_repository)->startsWith('ssh://')) {
|
||||
$git_repository = 'https://'.parse_url($git_repository, PHP_URL_HOST).parse_url($git_repository, PHP_URL_PATH);
|
||||
}
|
||||
|
|
@ -746,6 +741,17 @@ public function gitCommitLink($link): ?string
|
|||
return $url->__toString();
|
||||
}
|
||||
|
||||
private function httpsUrlFromScpStyleGitRepository(): ?string
|
||||
{
|
||||
$httpsRepository = scpStyleGitUrlToHttps($this->git_repository);
|
||||
|
||||
if (! is_string($httpsRepository)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Str::replaceEnd('.git', '', $httpsRepository);
|
||||
}
|
||||
|
||||
public function dockerfileLocation(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
|
@ -1477,7 +1483,7 @@ public function setGitImportSettings(string $deployment_uuid, string $git_clone_
|
|||
// Check if .gitmodules file exists before running submodule commands
|
||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && if [ -f .gitmodules ]; then";
|
||||
if ($public) {
|
||||
$git_clone_command = "{$git_clone_command} sed -i \"s#git@\(.*\):#https://\\1/#g\" {$escapedBaseDir}/.gitmodules || true &&";
|
||||
$git_clone_command = "{$git_clone_command} sed -i \"s#[A-Za-z0-9._-]*@\(.*\):#https://\\1/#g\" {$escapedBaseDir}/.gitmodules || true &&";
|
||||
}
|
||||
// Add shallow submodules flag if shallow clone is enabled
|
||||
$submoduleFlags = $isShallowCloneEnabled ? '--depth=1' : '';
|
||||
|
|
|
|||
|
|
@ -85,7 +85,8 @@ public function validate(string $attribute, mixed $value, Closure $fail): void
|
|||
}
|
||||
|
||||
// Validate scp-style SSH URL format (user@host:user/repo.git)
|
||||
if (! preg_match('/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+:[a-zA-Z0-9\-_\/.~]+$/', $value)) {
|
||||
$scp = parseScpStyleGitUrl($value);
|
||||
if ($scp === null || preg_match('/^[a-zA-Z0-9.-]+$/', $scp['host']) !== 1 || preg_match('/^[a-zA-Z0-9\-_\/.~]+$/', $scp['path']) !== 1) {
|
||||
$fail('The :attribute is not a valid SSH repository URL.');
|
||||
|
||||
return;
|
||||
|
|
@ -149,7 +150,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void
|
|||
return;
|
||||
}
|
||||
} else {
|
||||
$fail('The :attribute must start with https://, http://, git://, or git@.');
|
||||
$fail('The :attribute must start with https://, http://, git://, or be an SSH URL (user@host:path).');
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4358,6 +4358,62 @@ function defaultNginxConfiguration(string $type = 'static'): string
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an scp-style SSH Git URL (`user@host:path` or `user@host:port/path`).
|
||||
*
|
||||
* @return array{user: string, host: string, port: ?string, path: string}|null
|
||||
*/
|
||||
function parseScpStyleGitUrl(?string $gitRepository): ?array
|
||||
{
|
||||
if (! is_string($gitRepository) || $gitRepository === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('/^(?<user>[A-Za-z0-9._-]+)@(?<host>[^:]+):(?:(?<port>\d+)\/)?(?<path>.+)$/', $gitRepository, $matches) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$host = trim($matches['host']);
|
||||
$path = ltrim($matches['path'], '/');
|
||||
|
||||
if ($host === '' || $path === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'user' => $matches['user'],
|
||||
'host' => $host,
|
||||
'port' => ($matches['port'] ?? '') === '' ? null : $matches['port'],
|
||||
'path' => $path,
|
||||
];
|
||||
}
|
||||
|
||||
function scpStyleGitUrlToHttps(?string $gitRepository): ?string
|
||||
{
|
||||
$parts = parseScpStyleGitUrl($gitRepository);
|
||||
|
||||
if ($parts === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'https://'.$parts['host'].'/'.$parts['path'];
|
||||
}
|
||||
|
||||
function gitRepositorySlug(?string $gitRepository): string
|
||||
{
|
||||
if (! is_string($gitRepository) || $gitRepository === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (($scp = parseScpStyleGitUrl($gitRepository)) !== null) {
|
||||
$gitRepository = $scp['path'];
|
||||
} elseif (str($gitRepository)->startsWith('http') || str($gitRepository)->contains('github.com')) {
|
||||
$gitRepository = str($gitRepository)->replace('https://', '')->replace('http://', '')->replace('github.com/', '');
|
||||
}
|
||||
|
||||
return str($gitRepository)->trim('/')->replaceEnd('.git', '')->toString();
|
||||
}
|
||||
|
||||
function convertGitUrl(string $gitRepository, string $deploymentType, GithubApp|GitlabApp|null $source = null): array
|
||||
{
|
||||
$repository = $gitRepository;
|
||||
|
|
@ -4368,7 +4424,6 @@ function convertGitUrl(string $gitRepository, string $deploymentType, GithubApp|
|
|||
'repository' => $gitRepository,
|
||||
];
|
||||
$sshMatches = [];
|
||||
$matches = [];
|
||||
|
||||
// Let's try and parse the string to detect if it's a valid SSH string or not
|
||||
preg_match('/((.*?)\:\/\/)?(.*@.*:.*)/', $gitRepository, $sshMatches);
|
||||
|
|
@ -4403,11 +4458,11 @@ function convertGitUrl(string $gitRepository, string $deploymentType, GithubApp|
|
|||
$providerInfo['port'] = (string) $parsedRepository['port'];
|
||||
}
|
||||
} else {
|
||||
preg_match('/^(?<host>[^:]+):(?<port>\d+)\/(?<path>.+)$/', $normalizedRepository, $matches);
|
||||
$scp = parseScpStyleGitUrl($normalizedRepository);
|
||||
|
||||
if (! empty($matches['port'])) {
|
||||
$providerInfo['port'] = $matches['port'];
|
||||
$repository = "{$matches['host']}:{$matches['path']}";
|
||||
if ($scp !== null && $scp['port'] !== null) {
|
||||
$providerInfo['port'] = $scp['port'];
|
||||
$repository = "{$scp['user']}@{$scp['host']}:{$scp['path']}";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
53
tests/Feature/Api/PublicApplicationSshUrlApiTest.php
Normal file
53
tests/Feature/Api/PublicApplicationSshUrlApiTest.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
config(['app.maintenance.driver' => 'file']);
|
||||
Storage::fake('ssh-keys');
|
||||
InstanceSettings::unguarded(fn () => InstanceSettings::firstOrCreate(['id' => 0]));
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
$this->user = User::factory()->create();
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->bearerToken = $this->user->createToken('public-ssh-url-api-test', ['*'])->plainTextToken;
|
||||
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
$this->destination = StandaloneDocker::where('server_id', $this->server->id)->first();
|
||||
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
||||
});
|
||||
|
||||
test('public application api converts scp-style ssh urls to https', function () {
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/applications/public', [
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'environment_uuid' => $this->environment->uuid,
|
||||
'server_uuid' => $this->server->uuid,
|
||||
'git_repository' => 'custom-user@git.example.com:2222/organization/repository.git',
|
||||
'git_branch' => 'main',
|
||||
'build_pack' => 'nixpacks',
|
||||
'ports_exposes' => '3000',
|
||||
'autogenerate_domain' => false,
|
||||
]);
|
||||
|
||||
$response->assertCreated();
|
||||
|
||||
$application = Application::where('uuid', $response->json('uuid'))->firstOrFail();
|
||||
|
||||
expect($application->git_repository)->toBe('https://git.example.com/organization/repository.git');
|
||||
});
|
||||
|
|
@ -69,6 +69,28 @@ function applicationWithGitSettings(bool $shallow = true): Application
|
|||
->toContain("git -c http.version=HTTP/1.1 -c advice.detachedHead=false checkout 'abc123def456abc123def456abc123def456abc1'");
|
||||
});
|
||||
|
||||
it('rewrites generic ssh submodule remotes to https for public clones', function () {
|
||||
$application = applicationWithGitSettings(shallow: false);
|
||||
$application->settings->is_git_submodules_enabled = true;
|
||||
|
||||
$source = new GithubApp;
|
||||
$source->forceFill([
|
||||
'html_url' => 'https://github.com',
|
||||
'api_url' => 'https://api.github.com',
|
||||
'is_public' => true,
|
||||
]);
|
||||
$application->setRelation('source', $source);
|
||||
|
||||
$result = $application->generateGitImportCommands(
|
||||
deployment_uuid: 'test-deployment',
|
||||
exec_in_docker: false,
|
||||
);
|
||||
|
||||
expect($result['commands'])
|
||||
->toContain('sed -i "s#[A-Za-z0-9._-]*@\(.*\):#https://\\1/#g"')
|
||||
->not->toContain('s#git@\(.*\):#https://\\1/#g');
|
||||
});
|
||||
|
||||
it('does not add http transport config to ssh deploy key clones', function () {
|
||||
$application = applicationWithGitSettings();
|
||||
$application->private_key_id = 1;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,14 @@
|
|||
]);
|
||||
});
|
||||
|
||||
test('convertGitUrlsForSourceAndSshUrlWithCustomUsernameAndPort', function () {
|
||||
$result = convertGitUrl('custom-user@git.domain.com:766/group/project.git', 'source', null);
|
||||
expect($result)->toBe([
|
||||
'repository' => 'custom-user@git.domain.com:group/project.git',
|
||||
'port' => '766',
|
||||
]);
|
||||
});
|
||||
|
||||
test('convertGitUrlsForSourceAndSshUrlSchemeWithCustomPort', function () {
|
||||
$result = convertGitUrl('ssh://git@192.168.56.11:22222/User/Repo.git', 'source', null);
|
||||
expect($result)->toBe([
|
||||
|
|
|
|||
34
tests/Feature/PublicGitRepositorySshUrlTest.php
Normal file
34
tests/Feature/PublicGitRepositorySshUrlTest.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use App\Livewire\Project\New\PublicGitRepository;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$team = Team::factory()->create();
|
||||
$user = User::factory()->create();
|
||||
$team->members()->attach($user->id, ['role' => 'owner']);
|
||||
|
||||
$this->actingAs($user);
|
||||
session(['currentTeam' => $team]);
|
||||
});
|
||||
|
||||
test('converts scp-style ssh urls with custom usernames to https', function () {
|
||||
Livewire::test(PublicGitRepository::class, ['type' => 'public'])
|
||||
->set('repository_url', 'custom-user@git.example.com:organization/repository.git')
|
||||
->call('loadBranch')
|
||||
->assertSet('repository_url', 'https://git.example.com/organization/repository.git')
|
||||
->assertSet('branchFound', true);
|
||||
});
|
||||
|
||||
test('strips custom ports when converting scp-style ssh urls to https', function () {
|
||||
Livewire::test(PublicGitRepository::class, ['type' => 'public'])
|
||||
->set('repository_url', 'custom-user@git.example.com:2222/organization/repository.git')
|
||||
->call('loadBranch')
|
||||
->assertSet('repository_url', 'https://git.example.com/organization/repository.git')
|
||||
->assertSet('branchFound', true);
|
||||
});
|
||||
|
|
@ -565,6 +565,29 @@ function createApplicationWithWebhook(string $repo = 'test-org/test-repo', strin
|
|||
expect($response->getContent())->not->toContain('No applications found');
|
||||
});
|
||||
|
||||
test('github matches an ssh repository URL with a non-git username and custom port', function () {
|
||||
$app = createApplicationWithWebhook(overrides: [
|
||||
'git_repository' => 'custom-user@git.example.com:2222/test-org/test-repo.git',
|
||||
]);
|
||||
$secret = $app->manual_webhook_secret_github;
|
||||
|
||||
$payload = json_encode([
|
||||
'ref' => 'refs/heads/main',
|
||||
'repository' => ['full_name' => 'test-org/test-repo'],
|
||||
'after' => 'abc123',
|
||||
'commits' => [],
|
||||
]);
|
||||
|
||||
$response = $this->call('POST', '/webhooks/source/github/events/manual', [], [], [], [
|
||||
'HTTP_X-GitHub-Event' => 'push',
|
||||
'HTTP_X-Hub-Signature-256' => 'sha256='.hash_hmac('sha256', $payload, $secret),
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], $payload);
|
||||
|
||||
$response->assertOk();
|
||||
expect($response->getContent())->not->toContain('No applications found');
|
||||
});
|
||||
|
||||
test('gitlab matches scp-style ssh repository URL with custom port', function () {
|
||||
$app = createApplicationWithWebhook(overrides: [
|
||||
'git_repository' => 'git@gitlab.example.com:2222/services/xyz.git',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@
|
|||
'git@github.com:coollabsio/coolify.git',
|
||||
'https://github.com/coollabsio/coolify/commit/1234567890abcdef',
|
||||
],
|
||||
'SSH remote with custom username' => [
|
||||
'custom-user@git.example.com:coollabsio/coolify.git',
|
||||
'https://git.example.com/coollabsio/coolify/commit/1234567890abcdef',
|
||||
],
|
||||
'SSH remote with custom username and port' => [
|
||||
'custom-user@git.example.com:2222/coollabsio/coolify.git',
|
||||
'https://git.example.com/coollabsio/coolify/commit/1234567890abcdef',
|
||||
],
|
||||
'SSH URL' => [
|
||||
'ssh://git@gitlab.com/coollabsio/coolify.git',
|
||||
'https://gitlab.com/coollabsio/coolify/commit/1234567890abcdef',
|
||||
|
|
@ -37,3 +45,34 @@
|
|||
'missing host' => 'https://',
|
||||
'missing scheme' => 'github.com/coollabsio/coolify',
|
||||
]);
|
||||
|
||||
it('converts scp-style remotes with generic usernames into https repository links', function (string $repository, string $expectedBranch, string $expectedCommits, string $expectedWebhook) {
|
||||
$application = new Application;
|
||||
$application->setRelation('source', null);
|
||||
$application->git_repository = $repository;
|
||||
$application->git_branch = 'main';
|
||||
$application->base_directory = '/';
|
||||
|
||||
expect($application->gitBranchLocation)->toBe($expectedBranch)
|
||||
->and($application->gitCommits)->toBe($expectedCommits)
|
||||
->and($application->gitWebhook)->toBe($expectedWebhook);
|
||||
})->with([
|
||||
'git username' => [
|
||||
'git@github.com:coollabsio/coolify.git',
|
||||
'https://github.com/coollabsio/coolify/tree/main/',
|
||||
'https://github.com/coollabsio/coolify/commits/main',
|
||||
'https://github.com/coollabsio/coolify/settings/hooks',
|
||||
],
|
||||
'custom username' => [
|
||||
'custom-user@git.example.com:organization/repository.git',
|
||||
'https://git.example.com/organization/repository/tree/main/',
|
||||
'https://git.example.com/organization/repository/commits/main',
|
||||
'https://git.example.com/organization/repository/settings/hooks',
|
||||
],
|
||||
'custom username and port' => [
|
||||
'custom-user@git.example.com:2222/organization/repository.git',
|
||||
'https://git.example.com/organization/repository/tree/main/',
|
||||
'https://git.example.com/organization/repository/commits/main',
|
||||
'https://git.example.com/organization/repository/settings/hooks',
|
||||
],
|
||||
]);
|
||||
|
|
|
|||
70
tests/Unit/ScpStyleGitUrlTest.php
Normal file
70
tests/Unit/ScpStyleGitUrlTest.php
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
it('parses scp-style ssh git urls including custom usernames and ports', function (string $url, array $expected) {
|
||||
expect(parseScpStyleGitUrl($url))->toBe($expected);
|
||||
})->with([
|
||||
'git username' => [
|
||||
'git@github.com:organization/repository.git',
|
||||
[
|
||||
'user' => 'git',
|
||||
'host' => 'github.com',
|
||||
'port' => null,
|
||||
'path' => 'organization/repository.git',
|
||||
],
|
||||
],
|
||||
'custom username' => [
|
||||
'custom-user@git.example.com:organization/repository.git',
|
||||
[
|
||||
'user' => 'custom-user',
|
||||
'host' => 'git.example.com',
|
||||
'port' => null,
|
||||
'path' => 'organization/repository.git',
|
||||
],
|
||||
],
|
||||
'custom username and port' => [
|
||||
'custom-user@git.example.com:2222/organization/repository.git',
|
||||
[
|
||||
'user' => 'custom-user',
|
||||
'host' => 'git.example.com',
|
||||
'port' => '2222',
|
||||
'path' => 'organization/repository.git',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
it('converts scp-style ssh git urls to https without embedding custom ports in the path', function (string $url, string $expected) {
|
||||
expect(scpStyleGitUrlToHttps($url))->toBe($expected);
|
||||
})->with([
|
||||
'git username' => [
|
||||
'git@github.com:organization/repository.git',
|
||||
'https://github.com/organization/repository.git',
|
||||
],
|
||||
'custom username' => [
|
||||
'custom-user@git.example.com:organization/repository.git',
|
||||
'https://git.example.com/organization/repository.git',
|
||||
],
|
||||
'custom username and port' => [
|
||||
'custom-user@git.example.com:2222/organization/repository.git',
|
||||
'https://git.example.com/organization/repository.git',
|
||||
],
|
||||
]);
|
||||
|
||||
it('rejects non-scp-style git urls', function (string $url) {
|
||||
expect(parseScpStyleGitUrl($url))->toBeNull()
|
||||
->and(scpStyleGitUrlToHttps($url))->toBeNull();
|
||||
})->with([
|
||||
'https' => 'https://github.com/organization/repository.git',
|
||||
'email without path' => 'custom-user@git.example.com',
|
||||
'ssh scheme' => 'ssh://git@github.com/organization/repository.git',
|
||||
'empty' => '',
|
||||
]);
|
||||
|
||||
it('normalizes github app repository slugs from scp-style ssh urls', function (string $url, string $expected) {
|
||||
expect(gitRepositorySlug($url))->toBe($expected);
|
||||
})->with([
|
||||
'https' => ['https://github.com/organization/repository.git', 'organization/repository'],
|
||||
'owner/repo' => ['organization/repository', 'organization/repository'],
|
||||
'git username' => ['git@github.com:organization/repository.git', 'organization/repository'],
|
||||
'custom username' => ['custom-user@git.example.com:organization/repository.git', 'organization/repository'],
|
||||
'custom username and port' => ['custom-user@git.example.com:2222/organization/repository.git', 'organization/repository'],
|
||||
]);
|
||||
|
|
@ -108,6 +108,8 @@
|
|||
'git@gitlab.com:user/repo.git',
|
||||
'git@bitbucket.org:user/repo.git',
|
||||
'custom-user@git.example.com:organization/repository.git',
|
||||
'custom-user@git.example.com:2222/organization/repository.git',
|
||||
'enterprise-user@enterprise.ghe.com:organization/repository.git',
|
||||
];
|
||||
|
||||
foreach ($validUrls as $url) {
|
||||
|
|
@ -130,6 +132,7 @@
|
|||
$invalidUrls = [
|
||||
'git@github.com:user/repo.git',
|
||||
'git@gitlab.com:user/repo.git',
|
||||
'custom-user@git.example.com:organization/repository.git',
|
||||
];
|
||||
|
||||
foreach ($invalidUrls as $url) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue