mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description Prevent unresolved Git merge-conflict markers from ever being merged again. `CHANGELOG.md` collected literal `<<<<<<<` / `=======` / `>>>>>>>` lines from **two** separate merged PRs (removed in #1497) and nothing caught them: no `check-merge-conflict` hook, no workflow runs `pre-commit`, and `ruff`/`mypy`/`pytest` do not parse Markdown. This adds two layers: 1. **Dedicated `merge-conflicts` workflow** (`.github/workflows/merge-conflicts.yml`) — runs on every PR/push, greps all tracked files for conflict markers, and fails with the offending locations. It is a **standalone workflow on purpose**: `ci.yml` sets `on.pull_request.paths-ignore: ['**/*.md', ...]`, so a Markdown/CHANGELOG-only PR skips `ci.yml` entirely — and the markers this guard exists to catch landed in `CHANGELOG.md`. The new workflow has no `paths-ignore`, so it always runs. (Thanks to the automated review for catching that an earlier revision put the job inside `ci.yml`, which would have reproduced the gap.) 2. **`check-merge-conflict` pre-commit hook** (`--assume-in-merge`) — a local early-catch for devs who install hooks. The workflow is the actual enforcement since hooks are opt-in. ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - `.github/workflows/merge-conflicts.yml` (new): a single `merge-conflicts` job, **no `paths-ignore`**, that runs `git grep -nI -E '^(<{7}|>{7}|\|{7})( |$)'` over all tracked files and fails if any match. Matches conflict start/end (and diff3 `|||||||`) markers — every real conflict has these — while deliberately not matching a bare `=======`, to avoid false positives on Markdown setext headers. - `.pre-commit-config.yaml`: add `pre-commit/pre-commit-hooks` `check-merge-conflict` with `--assume-in-merge`. ## Testing - [x] Linting passes (`actionlint`) - [x] Manual testing performed - [ ] Unit tests pass (`pytest`) — N/A, CI-config + pre-commit change (no product code) - [ ] Type checking passes (`mypy headroom`) — N/A - [ ] New tests added for new functionality — N/A (the guard is itself the test; verified below) ### Test Output ```text # The guard's command catches real markers (run against the pre-#1497 tree that still had them): $ git grep -nI -E '^(<{7}|>{7}|\|{7})( |$)' <markered-tree> -- . CHANGELOG.md:11:<<<<<<< pr/503-proactive-expansion-xml-tag CHANGELOG.md:19:>>>>>>> main CHANGELOG.md:48:<<<<<<< fix/gemini-offload CHANGELOG.md:53:>>>>>>> main # -> exit 0 (matches found) => workflow step exits 1 (fails) # And passes on a clean tree (this branch): $ git grep -nI -E '^(<{7}|>{7}|\|{7})( |$)' HEAD -- . # (no output) -> exit 1 (no matches) => prints "No merge-conflict markers found." and exits 0 $ actionlint .github/workflows/merge-conflicts.yml .github/workflows/ci.yml -> (no output) exit 0 $ commitlint --from <base> --to HEAD -> 0 problems, 0 warnings # Both workflow YAMLs parse; merge-conflicts.yml has no `paths-ignore`, so it runs on Markdown-only PRs. ``` ## Real Behavior Proof - Environment: Linux; the repo at this branch (`ci/guard-merge-conflict-markers`), with `actionlint 1.7.7` and `commitlint` (`@commitlint/config-conventional`). The "real behavior" under test is the workflow's grep command and the YAML/trigger config — exercised directly, not mocked. - Exact command / steps: ran the workflow's exact command (`git grep -nI -E '^(<{7}|>{7}|\|{7})( |$)' -- .`) against a tree that contains markers and against this clean branch; ran `actionlint` on both workflow files; parsed `merge-conflicts.yml` and confirmed its `pull_request` trigger has no `paths-ignore` (the fix for the Markdown-only gap). - Observed result: against the markered tree the command matched the conflict start/end lines and exited 0 → the job exits 1 (red); against this branch it matched nothing and exited 1 → the job prints "No merge-conflict markers found." and exits 0 (green). `actionlint` reported no problems on either workflow, and `merge-conflicts.yml` is confirmed to omit `paths-ignore`, so it runs on CHANGELOG-only PRs that `ci.yml` skips. - Not tested: I did not drive the `check-merge-conflict` pre-commit hook through a real in-progress merge (it is a standard upstream hook); the gated CI run (fork-PR approval) was not executed — the guard logic and triggers are verified above. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation (inline comments explain the workflow + hook) - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works (the guard is self-verifying; see Real Behavior Proof) - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable (N/A — CI tooling, no user-facing change) ## Additional Notes - **Supply chain:** the only new dependency is the `pre-commit/pre-commit-hooks` repo pinned at `v5.0.0`. It is the canonical pre-commit hooks collection maintained by the pre-commit author (Anthony Sottile), is dev-only (runs solely inside pre-commit), contains no native code, and has no runtime/install-time footprint. The repo already trusts `astral-sh/ruff-pre-commit` and `pre-commit/mirrors-mypy` the same way. - The workflow adds ~10s per run and needs no secrets, network, or build. Rebased onto current `main` after #1497 merged, so this is now a clean single commit (the changelog cleanup is already on `main`).
38 lines
1.3 KiB
YAML
38 lines
1.3 KiB
YAML
repos:
|
|
- repo: local
|
|
hooks:
|
|
- id: sync-plugin-versions
|
|
name: Sync plugin versions
|
|
entry: python3 scripts/sync-plugin-versions.py
|
|
language: system
|
|
pass_filenames: false
|
|
always_run: true
|
|
- id: commitlint
|
|
name: Commitlint
|
|
entry: bash -lc 'npx --yes --package=@commitlint/cli --package=@commitlint/config-conventional -- commitlint --edit "$1" --config .commitlintrc.json' --
|
|
language: system
|
|
stages: [commit-msg]
|
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
rev: v5.0.0
|
|
hooks:
|
|
- id: check-merge-conflict
|
|
# Catch markers even outside an in-progress merge (e.g. committing a
|
|
# botched conflict resolution from a rebase). CI re-checks this
|
|
# unconditionally, so installing hooks is not required for enforcement.
|
|
args: [--assume-in-merge]
|
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
rev: v0.9.4
|
|
hooks:
|
|
- id: ruff
|
|
args: [--fix]
|
|
exclude: ^experiments/
|
|
- id: ruff-format
|
|
exclude: ^experiments/
|
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
rev: v1.14.1
|
|
hooks:
|
|
- id: mypy
|
|
args: [--ignore-missing-imports]
|
|
exclude: ^experiments/
|
|
pass_filenames: false
|
|
entry: mypy headroom
|