Compare commits
2
Commits
986162d270
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fcbe9b7f5 | ||
|
|
fba98acb86 |
@@ -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/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
This project uses [Semantic Versioning](https://semver.org).
|
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
|
## [0.5.0] - 2026-07-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License"></a>
|
<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>
|
</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.
|
> **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.
|
||||||
@@ -27,6 +27,35 @@ A local tool to validate and lint `.gitlab-ci.yml` pipelines without needing a G
|
|||||||
|
|
||||||
See [FEATURES.md](FEATURES.md) for the complete feature reference and lint rules table, and [ROADMAP.md](ROADMAP.md) for planned improvements.
|
See [FEATURES.md](FEATURES.md) for the complete feature reference and lint rules table, and [ROADMAP.md](ROADMAP.md) for planned improvements.
|
||||||
|
|
||||||
|
## glint vs glci
|
||||||
|
|
||||||
|
[glci](https://gitlab.com/gitlab-org/ci-cd/runner-tools/glci) is the official GitLab tool for running pipelines locally via Docker/Podman. **glint** takes a different approach: it is a pure static analyser — no container engine, no job execution, no side effects. The two tools are complementary.
|
||||||
|
|
||||||
|
| Feature | glint | glci |
|
||||||
|
|---|:---:|:---:|
|
||||||
|
| **Runs jobs in Docker/Podman** | — | ✓ |
|
||||||
|
| **Container engine required** | — | ✓ |
|
||||||
|
| **Pipeline linting (structured rules)** | ✓ 49 rules | basic |
|
||||||
|
| **`rules:if:` / `workflow:` evaluation** | ✓ static | ✓ dynamic |
|
||||||
|
| **`extends:` resolution** | ✓ | ✓ |
|
||||||
|
| **`include:` resolution** (local, HTTPS, project, component) | ✓ | ✓ |
|
||||||
|
| **Render merged YAML** (`glint render`) | ✓ | — |
|
||||||
|
| **Context simulation** (`--branch`, `--tag`, `--source`, `--var`) | ✓ static | ✓ dynamic |
|
||||||
|
| **Multi-context comparison table** | ✓ | — |
|
||||||
|
| **Graph visualization** (terminal tree, Mermaid, SVG/PNG) | ✓ | — |
|
||||||
|
| **LSP server / IDE diagnostics** | ✓ | — |
|
||||||
|
| **Output formats** (SARIF, JUnit, GitHub annotations) | ✓ | — |
|
||||||
|
| **Artifact management** (real artifacts from execution) | — | ✓ |
|
||||||
|
| **Job log streaming** | — | ✓ |
|
||||||
|
| **Docker-in-Docker / buildx** | — | ✓ |
|
||||||
|
| **Embedded OCI registry** | — | ✓ |
|
||||||
|
| **GitLab Pages preview** | — | ✓ |
|
||||||
|
| **Interactive TUI** | — | ✓ |
|
||||||
|
| **Watch mode** (auto-rerun on file change) | — | ✓ |
|
||||||
|
| **Pre-built binary, zero runtime deps** | ✓ | — |
|
||||||
|
|
||||||
|
Use **glint** when you want fast, offline, dependency-free pipeline validation — in pre-commit hooks, CI itself, or your editor. Use **glci** when you need to actually execute jobs and verify their output locally.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
See [INSTALL.md](INSTALL.md) for all options: pre-built binaries (Linux amd64/arm64, macOS Intel/Apple Silicon, Windows), Homebrew tap, and building from source.
|
See [INSTALL.md](INSTALL.md) for all options: pre-built binaries (Linux amd64/arm64, macOS Intel/Apple Silicon, Windows), Homebrew tap, and building from source.
|
||||||
|
|||||||
@@ -96,21 +96,31 @@ func EvalJob(job model.Job, ctx *Context) JobState {
|
|||||||
// All conditions in the group must match for the group to fire.
|
// All conditions in the group must match for the group to fire.
|
||||||
// Returns (matched, effectiveWhen, mergedVars). When strict=true, unparseable
|
// Returns (matched, effectiveWhen, mergedVars). When strict=true, unparseable
|
||||||
// if: expressions count as no-match; when false, they count as match (permissive).
|
// 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) {
|
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 {
|
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
|
var ifOk bool
|
||||||
if strict {
|
if strict {
|
||||||
ifOk = ruleIfMatchesStrict(rule.If, vars)
|
ifOk = ruleIfMatchesStrict(rule.If, cascadeVars)
|
||||||
} else {
|
} else {
|
||||||
ifOk = ruleIfMatches(rule.If, vars)
|
ifOk = ruleIfMatches(rule.If, cascadeVars)
|
||||||
}
|
}
|
||||||
if !ifOk || !changesMatch(rule.Changes, ctx) {
|
if !ifOk || !changesMatch(rule.Changes, ctx) {
|
||||||
return false, "", nil
|
return false, "", nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
merged := make(map[string]string)
|
|
||||||
effectiveWhen := ""
|
|
||||||
for _, rule := range group {
|
|
||||||
for k, v := range ExtractStringVars(rule.Variables) {
|
for k, v := range ExtractStringVars(rule.Variables) {
|
||||||
merged[k] = v
|
merged[k] = v
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+10
@@ -17,6 +17,16 @@ workflow:
|
|||||||
variables:
|
variables:
|
||||||
PIPELINE_NAME: "deploy"
|
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:
|
variables:
|
||||||
DEPLOY: "false"
|
DEPLOY: "false"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user