From e17cdc2bd47602f3942ff23e9a5c0e2aaba290fa Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Tue, 24 Mar 2026 16:31:42 +0530 Subject: [PATCH] fix pattern logic --- src/Appwrite/Vcs/Validator/BuildTrigger.php | 32 ++++++++++-- tests/unit/Vcs/Validator/BuildTriggerTest.php | 50 +++++++++++++++---- 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/src/Appwrite/Vcs/Validator/BuildTrigger.php b/src/Appwrite/Vcs/Validator/BuildTrigger.php index 44296ae73a..0cad312e7b 100644 --- a/src/Appwrite/Vcs/Validator/BuildTrigger.php +++ b/src/Appwrite/Vcs/Validator/BuildTrigger.php @@ -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 diff --git a/tests/unit/Vcs/Validator/BuildTriggerTest.php b/tests/unit/Vcs/Validator/BuildTriggerTest.php index 61d839cf99..a359a570ac 100644 --- a/tests/unit/Vcs/Validator/BuildTriggerTest.php +++ b/tests/unit/Vcs/Validator/BuildTriggerTest.php @@ -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