diff --git a/src/Appwrite/Vcs/Validator/CommitSkipPatterns.php b/src/Appwrite/Vcs/Validator/CommitSkipPatterns.php index 76c04e5417..7243808bf8 100644 --- a/src/Appwrite/Vcs/Validator/CommitSkipPatterns.php +++ b/src/Appwrite/Vcs/Validator/CommitSkipPatterns.php @@ -12,7 +12,7 @@ class CommitSkipPatterns extends Validator /** * Returns false (skip deployment) when the commit message contains any of the - * configured patterns (case-insensitive substring match). + * configured skip directives. * Returns true (proceed) when no patterns are configured or none match. */ public function isValid($value): bool @@ -21,11 +21,13 @@ class CommitSkipPatterns extends Validator return false; } - foreach ($this->patterns as $pattern) { - if (!is_string($pattern) || $pattern === '') { - continue; - } - if (stripos($value, $pattern) !== false) { + $patterns = $this->normalizePatterns($this->patterns); + if (empty($patterns)) { + return true; + } + + foreach ($this->extractDirectives($value) as $directive) { + if (isset($patterns[$directive])) { return false; } } @@ -47,4 +49,64 @@ class CommitSkipPatterns extends Validator { return self::TYPE_STRING; } + + /** + * @param array $patterns + * @return array + */ + private function normalizePatterns(array $patterns): array + { + $normalized = []; + + foreach ($patterns as $pattern) { + if (!\is_string($pattern)) { + continue; + } + + $pattern = $this->normalizeDirective($pattern); + if ($pattern === '') { + continue; + } + + $normalized[$pattern] = true; + } + + return $normalized; + } + + /** + * @return array + */ + private function extractDirectives(string $message): array + { + $directives = []; + + if (\preg_match_all('/\[[^\]\r\n]+\]/u', $message, $matches) > 0) { + foreach ($matches[0] as $match) { + $directives[] = $this->normalizeDirective($match); + } + } + + foreach (\preg_split("/\r\n|\n|\r/", $message) ?: [] as $line) { + $line = \trim($line); + if ($line === '' || !\str_contains($line, ':')) { + continue; + } + + $directives[] = $this->normalizeDirective($line); + } + + return \array_values(\array_filter(\array_unique($directives))); + } + + private function normalizeDirective(string $value): string + { + $value = \trim($value); + if ($value === '') { + return ''; + } + + $value = (string) \preg_replace('/\s+/u', ' ', $value); + return \mb_strtolower($value); + } } diff --git a/tests/unit/Vcs/Validator/CommitSkipPatternsTest.php b/tests/unit/Vcs/Validator/CommitSkipPatternsTest.php index 3725c643bc..efc88bbf6a 100644 --- a/tests/unit/Vcs/Validator/CommitSkipPatternsTest.php +++ b/tests/unit/Vcs/Validator/CommitSkipPatternsTest.php @@ -20,7 +20,7 @@ class CommitSkipPatternsTest extends TestCase } // ------------------------------------------------------------------------- - // Single pattern — exact substring + // Single pattern — directive match // ------------------------------------------------------------------------- public function testSinglePatternMatchSkips(): void @@ -37,6 +37,7 @@ class CommitSkipPatternsTest extends TestCase $this->assertTrue($validator->isValid('fix: real bug fix')); $this->assertTrue($validator->isValid('feat: add new feature')); $this->assertTrue($validator->isValid('skip deploy without brackets')); + $this->assertTrue($validator->isValid('prefix[skip deploy]suffix')); } // ------------------------------------------------------------------------- @@ -119,17 +120,15 @@ class CommitSkipPatternsTest extends TestCase public function testBlankPatternsInArrayAreIgnored(): void { $validator = new CommitSkipPatterns(['', ' ', '[skip deploy]']); - // empty/whitespace-only patterns must not cause a false positive on empty messages $this->assertTrue($validator->isValid('normal commit message')); - // but the real pattern still works $this->assertFalse($validator->isValid('[skip deploy] docs')); } - public function testPatternAsSubstringOfLongerWord(): void + public function testPatternMustBeStandaloneDirective(): void { - // "skip" is a substring of "skippy" — should NOT accidentally skip $validator = new CommitSkipPatterns(['[skip deploy]']); $this->assertTrue($validator->isValid('skippy the kangaroo')); + $this->assertTrue($validator->isValid('prefix[skip deploy]suffix')); } public function testMultilineCommitMessage(): void @@ -138,4 +137,18 @@ class CommitSkipPatternsTest extends TestCase $msg = "feat: add new stuff\n\nMore detail here.\n\n[skip deploy]"; $this->assertFalse($validator->isValid($msg)); } + + public function testWhitespaceInsideDirectiveIsNormalized(): void + { + $validator = new CommitSkipPatterns([' [skip deploy] ']); + $this->assertFalse($validator->isValid('[skip deploy] docs only')); + $this->assertFalse($validator->isValid('[SKIP DEPLOY] docs only')); + } + + public function testTrailerDirectiveCanSkip(): void + { + $validator = new CommitSkipPatterns(['skip-checks: true']); + $msg = "feat: add new stuff\n\nMore detail here.\n\nskip-checks:true"; + $this->assertFalse($validator->isValid($msg)); + } }