Add ticket branch discovery by ticket key
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace App\Actions\Git\Contracts;
|
||||
|
||||
use App\Actions\Git\Data\GitAuthenticatedUser;
|
||||
use App\Actions\Git\Data\GitBranch;
|
||||
use App\Actions\Git\Data\GitRepository;
|
||||
use App\Actions\Git\GitProviderKey;
|
||||
|
||||
@@ -16,4 +17,14 @@ interface GitProviderClient
|
||||
* @return array<int, GitRepository>
|
||||
*/
|
||||
public function listRepositories(string $baseUrl, string $token): array;
|
||||
|
||||
/**
|
||||
* @return array<int, GitBranch>
|
||||
*/
|
||||
public function listBranches(
|
||||
string $baseUrl,
|
||||
string $token,
|
||||
string $owner,
|
||||
string $repository,
|
||||
): array;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Git\Data;
|
||||
|
||||
/**
|
||||
* Provider-agnostic representation of a repository branch.
|
||||
*/
|
||||
final readonly class GitBranch
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
) {}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Actions\Git\Gitea;
|
||||
|
||||
use App\Actions\Git\Contracts\GitProviderClient;
|
||||
use App\Actions\Git\Data\GitAuthenticatedUser;
|
||||
use App\Actions\Git\Data\GitBranch;
|
||||
use App\Actions\Git\Data\GitRepository;
|
||||
use App\Actions\Git\GitProviderKey;
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
@@ -75,6 +76,43 @@ class GiteaProviderClient implements GitProviderClient
|
||||
return $repositories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, GitBranch>
|
||||
*/
|
||||
public function listBranches(
|
||||
string $baseUrl,
|
||||
string $token,
|
||||
string $owner,
|
||||
string $repository,
|
||||
): array {
|
||||
$page = 1;
|
||||
$branches = [];
|
||||
|
||||
do {
|
||||
/** @var array<int, array{name: string}> $payload */
|
||||
$payload = $this->request($baseUrl, $token)
|
||||
->get(
|
||||
'/repos/'.rawurlencode($owner).'/'.rawurlencode($repository).'/branches',
|
||||
[
|
||||
'limit' => 100,
|
||||
'page' => $page,
|
||||
],
|
||||
)
|
||||
->throw()
|
||||
->json();
|
||||
|
||||
foreach ($payload as $branch) {
|
||||
$branches[] = new GitBranch(
|
||||
name: $branch['name'],
|
||||
);
|
||||
}
|
||||
|
||||
$page++;
|
||||
} while (count($payload) === 100);
|
||||
|
||||
return $branches;
|
||||
}
|
||||
|
||||
private function request(string $baseUrl, string $token): PendingRequest
|
||||
{
|
||||
return Http::acceptJson()
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Git;
|
||||
|
||||
use App\Actions\Git\Data\GitBranch;
|
||||
use App\Models\Project;
|
||||
use App\Models\RepositoryConnection;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\User;
|
||||
use App\Models\UserGitConnection;
|
||||
use Illuminate\Support\Collection;
|
||||
use Throwable;
|
||||
|
||||
class ResolveTicketBranchMatches
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GitProviderManager $gitProviderManager,
|
||||
private readonly TicketKeyReferenceMatcher $ticketKeyReferenceMatcher,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<int, array{
|
||||
* branches: array<int, array{name: string}>,
|
||||
* error: string|null,
|
||||
* repository_connection: array{
|
||||
* base_url: string,
|
||||
* created_at: string|null,
|
||||
* has_personal_access: bool,
|
||||
* id: int,
|
||||
* owner_name: string,
|
||||
* project_id: int,
|
||||
* provider: string,
|
||||
* provider_label: string,
|
||||
* provider_repository_id: string,
|
||||
* repository_full_name: string,
|
||||
* repository_name: string,
|
||||
* repository_url: string,
|
||||
* updated_at: string|null
|
||||
* }
|
||||
* }>
|
||||
*/
|
||||
public function handle(User $user, Project $project, Ticket $ticket): array
|
||||
{
|
||||
/** @var Collection<int, RepositoryConnection> $repositoryConnections */
|
||||
$repositoryConnections = $project->repositoryConnections()
|
||||
->get();
|
||||
|
||||
return $repositoryConnections
|
||||
->map(function (RepositoryConnection $repositoryConnection) use ($ticket, $user): array {
|
||||
$userGitConnection = $this->matchingUserGitConnection($user, $repositoryConnection);
|
||||
$hasPersonalAccess = $userGitConnection !== null;
|
||||
|
||||
if ($userGitConnection === null) {
|
||||
return [
|
||||
'branches' => [],
|
||||
'error' => null,
|
||||
'repository_connection' => $this->repositoryConnectionPayload(
|
||||
$repositoryConnection,
|
||||
false,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$branches = collect(
|
||||
$this->gitProviderManager
|
||||
->client($userGitConnection->provider)
|
||||
->listBranches(
|
||||
$userGitConnection->base_url,
|
||||
$userGitConnection->api_token,
|
||||
$repositoryConnection->owner_name,
|
||||
$repositoryConnection->repository_name,
|
||||
),
|
||||
)
|
||||
->filter(fn (GitBranch $branch): bool => $this->ticketKeyReferenceMatcher->matches(
|
||||
$ticket->ticket_key,
|
||||
$branch->name,
|
||||
))
|
||||
->map(fn (GitBranch $branch): array => [
|
||||
'name' => $branch->name,
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$error = null;
|
||||
} catch (Throwable) {
|
||||
$branches = [];
|
||||
$error = 'KanbanGit could not load branches from this repository right now.';
|
||||
}
|
||||
|
||||
return [
|
||||
'branches' => $branches,
|
||||
'error' => $error,
|
||||
'repository_connection' => $this->repositoryConnectionPayload(
|
||||
$repositoryConnection,
|
||||
$hasPersonalAccess,
|
||||
),
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* base_url: string,
|
||||
* created_at: string|null,
|
||||
* has_personal_access: bool,
|
||||
* id: int,
|
||||
* owner_name: string,
|
||||
* project_id: int,
|
||||
* provider: string,
|
||||
* provider_label: string,
|
||||
* provider_repository_id: string,
|
||||
* repository_full_name: string,
|
||||
* repository_name: string,
|
||||
* repository_url: string,
|
||||
* updated_at: string|null
|
||||
* }
|
||||
*/
|
||||
private function repositoryConnectionPayload(
|
||||
RepositoryConnection $repositoryConnection,
|
||||
bool $hasPersonalAccess,
|
||||
): array {
|
||||
return [
|
||||
'base_url' => $repositoryConnection->base_url,
|
||||
'created_at' => $repositoryConnection->created_at?->toISOString(),
|
||||
'has_personal_access' => $hasPersonalAccess,
|
||||
'id' => $repositoryConnection->id,
|
||||
'owner_name' => $repositoryConnection->owner_name,
|
||||
'project_id' => $repositoryConnection->project_id,
|
||||
'provider' => $repositoryConnection->provider->value,
|
||||
'provider_label' => $repositoryConnection->provider->label(),
|
||||
'provider_repository_id' => $repositoryConnection->provider_repository_id,
|
||||
'repository_full_name' => $repositoryConnection->repository_full_name,
|
||||
'repository_name' => $repositoryConnection->repository_name,
|
||||
'repository_url' => $repositoryConnection->repository_url,
|
||||
'updated_at' => $repositoryConnection->updated_at?->toISOString(),
|
||||
];
|
||||
}
|
||||
|
||||
private function matchingUserGitConnection(
|
||||
User $user,
|
||||
RepositoryConnection $repositoryConnection,
|
||||
): ?UserGitConnection {
|
||||
return $user->gitConnections()
|
||||
->where('provider', $repositoryConnection->provider)
|
||||
->where('base_url', $repositoryConnection->base_url)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Git;
|
||||
|
||||
class TicketKeyReferenceMatcher
|
||||
{
|
||||
public function matches(string $ticketKey, string $referenceName): bool
|
||||
{
|
||||
return preg_match(
|
||||
'/^'.preg_quote($ticketKey, '/').'(?=$|[-_\/])/',
|
||||
$referenceName,
|
||||
) === 1;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Git\ResolveTicketBranchMatches;
|
||||
use App\Actions\ProjectPermissions\ProjectPermissionService;
|
||||
use App\Actions\Tickets\UpdateProjectTicket;
|
||||
use App\Http\Requests\UpdateProjectTicketRequest;
|
||||
@@ -18,6 +19,7 @@ class ProjectTicketController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProjectPermissionService $projectPermissionService,
|
||||
private readonly ResolveTicketBranchMatches $resolveTicketBranchMatches,
|
||||
private readonly UpdateProjectTicket $updateProjectTicket,
|
||||
) {}
|
||||
|
||||
@@ -46,6 +48,11 @@ class ProjectTicketController extends Controller
|
||||
'title' => $parentTicket->title,
|
||||
]),
|
||||
'project' => $project,
|
||||
'repositoryBranches' => $this->resolveTicketBranchMatches->handle(
|
||||
$request->user(),
|
||||
$project,
|
||||
$ticket,
|
||||
),
|
||||
'status' => session('status'),
|
||||
'ticket' => $ticket->load([
|
||||
'fieldValues.ticketFieldDefinition',
|
||||
|
||||
Reference in New Issue
Block a user