This commit is contained in:
nuno maduro
2026-08-05 20:20:35 +01:00
parent d112582857
commit e90e4f70fc
8 changed files with 360 additions and 16 deletions
+14 -1
View File
@@ -241,7 +241,20 @@ final readonly class ChangedFiles
}
}
return $this->gitOutput(['git', 'config', '--get', 'init.defaultBranch']);
// `init.defaultBranch` is a setting of the machine, not of the
// repository — it names what `git init` would have called the first
// branch here, which is worth nothing once the repository disagrees.
// Taken only when a branch by that name actually exists.
$configured = $this->gitOutput(['git', 'config', '--get', 'init.defaultBranch']);
if ($configured === null) {
return null;
}
$exists = $this->gitOutput(['git', 'rev-parse', '--verify', '--quiet', 'refs/heads/'.$configured]) !== null
|| $this->gitOutput(['git', 'rev-parse', '--verify', '--quiet', 'refs/remotes/origin/'.$configured]) !== null;
return $exists ? $configured : null;
}
/**
+77
View File
@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Pest\Plugins\Tia;
/**
* The default branch as the CI provider itself reports it.
*
* Worth asking before git: a CI checkout is the one place where git knows the
* least. `actions/checkout` builds the working copy with `git init` plus a
* single-ref `fetch` rather than a `clone`, so `origin/HEAD` is never set and
* `init.defaultBranch` — a setting of the runner image, not of the repository —
* is all git has left to offer. The provider, meanwhile, states the answer
* outright in the environment it handed us.
*
* @internal
*/
final class CiDefaultBranch
{
/**
* Advisory, like every other source in the chain: anything unreadable,
* unparsable, or simply absent means "no answer", never a failure.
*/
public static function detect(): ?string
{
return self::fromGitLab() ?? self::fromGitHubEvent();
}
private static function fromGitLab(): ?string
{
return self::environment('CI_DEFAULT_BRANCH');
}
/**
* GitHub publishes no default-branch variable, but every repository-scoped
* event payload carries `repository.default_branch`, and the path to that
* payload is in the environment.
*/
private static function fromGitHubEvent(): ?string
{
$path = self::environment('GITHUB_EVENT_PATH');
if ($path === null || ! is_file($path) || ! is_readable($path)) {
return null;
}
$contents = @file_get_contents($path);
if ($contents === false) {
return null;
}
$payload = json_decode($contents, true);
if (! is_array($payload) || ! is_array($payload['repository'] ?? null)) {
return null;
}
$branch = $payload['repository']['default_branch'] ?? null;
return is_string($branch) && $branch !== '' ? $branch : null;
}
private static function environment(string $name): ?string
{
$value = getenv($name);
if (! is_string($value)) {
return null;
}
$value = trim($value);
return $value === '' ? null : $value;
}
}
+29
View File
@@ -1482,6 +1482,35 @@ final class Graph
}
}
/**
* The branches a recorded graph holds baselines for, read straight from the
* encoded form.
*
* Answerable before the graph is hydrated because the default branch has to
* be resolved first: the fallback is what every hydrated graph reads its
* baselines through.
*
* @return list<string>
*/
public static function branchesIn(string $json): array
{
$data = json_decode($json, true);
if (! is_array($data) || ! is_array($data['baselines'] ?? null)) {
return [];
}
$branches = [];
foreach (array_keys($data['baselines']) as $branch) {
if (is_string($branch) && $branch !== '') {
$branches[] = $branch;
}
}
return $branches;
}
public static function decode(string $json, string $projectRoot): ?self
{
$data = json_decode($json, true);