fix pattern logic

This commit is contained in:
Hemachandar
2026-04-20 12:02:07 +05:30
committed by harsh mahajan
parent a4f174ea7e
commit e17cdc2bd4
2 changed files with 67 additions and 15 deletions
+28 -4
View File
@@ -21,24 +21,48 @@ class BuildTrigger extends Validator
$include = array_filter($this->patterns, fn ($p) => !str_starts_with($p, '!'));
$exclude = array_filter($this->patterns, fn ($p) => str_starts_with($p, '!'));
if (empty($include)) {
// Only exclusions: pass everything unless excluded.
foreach ($exclude as $pattern) {
if ($this->matchGlob($value, substr($pattern, 1))) {
return false;
}
}
return true;
}
// A pattern is "specific" when it contains no wildcard characters.
$isSpecific = fn($p) => !str_contains($p, '*') && !str_contains($p, '?');
// 1. Specific inclusion always wins — an explicit exact match is never blocked.
foreach ($include as $pattern) {
if ($this->matchGlob($value, $pattern)) {
if ($isSpecific($pattern) && $this->matchGlob($value, $pattern)) {
return true;
}
}
// 2. Specific exclusion overrides a wildcard inclusion — refines broad patterns.
foreach ($exclude as $pattern) {
if ($this->matchGlob($value, substr($pattern, 1))) {
$raw = substr($pattern, 1);
if ($isSpecific($raw) && $this->matchGlob($value, $raw)) {
return false;
}
}
return empty($include);
// 3. Wildcard inclusion wins over any remaining wildcard exclusion.
foreach ($include as $pattern) {
if (!$isSpecific($pattern) && $this->matchGlob($value, $pattern)) {
return true;
}
}
// No inclusion matched.
return false;
}
public function getDescription(): string
{
return 'Value must match at least one inclusion pattern and must not match any exclusion pattern.';
return 'Value must match a specific inclusion, or a wildcard inclusion not overridden by a specific exclusion.';
}
public function isArray(): bool
+39 -11
View File
@@ -59,6 +59,34 @@ class BuildTriggerTest extends TestCase
$this->assertFalse($validator->isValid('feature/test'));
}
public function testQuestionMarkWildcard(): void
{
$validator = new BuildTrigger(['v?.?']);
$this->assertTrue($validator->isValid('v1.0'));
$this->assertTrue($validator->isValid('v2.5'));
$this->assertFalse($validator->isValid('v10.0')); // ? matches exactly one char, not two
$this->assertFalse($validator->isValid('v1/0')); // ? does not cross /
}
public function testQuestionMarkDoesNotCrossSlash(): void
{
$validator = new BuildTrigger(['feature/?']);
$this->assertTrue($validator->isValid('feature/a'));
$this->assertTrue($validator->isValid('feature/z'));
$this->assertFalse($validator->isValid('feature/ab')); // ? matches only one char
$this->assertFalse($validator->isValid('feature/a/b')); // ? does not cross /
$this->assertFalse($validator->isValid('feature/'));
}
public function testQuestionMarkMixedWithStar(): void
{
$validator = new BuildTrigger(['fix-?.*']);
$this->assertTrue($validator->isValid('fix-1.php'));
$this->assertTrue($validator->isValid('fix-a.js'));
$this->assertFalse($validator->isValid('fix-12.php')); // ? matches only one char
$this->assertFalse($validator->isValid('fix-.php')); // ? requires exactly one char
}
public function testDoubleWildcardAtEnd(): void
{
$validator = new BuildTrigger(['src/**']);
@@ -169,36 +197,36 @@ class BuildTriggerTest extends TestCase
public function testMultipleInclusionsWithSingleExclusion(): void
{
// feature/wip matches the inclusion feature/* → true (exclusion !feature/wip is never reached)
// feature/wip matches wildcard inclusion feature/* but specific exclusion !feature/wip overrides it
$validator = new BuildTrigger(['main', 'develop', 'feature/*', '!feature/wip']);
$this->assertTrue($validator->isValid('main'));
$this->assertTrue($validator->isValid('develop'));
$this->assertTrue($validator->isValid('feature/foo'));
$this->assertTrue($validator->isValid('feature/wip')); // inclusion wins
$this->assertFalse($validator->isValid('feature/wip')); // specific exclusion overrides wildcard inclusion
$this->assertFalse($validator->isValid('hotfix/urgent')); // no inclusion match
}
public function testSingleInclusionWithMultipleExclusions(): void
{
// feature/wip and feature/experimental both match the inclusion feature/** → true
// specific exclusions !feature/wip and !feature/experimental override wildcard inclusion feature/**
$validator = new BuildTrigger(['feature/**', '!feature/wip', '!feature/experimental']);
$this->assertTrue($validator->isValid('feature/foo'));
$this->assertTrue($validator->isValid('feature/a/b'));
$this->assertTrue($validator->isValid('feature/wip')); // inclusion wins
$this->assertTrue($validator->isValid('feature/experimental')); // inclusion wins
$this->assertFalse($validator->isValid('main')); // no inclusion match
$this->assertFalse($validator->isValid('feature/wip')); // specific exclusion wins
$this->assertFalse($validator->isValid('feature/experimental')); // specific exclusion wins
$this->assertFalse($validator->isValid('main')); // no inclusion match
}
public function testMultipleInclusionsWithMultipleExclusions(): void
{
// feature/wip and feature/experimental both match the inclusion feature/** → true
// specific exclusions override the wildcard inclusion; specific inclusion 'main' is unaffected
$validator = new BuildTrigger(['main', 'feature/**', '!feature/wip', '!feature/experimental']);
$this->assertTrue($validator->isValid('main'));
$this->assertTrue($validator->isValid('main')); // specific inclusion wins regardless
$this->assertTrue($validator->isValid('feature/foo'));
$this->assertTrue($validator->isValid('feature/a/b'));
$this->assertTrue($validator->isValid('feature/wip')); // inclusion wins
$this->assertTrue($validator->isValid('feature/experimental')); // inclusion wins
$this->assertFalse($validator->isValid('develop')); // no inclusion match
$this->assertFalse($validator->isValid('feature/wip')); // specific exclusion wins
$this->assertFalse($validator->isValid('feature/experimental')); // specific exclusion wins
$this->assertFalse($validator->isValid('develop')); // no inclusion match
}
public function testSpecificInclusionOverridesWildcardExclusion(): void