fix(project): handle slash branches in public repo URLs

Parse `/tree/...` URLs by first capturing the full branch candidate, then
iteratively resolving valid branch names for GitHub API lookups and deriving
the remaining path as base directory. Also adjust env var editor/input view
classes (`font-sans`, `w-full`) and add/extend feature tests for both branch
parsing and multiline toggle rendering.
This commit is contained in:
Andras Bacsai 2026-04-01 09:11:56 +02:00
parent c1d670b1e5
commit 968508583d
5 changed files with 113 additions and 15 deletions

View file

@ -208,13 +208,8 @@ private function getGitSource()
if ($this->repository_url_parsed->getSegment(3) === 'tree') {
$path = str($this->repository_url_parsed->getPath())->trim('/');
$this->git_branch = str($path)->after('tree/')->before('/')->value();
$this->base_directory = str($path)->after($this->git_branch)->after('/')->value();
if (filled($this->base_directory)) {
$this->base_directory = '/'.$this->base_directory;
} else {
$this->base_directory = '/';
}
$this->git_branch = str($path)->after('tree/')->value();
$this->base_directory = '/';
} else {
$this->git_branch = 'main';
}
@ -235,9 +230,32 @@ private function getBranch()
return;
}
if ($this->git_source->getMorphClass() === GithubApp::class) {
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
$this->rate_limit_reset = Carbon::parse((int) $this->rate_limit_reset)->format('Y-M-d H:i:s');
$this->branchFound = true;
$originalBranch = $this->git_branch;
$branchToTry = $originalBranch;
while (true) {
try {
$encodedBranch = urlencode($branchToTry);
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$encodedBranch}");
$this->rate_limit_reset = Carbon::parse((int) $this->rate_limit_reset)->format('Y-M-d H:i:s');
$this->git_branch = $branchToTry;
$remaining = str($originalBranch)->after($branchToTry)->trim('/')->value();
$this->base_directory = filled($remaining) ? '/'.$remaining : '/';
$this->branchFound = true;
return;
} catch (\Throwable $e) {
if (str_contains($branchToTry, '/')) {
$branchToTry = str($branchToTry)->beforeLast('/')->value();
continue;
}
throw $e;
}
}
}
}

View file

@ -84,21 +84,21 @@
Inline comments with space before # (e.g., <code class="font-mono">KEY=value #comment</code>) are stripped.
</x-callout>
<x-forms.textarea rows="10" class="whitespace-pre-wrap" id="variables" wire:model="variables" monospace
<x-forms.textarea rows="10" class="whitespace-pre-wrap font-sans" id="variables" wire:model="variables"
label="Production Environment Variables"></x-forms.textarea>
@if ($showPreview)
<x-forms.textarea rows="10" class="whitespace-pre-wrap" label="Preview Deployments Environment Variables" monospace
<x-forms.textarea rows="10" class="whitespace-pre-wrap font-sans" label="Preview Deployments Environment Variables"
id="variablesPreview" wire:model="variablesPreview"></x-forms.textarea>
@endif
<x-forms.button type="submit" class="btn btn-primary">Save All Environment Variables</x-forms.button>
@else
<x-forms.textarea rows="10" class="whitespace-pre-wrap" id="variables" wire:model="variables" monospace
<x-forms.textarea rows="10" class="whitespace-pre-wrap font-sans" id="variables" wire:model="variables"
label="Production Environment Variables" disabled></x-forms.textarea>
@if ($showPreview)
<x-forms.textarea rows="10" class="whitespace-pre-wrap" label="Preview Deployments Environment Variables" monospace
<x-forms.textarea rows="10" class="whitespace-pre-wrap font-sans" label="Preview Deployments Environment Variables"
id="variablesPreview" wire:model="variablesPreview" disabled></x-forms.textarea>
@endif
@endcan

View file

@ -155,7 +155,7 @@
</div>
@else
<x-forms.input :disabled="$is_redis_credential" :required="$is_redis_credential" id="key" />
<div class="flex-1" wire:key="env-show-value-input-{{ $env->id }}">
<div class="w-full" wire:key="env-show-value-input-{{ $env->id }}">
<x-forms.env-var-input
:required="$is_redis_credential"
type="password"

View file

@ -20,3 +20,12 @@
->toContain('wire:key="env-show-value-textarea-{{ $env->id }}"')
->toContain('wire:key="env-show-value-input-{{ $env->id }}"');
});
it('uses sans font for the developer bulk environment variable editor', function () {
$view = file_get_contents(resource_path('views/livewire/project/shared/environment-variable/all.blade.php'));
expect($view)
->toContain('class="whitespace-pre-wrap font-sans"')
->not->toContain('wire:model="variables" monospace')
->not->toContain('wire:model="variablesPreview" monospace');
});

View file

@ -0,0 +1,71 @@
<?php
use Spatie\Url\Url;
/**
* Tests for branch name parsing from Git repository URLs.
* Verifies that branch names containing slashes (e.g., fix/something)
* are correctly extracted from URLs like /tree/fix/something.
*/
function parseBranchFromUrl(string $url): array
{
$parsed = Url::fromString($url);
$branch = 'main';
$baseDirectory = '/';
if ($parsed->getSegment(3) === 'tree') {
$path = str($parsed->getPath())->trim('/');
$branch = str($path)->after('tree/')->value();
$baseDirectory = '/';
}
return [
'branch' => $branch,
'base_directory' => $baseDirectory,
'repository' => $parsed->getSegment(1).'/'.$parsed->getSegment(2),
];
}
test('parses simple branch from GitHub URL', function () {
$result = parseBranchFromUrl('https://github.com/andrasbacsai/coolify-examples/tree/main');
expect($result['branch'])->toBe('main');
expect($result['base_directory'])->toBe('/');
expect($result['repository'])->toBe('andrasbacsai/coolify-examples');
});
test('parses branch with slash from GitHub URL', function () {
$result = parseBranchFromUrl('https://github.com/andrasbacsai/coolify-examples-1/tree/fix/8854-env-var-fallback-volume');
expect($result['branch'])->toBe('fix/8854-env-var-fallback-volume');
expect($result['base_directory'])->toBe('/');
expect($result['repository'])->toBe('andrasbacsai/coolify-examples-1');
});
test('parses branch with multiple slashes from GitHub URL', function () {
$result = parseBranchFromUrl('https://github.com/user/repo/tree/feature/team/new-widget');
expect($result['branch'])->toBe('feature/team/new-widget');
expect($result['base_directory'])->toBe('/');
});
test('defaults to main branch when no tree segment in URL', function () {
$result = parseBranchFromUrl('https://github.com/andrasbacsai/coolify-examples');
expect($result['branch'])->toBe('main');
expect($result['base_directory'])->toBe('/');
});
test('parses version-style branch with slash from GitHub URL', function () {
$result = parseBranchFromUrl('https://github.com/coollabsio/coolify-examples/tree/release/v2.0');
expect($result['branch'])->toBe('release/v2.0');
expect($result['base_directory'])->toBe('/');
});
test('parses branch from non-GitHub URL with tree segment', function () {
$result = parseBranchFromUrl('https://gitlab.com/user/repo/tree/hotfix/critical-bug');
expect($result['branch'])->toBe('hotfix/critical-bug');
expect($result['base_directory'])->toBe('/');
});