Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# The catalog files here are written by a tool and compared back byte for byte
# -- `git diff --exit-code -- index.yaml`, and the watcher's committed baseline.
# A checkout that hands a generator CRLF makes those comparisons fail on line
# endings alone, on a tree where nothing actually changed. Keep them LF in the
# working tree on every platform, so regenerating anywhere reads the same.
*.json text eol=lf
*.yaml text eol=lf
*.md text eol=lf
*.lua text eol=lf
*.py text eol=lf
*.sh text eol=lf
64 changes: 59 additions & 5 deletions .github/workflows/watch-upstream-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ on:
workflow_dispatch:

permissions:
contents: write # commit the updated baseline
contents: write # push the baseline branch
issues: write # open tracking issues
pull-requests: write # propose the updated baseline
actions: write # start validate on the proposal (see that step)

concurrency:
group: watch-upstream-docs
Expand Down Expand Up @@ -66,15 +68,67 @@ jobs:
gh issue create --title "$title" --body-file "$RUNNER_TEMP/body.md" --label "$label"
done

- name: Commit the updated baseline
# main is protected: a direct push is refused for the required checks it
# has not run, so the baseline could never advance and every week's run
# ended red. The baseline moves the same way anything else moves here --
# as a pull request. One branch, force-pushed: the baseline is a whole
# snapshot, so the newest proposal replaces the previous one rather than
# stacking on it.
- name: Propose the updated baseline
env:
GH_TOKEN: ${{ github.token }}
BRANCH: upstream-docs-baseline
BOT_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
run: |
set -euo pipefail
if git diff --quiet -- upstream-docs-state.json; then
echo "baseline unchanged"
exit 0
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config user.email "$BOT_EMAIL"
git add upstream-docs-state.json
git commit -m "chore(upstream-docs): update watched-document baseline [skip ci]"
git push
git commit -m "chore(upstream-docs): update watched-document baseline" \
-m "Signed-off-by: github-actions[bot] <$BOT_EMAIL>"
git push --force origin "HEAD:refs/heads/$BRANCH"

existing="$(gh pr list --head "$BRANCH" --state open --json number \
--jq '.[0].number // empty')"
if [ -n "$existing" ]; then
echo "refreshed the open proposal: #$existing"
else
cat > "$RUNNER_TEMP/pr-body.md" <<'BODY'
A watched upstream document moved, or a fetch failure changed its
count, so `upstream-docs-state.json` no longer describes what the
watcher sees. Merging this makes the new observation the baseline the
next run compares against.

Read the `upstream-doc` issues this run opened before merging: this
accepts the new bytes as normal, and on its own says nothing about
whether the registers a driver decodes by have moved.

**The required checks do not start on their own here.** A pull request
opened with `GITHUB_TOKEN` starts no workflow run, so this one arrives
with an empty status rollup. Close and reopen it to run them. The
`validate` run dispatched against the branch head is a verdict you can
read in the meantime -- GitHub does not count it toward the rollup.

Opened by the `watch-upstream-docs` workflow.
BODY
gh pr create --base main --head "$BRANCH" \
--title "chore(upstream-docs): update watched-document baseline" \
--body-file "$RUNNER_TEMP/pr-body.md"
fi

# A pull request opened with GITHUB_TOKEN starts no workflow run, so
# nothing would run against this proposal at all. workflow_dispatch is
# the documented exception to that rule, so this asks validate for a
# verdict on the head the proposal carries -- which is worth having
# before a maintainer decides to spend attention on it.
#
# It does NOT turn the required checks green: a commit whose only
# check suite came from a dispatch has no status rollup, so branch
# protection still reads the proposal as blocked. Reopening it is what
# starts the checks that count. Measured on #67, not assumed.
gh workflow run validate.yml --ref "$BRANCH"
18 changes: 18 additions & 0 deletions tests/test_upstream_docs_watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,21 @@ def test_state_file_round_trips(tmp_path):
write_state(path, docs)
assert load_state(path) == docs
assert load_state(tmp_path / "missing.json") == {}


# The runner diffs the baseline it rewrites against the committed one and
# proposes the difference. Written in the platform's line ending, a baseline
# authored on Windows is 31 changed lines to Linux on a week when no watched
# document moved -- a pull request that says nothing. Both halves are pinned:
# what the tool writes, and what is committed.


def test_state_file_is_written_with_lf_on_every_platform(tmp_path):
docs, _ = compute_updates({}, CURRENT, ok(SHA_A), "t0")
path = tmp_path / "upstream-docs-state.json"
write_state(path, docs)
assert b"\r" not in path.read_bytes()


def test_committed_baseline_is_lf():
assert b"\r" not in (ROOT / "upstream-docs-state.json").read_bytes()
7 changes: 6 additions & 1 deletion tools/check_upstream_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,12 @@ def write_state(path: Path, docs: dict[str, dict]) -> None:
"schema_version": SCHEMA_VERSION,
"docs": {key: docs[key] for key in sorted(docs)},
}
path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
# Without newline="\n", text mode writes the platform's line ending. A
# baseline written on Windows then reads as a whole-file change to the
# Linux runner that rewrites it, and the watcher proposes 31 changed lines
# on a week when no watched document moved.
path.write_text(json.dumps(payload, indent=2) + "\n",
encoding="utf-8", newline="\n")


# ---- CLI ----------------------------------------------------------------
Expand Down
62 changes: 31 additions & 31 deletions upstream-docs-state.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
{
"schema_version": "sourceful.upstream-docs-state/v1",
"docs": {
"myuplink::https://www.nibe.eu/webdav/files/myuplink_changelog/nibe-n.pdf": {
"driver": "myuplink",
"url": "https://www.nibe.eu/webdav/files/myuplink_changelog/nibe-n.pdf",
"title": "NIBE myUplink register/parameter changelog (nibe-n.pdf)",
"kind": "changelog",
"url_stability": "stable",
"sha256": "fa1186981a020f6c262cf35abd50fa447343cfaa16a4eedb018cb2525c00f221",
"content_length": 850716,
"first_seen": "2026-07-29T15:08:58+00:00",
"last_changed": "2026-07-29T15:08:58+00:00",
"consecutive_failures": 0,
"last_error": null
},
"nibe_local::https://www.nibe.eu/webdav/files/myuplink_changelog/nibe-n.pdf": {
"driver": "nibe_local",
"url": "https://www.nibe.eu/webdav/files/myuplink_changelog/nibe-n.pdf",
"title": "NIBE S-series myUplink register/parameter changelog (nibe-n.pdf)",
"kind": "changelog",
"url_stability": "stable",
"sha256": "fa1186981a020f6c262cf35abd50fa447343cfaa16a4eedb018cb2525c00f221",
"content_length": 850716,
"first_seen": "2026-07-29T15:08:58+00:00",
"last_changed": "2026-07-29T15:08:58+00:00",
"consecutive_failures": 0,
"last_error": null
}
}
}
{
"schema_version": "sourceful.upstream-docs-state/v1",
"docs": {
"myuplink::https://www.nibe.eu/webdav/files/myuplink_changelog/nibe-n.pdf": {
"driver": "myuplink",
"url": "https://www.nibe.eu/webdav/files/myuplink_changelog/nibe-n.pdf",
"title": "NIBE myUplink register/parameter changelog (nibe-n.pdf)",
"kind": "changelog",
"url_stability": "stable",
"sha256": "fa1186981a020f6c262cf35abd50fa447343cfaa16a4eedb018cb2525c00f221",
"content_length": 850716,
"first_seen": "2026-07-29T15:08:58+00:00",
"last_changed": "2026-07-29T15:08:58+00:00",
"consecutive_failures": 0,
"last_error": null
},
"nibe_local::https://www.nibe.eu/webdav/files/myuplink_changelog/nibe-n.pdf": {
"driver": "nibe_local",
"url": "https://www.nibe.eu/webdav/files/myuplink_changelog/nibe-n.pdf",
"title": "NIBE S-series myUplink register/parameter changelog (nibe-n.pdf)",
"kind": "changelog",
"url_stability": "stable",
"sha256": "fa1186981a020f6c262cf35abd50fa447343cfaa16a4eedb018cb2525c00f221",
"content_length": 850716,
"first_seen": "2026-07-29T15:08:58+00:00",
"last_changed": "2026-07-29T15:08:58+00:00",
"consecutive_failures": 0,
"last_error": null
}
}
}
Loading