fix(cicontext): cascade AND-group variables to later members' if: conditions

Variables set by an earlier AND-group member's variables: block are now
injected into the evaluation context before testing later members' if:
expressions. This matches GitLab CI behaviour where e.g. element 0 sets
DEPLOY=true and element 1 can then check $DEPLOY without requiring the
caller to inject that variable manually.

Previously evalRuleGroup passed the same base vars closure to every
member, so cascaded variables were never visible during if: evaluation
(only in the merged output returned after the group fired).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 23:14:17 +02:00
co-authored by Claude Sonnet 4.6
parent 986162d270
commit 1acce26ff0
4 changed files with 33 additions and 7 deletions
+6
View File
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
This project uses [Semantic Versioning](https://semver.org).
## [0.5.1] - 2026-07-30
### Fixed
- **AND-group variable cascading** — variables set by an earlier AND-group member's `variables:` block are now visible when evaluating later members' `if:` conditions. Previously all members were evaluated with the same base context, so a group like `[{if: "$CI_COMMIT_TAG", variables: {DEPLOY: "true"}}, {if: "$DEPLOY"}]` would never fire because `$DEPLOY` was not yet injected when the second element was tested. This matches GitLab CI's actual behaviour.
## [0.5.0] - 2026-07-30
### Added
+1 -1
View File
@@ -6,7 +6,7 @@
<p align="center">
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License"></a>
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/release-v0.5.0-blue.svg" alt="Release"></a>
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/release-v0.5.1-blue.svg" alt="Release"></a>
</p>
> **Disclaimer:** This tool was built through iterative AI-assisted development with [Claude](https://claude.ai). It is experimental, incomplete, and not intended for production use. Coverage of GitLab CI keywords is best-effort and may lag behind GitLab's evolving spec. Use it at your own discretion — no correctness guarantees are made. Contributions and bug reports are welcome.
+16 -6
View File
@@ -96,21 +96,31 @@ func EvalJob(job model.Job, ctx *Context) JobState {
// All conditions in the group must match for the group to fire.
// Returns (matched, effectiveWhen, mergedVars). When strict=true, unparseable
// if: expressions count as no-match; when false, they count as match (permissive).
//
// Variables defined on an earlier member of the AND-group are cascaded to later
// members when evaluating their if: conditions. This matches GitLab CI behaviour
// where e.g. element 0 sets DEPLOY=true and element 1 can then test $DEPLOY.
func evalRuleGroup(group []model.Rule, vars func(string) string, ctx *Context, strict bool) (bool, string, map[string]string) {
merged := make(map[string]string)
effectiveWhen := ""
for _, rule := range group {
// Cascade: variables accumulated from previous group members are visible
// to this member's if: condition, shadowing the base context.
cascadeVars := func(key string) string {
if v, ok := merged[key]; ok {
return v
}
return vars(key)
}
var ifOk bool
if strict {
ifOk = ruleIfMatchesStrict(rule.If, vars)
ifOk = ruleIfMatchesStrict(rule.If, cascadeVars)
} else {
ifOk = ruleIfMatches(rule.If, vars)
ifOk = ruleIfMatches(rule.If, cascadeVars)
}
if !ifOk || !changesMatch(rule.Changes, ctx) {
return false, "", nil
}
}
merged := make(map[string]string)
effectiveWhen := ""
for _, rule := range group {
for k, v := range ExtractStringVars(rule.Variables) {
merged[k] = v
}
+10
View File
@@ -17,6 +17,16 @@ workflow:
variables:
PIPELINE_NAME: "deploy"
# AND-group where element 0 sets PHASE=build; element 1 checks $PHASE (cascade).
# Without cascading, element 1 would never match because PHASE is not in
# pipeline variables. With cascading, element 1 sees PHASE from element 0.
- - if: "$CI_COMMIT_BRANCH"
variables:
PHASE: build
- if: "$PHASE"
variables:
PIPELINE_NAME: "branch-build"
variables:
DEPLOY: "false"