From 58f3eca909cc517d23e272b7de0809343fb6129f Mon Sep 17 00:00:00 2001 From: panicboat Date: Sat, 10 Oct 2026 03:44:17 +0900 Subject: [PATCH] fix: match changed files only under a stack path directory A stack path such as `dystopia/{service}` resolves to a directory, but the dispatcher also matched a changed file whose own path equals the resolved path. `dystopia/README.md` was captured as the service `README.md` and produced a `deploy:README.md` label that the resolver can never turn into a deployment target, because the resolver only walks real directories. Require at least one path segment after the pattern so that only files located under the resolved directory are detected. The match stays path-based so that deleted files keep being detected. Claude-Session: https://claude.ai/code/session_01W3CmNyPA4mB5efonYJRPij Signed-off-by: panicboat --- action-scripts/label-dispatcher/README-ja.md | 2 +- action-scripts/label-dispatcher/README.md | 2 +- action-scripts/shared/entities/pattern_matcher.rb | 5 ++--- .../use_cases/detect_changed_services_spec.rb | 12 ++++++++++++ .../spec/shared/entities/pattern_matcher_spec.rb | 10 ++++++++-- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/action-scripts/label-dispatcher/README-ja.md b/action-scripts/label-dispatcher/README-ja.md index c7b45a5..7315585 100644 --- a/action-scripts/label-dispatcher/README-ja.md +++ b/action-scripts/label-dispatcher/README-ja.md @@ -86,7 +86,7 @@ bundle exec ruby label-dispatcher/bin/dispatcher help_usage ## 変更パスの照合 -変更ファイルを定義された stack のパスに照合し、サービス・環境・任意 placeholder を抽出します。`environments` を持つ stack は定義環境だけを受け付け、パスに環境名がない場合は定義環境ごとに評価します。両方の属性マップがない stack は変更パスから抽出した環境名を使います。`attributes` を持つ stack は `environment: null` の照合として扱います。 +変更ファイルを定義された stack のパスに照合し、サービス・環境・任意 placeholder を抽出します。stack のパスはディレクトリを指すため、そのディレクトリ配下にある変更ファイルだけが一致し、ファイル自身のパスが解決後のパスと等しい場合は対象外です。`environments` を持つ stack は定義環境だけを受け付け、パスに環境名がない場合は定義環境ごとに評価します。両方の属性マップがない stack は変更パスから抽出した環境名を使います。`attributes` を持つ stack は `environment: null` の照合として扱います。 各照合に除外条件を適用し、除外されない照合が一つでもあるサービスを一度だけラベル対象にします。サービス全体のディレクトリを検出対象にする場合は、そのパスも stack に定義します。削除されたファイルも照合するため、ディレクトリの存在は要求しません。 diff --git a/action-scripts/label-dispatcher/README.md b/action-scripts/label-dispatcher/README.md index 2164cf9..25dfae0 100644 --- a/action-scripts/label-dispatcher/README.md +++ b/action-scripts/label-dispatcher/README.md @@ -86,7 +86,7 @@ The dispatcher sets the following environment variables for GitHub Actions: ## Change Matching -Matches changed files against configured stack paths and extracts services, environments, and arbitrary placeholders. A stack with `environments` accepts only its declared names; a path without an environment name is evaluated for each declared environment. A stack with neither attribute map uses the environment captured from the changed path. A stack with `attributes` is evaluated with `environment: null`. +Matches changed files against configured stack paths and extracts services, environments, and arbitrary placeholders. A stack path resolves to a directory, so a changed file matches only when it is located under that directory; a file whose own path equals the resolved path is ignored. A stack with `environments` accepts only its declared names; a path without an environment name is evaluated for each declared environment. A stack with neither attribute map uses the environment captured from the changed path. A stack with `attributes` is evaluated with `environment: null`. Applies exclusion conditions to each match. A service receives one label if at least one match is not excluded. To detect changes throughout a service directory, include that path in a stack definition. Directories need not exist because deleted files are also matched. diff --git a/action-scripts/shared/entities/pattern_matcher.rb b/action-scripts/shared/entities/pattern_matcher.rb index 24f30e9..367e7f2 100644 --- a/action-scripts/shared/entities/pattern_matcher.rb +++ b/action-scripts/shared/entities/pattern_matcher.rb @@ -41,11 +41,10 @@ def self.extract(pattern, path) match_to_hash(regex.match(path), pattern) end - # Like extract, but only requires the pattern to match the prefix of path. - # Path may carry additional "/"-separated segments after the pattern. + # Like extract, but path must carry at least one "/"-separated segment after the pattern. def self.extract_prefix(pattern, path) return nil if pattern.nil? || path.nil? - regex = Regexp.new("\\A#{compile_regex_body(pattern)}(?:/.*)?\\z") + regex = Regexp.new("\\A#{compile_regex_body(pattern)}/.+\\z") match_to_hash(regex.match(path), pattern) end diff --git a/action-scripts/spec/label-dispatcher/use_cases/detect_changed_services_spec.rb b/action-scripts/spec/label-dispatcher/use_cases/detect_changed_services_spec.rb index caba8dd..67302c4 100644 --- a/action-scripts/spec/label-dispatcher/use_cases/detect_changed_services_spec.rb +++ b/action-scripts/spec/label-dispatcher/use_cases/detect_changed_services_spec.rb @@ -63,6 +63,18 @@ def services expect(services).to eq(['api']) end + it 'ignores files placed at the service position' do + config_hash['stacks'] = [{ 'name' => 'container', 'paths' => ['dystopia/{service}'], 'attributes' => {} }] + changed_files.replace(['dystopia/README.md', 'dystopia/api/main.rb']) + expect(services).to eq(['api']) + end + + it 'ignores files placed at the environment position' do + config_hash['stacks'] = [{ 'name' => 'kubernetes', 'paths' => ['dystopia/{service}/kubernetes/overlays/{environment}'] }] + changed_files.replace(['dystopia/web/kubernetes/overlays/README.md', 'dystopia/api/kubernetes/overlays/develop/kustomization.yaml']) + expect(services).to eq(['api']) + end + it 'uses AND within conditions and OR across conditions' do stack['exclude'] = [{ 'team' => 'platform', 'service' => 'demo' }, { 'team' => 'sandbox' }] changed_files.replace(['teams/platform/api/aws/develop/main.tf', 'teams/sandbox/api/aws/develop/main.tf', 'teams/platform/demo/aws/develop/main.tf']) diff --git a/action-scripts/spec/shared/entities/pattern_matcher_spec.rb b/action-scripts/spec/shared/entities/pattern_matcher_spec.rb index 1b38df3..a47d684 100644 --- a/action-scripts/spec/shared/entities/pattern_matcher_spec.rb +++ b/action-scripts/spec/shared/entities/pattern_matcher_spec.rb @@ -129,10 +129,16 @@ expect(result).to eq('service' => 'foo') end - it 'matches when path equals the pattern exactly' do + it 'returns nil when the path equals the pattern exactly' do expect( described_class.extract_prefix('{service}/terragrunt', 'foo/terragrunt') - ).to eq('service' => 'foo') + ).to be_nil + end + + it 'returns nil when the path ends with a separator after the pattern' do + expect( + described_class.extract_prefix('{service}/terragrunt', 'foo/terragrunt/') + ).to be_nil end it 'returns nil when the path is shorter than the pattern' do