- Notifications
You must be signed in to change notification settings - Fork 0
Adopt the Fleet's text=auto .gitattributes, Retire check-eol-pins.py#98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
c1e58be
Adopt the Fleet's text=auto .gitattributes and Retire check-eol-pins.py
ptr727 06bd3bc
Fix Broken Link and Orphaned Reference From the check-eol-pins.py Del…
ptr727 3a4522d
Renormalize CRLF Theme Files and Fix a Stale Gate Count
ptr727 af16a68
Name the checks/ Gates Precisely Instead of by Loose Grouping
ptr727 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,58 +1,17 @@ | ||
| # Git stays passive on line endings by default. | ||
| # The .editorconfig end_of_line rules guide what the editor writes. | ||
| # The pins below are git's own enforcement for execution-sensitive files. | ||
| # - git config --global core.autocrlf false | ||
| # - git add --renormalize . | ||
| # - git ls-files --eol | ||
| * -text | ||
| # Normalize every detected text file to LF in the index and on checkout. | ||
| # `text=auto` leaves binary files byte-preserved. | ||
| * text=auto eol=lf | ||
qodo-code-review[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| # This repo declares LF, so .editorconfig already defaults every file to LF. | ||
| # That does not make the pins below redundant. | ||
| # `* -text` keeps git passive, so .editorconfig binds only the editor. | ||
| # An explicit `text eol=lf` is what binds git on checkout and on renormalize. | ||
| # Execution-sensitive files therefore get both. | ||
| # Windows command scripts require CRLF. | ||
| *.bat text eol=crlf | ||
| *.cmd text eol=crlf | ||
| # A CRLF shebang breaks execution, so scripts stay LF regardless of the default. | ||
| *.sh text eol=lf | ||
| # Shebang-executable files carrying no extension, which every rule above matches by | ||
| # extension and therefore misses. `ops/vps-backup-pull` is run by systemd on the backup | ||
| # host. A named line per file, as with the Python rules below, and `check-eol-pins.py` | ||
| # fails if a tracked shebang file ever lands without one. | ||
| ops/vps-backup-pull text eol=lf | ||
| # Shebang-executable Python, where a CRLF on line one is a broken interpreter line rather | ||
| # than a cosmetic difference. `capture/` is pinned whole, because everything there is a | ||
| # script; under `checks/` the two executables are named, because that directory also holds | ||
| # lists and fixtures. Neither is a blanket `*.py` rule, so a future non-executable module | ||
| # elsewhere is not swept in by accident, and a new executable needs a line here. | ||
| capture/*.py text eol=lf | ||
| checks/check-url-parity.py text eol=lf | ||
| checks/check-env-docs.py text eol=lf | ||
| checks/check-eol-pins.py text eol=lf | ||
| # Caddy config is parsed line by line by a daemon rather than by a shell, and a CRLF file | ||
| # is rejected or silently mis-parsed. Both files are named: the bundle's `Caddyfile`, and | ||
| # the bootstrap that is installed into the container's config directory and is the only | ||
| # Caddy file outside the release. | ||
| # | ||
| # The restricted `authorized_keys` and the forced-command deploy shell were pinned here | ||
| # too, and this repository has never carried either: they live on the server, described in | ||
| # OPERATIONS.md "Server Hardening". A pin binds nothing for a file that does not exist, | ||
| # and the comment claiming to cover "the extensionless shebang script" is what let the | ||
| # real one, `ops/vps-backup-pull`, sit unpinned above. `check-eol-pins.py` now fails on a | ||
| # pattern matching no tracked file, so neither can come back silently. | ||
| deploy/Caddyfile text eol=lf | ||
| deploy/bootstrap.Caddyfile text eol=lf | ||
| # Caddy map files are tabular data read by `map` directives. | ||
| # They stay LF for the same reason as the Caddyfile. | ||
| deploy/maps/*.map text eol=lf | ||
| # Repo-specific below: this repo's own additions layered on the fleet baseline above. | ||
| # Media is 566 MB of images, video, and archives across two trees. | ||
| # `* -text` already stops git normalizing them. | ||
| # `binary` additionally suppresses textual diff and merge. | ||
| # That keeps a `git log -p` or a bad merge from rendering a 4 MB PNG as text. | ||
| # `binary` suppresses normalization plus textual diff and merge, so a `git log -p` or a | ||
| # bad merge never renders a 4 MB PNG as text, and text=auto's own heuristic is never | ||
| # consulted for these paths regardless of what it would guess. | ||
| # Extensions present are png, jpg, gif, webp, mov, zip, and ico, with nothing textual. | ||
| static/media/** binary | ||
| static/external/** binary | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -12,7 +12,7 @@ The site is built, gated in CI, and deployed to staging by pipeline. It is not y | ||
| | Media integrity | closed for the files themselves. The conversion's only loss was 19 images, inside five galleries, restored from the capture, and the 98 files with no page links are adjudicated rather than unknown, with `ORPHANED_MEDIA` failing if that count moves either way. How media was *presented* was a separate loss, found later and now gated | | ||
| | URL contract | done. 328 render, 917 redirect, 778 legacy image URLs, all gated. **Two gates read different subsets of that, which is why two totals appear in this file**: the build gate proves all three against files on disk, and the live check requests 328 + 917 + 8 curated media = **1,253** against a running server, since proving 778 images over the wire on every deploy buys nothing the build gate has not already proven | | ||
| | Deploy shape | done. Proven on two local mirrors and on the VPS, by hand and by pipeline | | ||
| | CI workflows | green. Validation runs on every pull request and feeds the required check. Three gates live in `checks/`: the URL contract, the environment-docs pair, and the line-ending pins | | ||
| | CI workflows | green. Validation runs on every pull request and feeds the required check. `checks/` holds the URL contract's build gate (`check-url-parity.py`, live gate `check-live-urls.sh` is deploy-time only) plus `check-env-docs.py`, which checks `ENVIRONMENT.md` against the tree in both directions | | ||
| | Branches | converged 2026-08-09. [#68][pr-68] promoted `develop` to `main` as a merge commit, `main` carries every commit on `develop`, and the content diff is empty. **The merge itself published nothing**, which is the release model behaving as specified: a human merge never auto-publishes, and the releases named two rows down were cut afterwards by explicit dispatch | | ||
| | GitHub repo | public, both rulesets active, `configure.sh check` exits 0 | | ||
| | Release pipeline | proven end to end and cut from both branches 2026-08-09. `1.0.54` is the newest release, from `main` and marked Latest, and `1.0.54-gcd9285a9fe` the newest prerelease, from `develop`. Each tag is pinned to its own branch tip, and the clean-versus-suffixed classification is the gate working rather than a coincidence | | ||
| @@ -57,7 +57,7 @@ The site is built, gated in CI, and deployed to staging by pipeline. It is not y | ||
| - **This reverses a deliberate decision, so the comment above the job has to change with the code.** It currently reads "Staging deploys from any ref, since proving a branch before it merges is what staging is for", which is the argument for the present behavior and would be left contradicting the gate. Whoever makes the change states the new reason instead: staging is the rehearsal for what `develop` will promote, so a deploy from a feature branch proves something that is not what ships. Requested by the maintainer 2026-08-09, after a staging deploy was dispatched from `main` here. | ||
| - Keep the existing comparison style, against the full `github.ref` rather than `ref_name`, for the reason already recorded there: a tag and a branch can share a short name, so a tag called `develop` would satisfy a `ref_name` test while pointing at an arbitrary commit. The new arm inherits that hazard exactly and is not a special case. | ||
| - Add the weekly non-blocking external-link-check workflow, which is the one gate that cannot be blocking because it fails on other people's outages. | ||
| - **[`checks/README.md`](./checks/README.md) names one of the three gates in its own directory.** It is written as the URL contract's document, titled `URL Parity Gate`, and `check-env-docs.py` and `check-eol-pins.py` appear in it nowhere. Each carries its reasoning in its own docstring, which is where the detail belongs, but [#66][pr-66] made a directory's README the authority on that directory and this one currently describes a subset of it without saying so. The small fix is a section naming what else lives there and what each gate answers; the alternative is to state in the README that it is scoped to the contract alone. Either resolves it, and the present state is the one that reads as complete while not being. | ||
| - **[`checks/README.md`](./checks/README.md) names one of the two gates in its own directory.** It is written as the URL contract's document, titled `URL Parity Gate`, and `check-env-docs.py` appears in it nowhere. It carries its reasoning in its own docstring, which is where the detail belongs, but [#66][pr-66] made a directory's README the authority on that directory and this one currently describes a subset of it without saying so. The small fix is a section naming what else lives there and what it answers; the alternative is to state in the README that it is scoped to the contract alone. Either resolves it, and the present state is the one that reads as complete while not being. | ||
coderabbitai[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. qodo-code-review[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| - Decommission WordPress.com only after **30 clean days**, and downgrade to free rather than deleting, which keeps the media reachable as a safety net and preserves the ability to re-export. Do not start sooner: the conversion fetched media over HTTP from the live site. | ||
| ## Recurring operations | ||
| @@ -78,7 +78,7 @@ The site is built, gated in CI, and deployed to staging by pipeline. It is not y | ||
| ## Owed to the hub | ||
| **One thing, and it is conditional.** [ProjectTemplate#633][issue-633] reports that the hub's `repo_gate.py --check eol` compares `.gitattributes` and `.editorconfig` to each other and never to the tree, and offers to port this repo's [`checks/check-eol-pins.py`](./checks/check-eol-pins.py) into the hub's `scripts/` with tests in the `test_repo_gate.py` style. That offer is owed only if the hub wants that shape. **Do not start it before the hub rules**, because the open design question changes the code: the dead-pattern direction cannot be lifted unmodified, since a carried baseline forward-declares pins for files its consumers will have and the template itself does not, which is correct there and a finding anywhere else. | ||
| **The one open item here closed itself.** [ProjectTemplate#633][issue-633] reported that the hub's `repo_gate.py --check eol` compares `.gitattributes` and `.editorconfig` to each other and never to the tree, citing two shapes this repo's own `checks/check-eol-pins.py` had caught. Rather than porting this repo's script, the hub built the equivalent directly into `repo_gate.py` as `eol-coverage` (`unpinned` plus `dead`), closing #633. This repo has since retired `check-eol-pins.py` and switched `.gitattributes` to the fleet-standard `text=auto eol=lf`, which structurally removes the bug class the script existed to catch (a tracked file with no pin keeping whatever encoding it arrived with, only possible when git stays passive by default) rather than merely fixing an instance of it. | ||
| The spec update this repo owed the hub has landed: [ProjectTemplate#560][hub-type-pr] authored the `hugo` type, the `self-hosted` target, the `deploy-ssh` mechanism, guarantees D4.6 and D5.6, and a reference leaf pair, all measured from what this repo actually runs rather than from the prediction the intake carried. [#456][hub-issue] and [#558][hub-spec-issue] are closed with it. | ||
| @@ -124,7 +124,7 @@ Three findings are open at the hub, recorded here rather than only in the issues | ||
| | [#550][issue-550] | Nothing detects a repo missing from the registry, which is how this repo stayed invisible. Three other repos are still absent. | | ||
| | [#552][issue-552] | The audit flags any carried `AGENTS.md` naming the template repo, and the byte-locked `Fleet Bootstrap` section names it. Carrying the canonical correctly cannot pass, and it is the one finding the current run cannot clear. | | ||
| | [#597][issue-597] | Filed from here, after a mandatory pre-merge gate in `OPERATIONS.md` was skipped on [#40][pr-40]. The ruling is that a verification a runner cannot perform needs a declared destination, not a better per-repo pointer. Answered by [#598][pr-598], which this repo owes work against once it is ground truth. | | ||
| | [#633][issue-633] | Filed from here, 2026-08-09. The hub's `eol` gate is document-to-document, so it reported `0 issue(s)` against the exact tree carrying both defects [#69][pr-69] fixed. Where a repo sets `[*] end_of_line = lf`, as this one does, that gate is vacuously satisfied by any pin at all, including one naming a file that does not exist. Open on the hub's side; the conditional work it may ask of this repo is under **Owed to the hub**. | | ||
| | [#633][issue-633] | Filed from here, 2026-08-09, closed on the hub's side via `eol-coverage` (see **Owed to the hub**). Resolved rather than open; kept in this table as the record of what was filed and why. | | ||
| **What [#598][pr-598] will ask of this repo, once it is ground truth.** It is merged to the hub's `develop` and not to `main`, so it binds nothing yet, per the trap below about reading `main` as ground truth. It makes `Local Verification` a sixth declared `OPERATIONS.md` heading, leading the file as the only pre-merge one. This repo's `OPERATIONS.md` carries **13 level-two headings and matches none of the five declared today**, so the work is a rename and reorder rather than new prose: `Local Verification Before a Pull Request` becomes `Local Verification`, and `Backup and Restore` is one word from the declared `Backup and Recovery`. Both are near-misses rather than absences, which is the shape a heading check will mostly find in a repo that wrote its operational document before the spec declared headings. Do not start until the hub promotes it. | ||
| @@ -171,8 +171,8 @@ Each of these was hit or nearly hit, and each is cheap to re-trip. | ||
| - **A Copilot review hides findings in the review body, where the thread API cannot see them.** The `reviewThreads` query returns line threads only, so a review carrying `Suppressed comments (N)` in a `<details>` block reports zero unresolved while real findings sit unread. Read the review body itself, not just the threads, before calling a review loop finished. | ||
| - **`gh pr merge --delete-branch` on a `develop -> main` promotion deletes `develop`.** Use a plain `gh pr merge --merge`. | ||
| - **A review that exists on the head is not a finished review.** On [#68][pr-68] `pr_review.py wait` returned after one second with `review_on_head=yes unresolved=0 suppressed=0`, an independent thread poll agreed at zero, and the pull request was reported green. Copilot posted a third finding *after* both reads, and the maintainer found it. The re-request had matched a review still being written, so every query was correct and every query was early. Wait and re-sweep all three surfaces, line threads, issue-level comments and the review bodies, before calling a loop finished. A very short `waited=` is the tell. | ||
| - **A rule naming a target that does not exist reads as coverage.** `.gitattributes` pinned `deploy/blog-deploy-shell` and `deploy/authorized_keys`, neither ever tracked in this repo, and the comment above them claimed the extensionless-shebang case was handled. It was not, and the one real instance sat unpinned twenty lines up, invisible precisely because the file looked thorough. [`checks/check-eol-pins.py`](./checks/check-eol-pins.py) gates this file now, in both directions. The generalisation is not gated anywhere: a config that names a path, a job, or a host that is absent will read as protection for as long as nobody checks. | ||
| - **A gate is only as good as the matcher underneath it, and a wrong matcher fails quietly.** `check-eol-pins.py` shipped with two defects in one function, `git ls-files` pathspec not sharing gitattributes glob semantics and then a leading `/` being stripped before anchoring was decided. Both were found by review rather than by the gate, and each would have made it report a confident wrong answer. [#70][pr-70] fixed them and it now checks its own matching against `git check-attr` on every run, which is the general move: where a check predicts what a tool will do, compare it against the tool rather than against the documentation. | ||
| - **A rule naming a target that does not exist reads as coverage.** `.gitattributes` pinned `deploy/blog-deploy-shell` and `deploy/authorized_keys`, neither ever tracked in this repo, and the comment above them claimed the extensionless-shebang case was handled. It was not, and the one real instance sat unpinned twenty lines up, invisible precisely because the file looked thorough. `checks/check-eol-pins.py` gated this file in both directions until this repo retired it and the pin list it audited, per **Owed to the hub**. The generalisation is not gated anywhere: a config that names a path, a job, or a host that is absent will read as protection for as long as nobody checks. | ||
| - **A gate is only as good as the matcher underneath it, and a wrong matcher fails quietly.** `check-eol-pins.py` shipped with two defects in one function, `git ls-files` pathspec not sharing gitattributes glob semantics and then a leading `/` being stripped before anchoring was decided. Both were found by review rather than by the gate, and each would have made it report a confident wrong answer. [#70][pr-70] fixed them, and the fixed version checked its own matching against `git check-attr` on every run for as long as the script existed, which is the general move: where a check predicts what a tool will do, compare it against the tool rather than against the documentation. | ||
| ## Reference | ||
| @@ -211,7 +211,6 @@ The deploy root is deliberately absent from this table. The rsync destination is | ||
| [pr-65]: https://github.com/ptr727/Blog/pull/65 | ||
| [pr-66]: https://github.com/ptr727/Blog/pull/66 | ||
| [pr-68]: https://github.com/ptr727/Blog/pull/68 | ||
| [pr-69]: https://github.com/ptr727/Blog/pull/69 | ||
| [pr-70]: https://github.com/ptr727/Blog/pull/70 | ||
| [pr-75]: https://github.com/ptr727/Blog/pull/75 | ||
| [pr-76]: https://github.com/ptr727/Blog/pull/76 | ||
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.