From 822447d6851f248e5d31612de52f684528841b1a Mon Sep 17 00:00:00 2001 From: rgdevment Date: Mon, 5 Oct 2026 20:15:10 -0300 Subject: [PATCH 1/3] chore(rules): the conventions no longer police the variety of Spanish --- scripts/rules.sh | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/scripts/rules.sh b/scripts/rules.sh index da58e788..7661fb33 100755 --- a/scripts/rules.sh +++ b/scripts/rules.sh @@ -62,24 +62,6 @@ written_in_english() { && went_well "no Spanish identifiers" } -neutral_spanish() { - local voseo='\b(v[o]s|ten[é]s|quer[é]s|pod[é]s|and[á]|mir[á]|hac[é]|ch[e])\b' - local peninsular='\b(fichero|ficheros|ordenador|pulsa|pulsar|pulsando)\b' - # a path that is not there is an error to grep, so only what exists is handed over - local -a where=(crates README.md) - [ -d docs ] && where+=(docs) - grep -rniE "$voseo" "${where[@]}" --include='*.rs' --include='*.md' - found_nothing \ - "neutral Spanish, no voseo" $? \ - "the Spanish could not be looked through for voseo" \ - && went_well "no voseo anywhere" - grep -rniE "$peninsular" "${where[@]}" --include='*.rs' --include='*.md' - found_nothing \ - "neutral Spanish: archivo, computador, presiona" $? \ - "the Spanish could not be looked through for peninsular words" \ - && went_well "no peninsular Spanish in what a person reads" -} - no_comments_in_the_code() { grep -rnE '[/]{2}' crates app/src-tauri/src --include='*.rs' \ | grep -vE '[a-z]+:[/][/]' | grep -vE '"[^"]*[/]{2}' @@ -135,7 +117,6 @@ unsafe_only_in_the_sys_crates nothing_the_core_prints nothing_the_core_depends_on written_in_english -neutral_spanish no_comments_in_the_code every_example_has_its_own_name what_python_measures "the tests live beside the file, not inside it" oversized.py --inline From 47d1b6e4bd4fa6b7ed35c5f95ee38e9bcacee209 Mon Sep 17 00:00:00 2001 From: rgdevment Date: Mon, 5 Oct 2026 20:15:14 -0300 Subject: [PATCH 2/3] chore(commits): one line of up to 120 characters, no body, no trailers, no outside links --- CONTRIBUTING.md | 35 ++++++++++++----- hooks/commit-msg | 5 +-- scripts/commits.sh | 94 +++++++++++++++++++++++++++++++++------------- 3 files changed, 94 insertions(+), 40 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c02c80b..1b1cf01e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -121,9 +121,9 @@ We keep the code simple and consistent: `scripts/rules.sh` holds the conventions a person can break in a second: a comment where the code should speak for itself, `unsafe` outside the `-sys` crates, the core printing to a terminal or reaching for a platform, Spanish in -an identifier, voseo or peninsular words in what a person reads, two crates -naming an example the same, and a file grown past what anybody reads through. -`scripts/commits.sh` holds the shape and the length of a subject. Both answer +an identifier, two crates naming an example the same, and a file grown past +what anybody reads through. `scripts/commits.sh` holds the commit convention +below. Both answer the same whether you run them or CI does, and they say every rule that broke rather than stopping at the first. Run either whenever you like, and if you want them run for you: @@ -134,7 +134,7 @@ git config core.hooksPath hooks That gives you three. `pre-commit` runs the conventions, `cargo fmt --all --check` and biome — a couple of seconds, and between them they are most of what -turns CI red. `commit-msg` weighs the subject while the fix is still an +turns CI red. `commit-msg` weighs the message while the fix is still an `--amend` rather than a rebase. `pre-push` runs the conventions again and the subjects of everything you are about to send, and when what you are sending is a tag it asks `scripts/news.sh` whether `app/src/news.json` says what changed in @@ -145,6 +145,25 @@ is above `3.0.0-rc1`. Nothing slower goes in any of them: the suite, the build and the markdown lint are minutes, and they belong to CI. +### Commit messages + +One line, in English, and nothing else: + +```text +type(scope): the concrete change +``` + +- **Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, + `build`, `ci`, `chore`, `revert`. The scope is optional. +- **At most 120 characters.** +- **No body and no trailers** — no `Co-authored-by`, no `Signed-off-by`. The + CLA below is signed once on the pull request, not on each commit. +- **No links**, except `#123` for an issue or pull request of this repository. +- Say what changed, not how it was found. + +A pull request's title follows the same rule, because the squash keeps it as +the subject on `main`. + **UI/UX:** - Native look and feel on each platform (Windows, macOS) @@ -179,11 +198,9 @@ I have read the CLA Document and I hereby sign the CLA That is it — every later Pull Request from the same account is covered. -**Please leave tool co-authorship out of your commits.** Assistants are welcome -here — this project is built with them — but the credit line is for people. If -your editor adds a trailer naming one, drop it before you push. It changes -nothing about what you are allowed to submit; section 4 of the CLA already puts -the responsibility for generated code on you, whichever tool helped write it. +**Commits carry no trailers.** If your editor adds one, drop it before you +push. Section 4 of the CLA already puts the responsibility for what you submit +on you, whatever helped you write it. **In return, the project commits that:** diff --git a/hooks/commit-msg b/hooks/commit-msg index ec9576f4..c031dadb 100755 --- a/hooks/commit-msg +++ b/hooks/commit-msg @@ -3,7 +3,4 @@ set -uo pipefail cd "$(git rev-parse --show-toplevel)" || exit 0 -said=$(sed '/^# -* >8 -*$/,$d' "$1" | git stripspace | sed -n '1,/^$/p' | paste -sd' ' -) -[ -n "$said" ] || exit 0 - -exec bash scripts/commits.sh --subject "$said" +exec bash scripts/commits.sh --message "$1" diff --git a/scripts/commits.sh b/scripts/commits.sh index f8c4fda9..f0e13ba2 100755 --- a/scripts/commits.sh +++ b/scripts/commits.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash set -uo pipefail -shape='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9._-]+\))?!?: .+' -most=90 +shape='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9._-]+\))?: .+' +elsewhere='https?://|[[:alnum:]_.-]+/[[:alnum:]_.-]+#[0-9]+' +most=120 status=0 amiss() { @@ -14,21 +15,30 @@ amiss() { status=1 } +# commit-msg runs on git merge too, where a refusal leaves it half done +git_wrote() { + case $1 in + "Merge branch '"* | "Merge branches "* | "Merge pull request #"* | \ + "Merge remote-tracking branch '"* | "Merge commit '"* | "Merge tag '"* | \ + "Merge http://"* | "Merge https://"* | "Merge git://"* | "Merge ssh://"* | \ + "Squashed commit of the following:"* | 'Revert "'* | 'Reapply "'* | \ + "fixup! "* | "squash! "* | "amend! "*) + return 0 + ;; + esac + return 1 +} + +trimmed() { + printf '%s' "$1" | LC_ALL=C sed 's/^[[:space:]]*//;s/[[:space:]]*$//' +} + # strict is for a title, which reaches main: only what git writes itself keeps its shape there weighed() { - local who=$1 said=$2 strict=${3:-} - said=$(printf '%s' "$said" | LC_ALL=C sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - if [ -z "$strict" ]; then - # every shape git writes itself, and commit-msg runs on git merge, where a refusal leaves it half done - case $said in - "Merge branch '"* | "Merge branches "* | "Merge pull request #"* | \ - "Merge remote-tracking branch '"* | "Merge commit '"* | "Merge tag '"* | \ - "Merge http://"* | "Merge https://"* | "Merge git://"* | "Merge ssh://"* | \ - "Squashed commit of the following:"* | 'Revert "'* | 'Reapply "'* | \ - "fixup! "* | "squash! "* | "amend! "*) - return - ;; - esac + local who=$1 said strict=${3:-} + said=$(trimmed "$2") + if [ -z "$strict" ] && git_wrote "$said"; then + return fi case $said in 'Revert "'* | 'Reapply "'*) ;; @@ -40,6 +50,10 @@ weighed() { fi ;; esac + if printf '%s' "${said% (#[0-9]*)}" | grep -qE "$elsewhere"; then + amiss "$who links outside this repository; only #123 of this one" + printf ' %s\n' "$said" + fi # bytes minus UTF-8 continuation bytes: ${#said} counts bytes when a client spawns git with no locale local long long=$(printf '%s' "$said" | LC_ALL=C tr -d '\200-\277' | wc -c | tr -d ' ') @@ -49,29 +63,53 @@ weighed() { fi } +bodiless() { + local who=$1 subject body + subject=$(trimmed "$2") + body=$(trimmed "$3") + if [ -n "$body" ] && ! git_wrote "$subject"; then + amiss "$who carries a body or a trailer; a commit is its subject line alone" + fi +} + how_it_reads() { if [ "$status" -eq 1 ]; then printf '\n' - printf 'Expected: type(optional scope): description\n' + printf 'Expected: type(optional scope): concrete change, one line, in English\n' printf 'Types: feat fix docs style refactor perf test build ci chore revert\n' printf 'Example: fix(panel): the menu yields to the wheel\n' fi exit $status } -if [ "${1:-}" = "--subject" ] || [ "${1:-}" = "--title" ]; then - [ -n "${2:-}" ] || { - echo "usage: commits.sh --subject|--title " - exit 2 - } - weighed "the subject" "$2" "$([ "$1" = "--title" ] && echo strict)" - [ "$status" -eq 0 ] && [ -z "${GITHUB_ACTIONS:-}" ] && printf 'ok the subject is well formed\n' - how_it_reads -fi +case ${1:-} in + --subject | --title) + [ -n "${2:-}" ] || { + echo "usage: commits.sh --subject|--title " + exit 2 + } + weighed "the subject" "$2" "$([ "$1" = "--title" ] && echo strict)" + [ "$status" -eq 0 ] && [ -z "${GITHUB_ACTIONS:-}" ] && printf 'ok the subject is well formed\n' + how_it_reads + ;; + --message) + [ -f "${2:-}" ] || { + echo "usage: commits.sh --message " + exit 2 + } + said=$(sed '/^# -* >8 -*$/,$d' "$2" | git stripspace --strip-comments) + [ -n "$said" ] || exit 0 + subject=$(printf '%s\n' "$said" | head -1) + weighed "the subject" "$subject" + bodiless "the message" "$subject" "$(printf '%s\n' "$said" | tail -n +2)" + [ "$status" -eq 0 ] && [ -z "${GITHUB_ACTIONS:-}" ] && printf 'ok the message is well formed\n' + how_it_reads + ;; +esac range=${1:-} [ -n "$range" ] || { - echo "usage: commits.sh | --subject | --title " + echo "usage: commits.sh | --subject | --title | --message " exit 2 } @@ -89,7 +127,9 @@ seen=0 while read -r sha; do [ -n "$sha" ] || continue seen=$((seen + 1)) - weighed "${sha:0:8}" "$(git log -1 --format=%s "$sha")" + subject=$(git log -1 --format=%s "$sha") + weighed "${sha:0:8}" "$subject" + bodiless "${sha:0:8}" "$subject" "$(git log -1 --format=%b "$sha")" done <<< "$listed" [ "$status" -eq 0 ] && [ -z "${GITHUB_ACTIONS:-}" ] && printf 'ok %s commit subject(s) well formed\n' "$seen" From 7203bd2248302f8b6e89c2a1b81c6ac3c5764867 Mon Sep 17 00:00:00 2001 From: rgdevment Date: Mon, 5 Oct 2026 20:36:04 -0300 Subject: [PATCH 3/3] fix(commits): every rule reads the whole message, bots keep their body, unpushed work alone is weighed --- CONTRIBUTING.md | 2 +- hooks/pre-push | 8 ++----- scripts/commits.sh | 59 ++++++++++++++++++++++++++++------------------ 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b1cf01e..c1f28359 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -136,7 +136,7 @@ That gives you three. `pre-commit` runs the conventions, `cargo fmt --all --check` and biome — a couple of seconds, and between them they are most of what turns CI red. `commit-msg` weighs the message while the fix is still an `--amend` rather than a rebase. `pre-push` runs the conventions again and the -subjects of everything you are about to send, and when what you are sending is a +messages of everything you are about to send, and when what you are sending is a tag it asks `scripts/news.sh` whether `app/src/news.json` says what changed in that version — the screen that tells a person what is new is the only place the app says it, and a tag is the last moment to notice it is empty. A candidate is diff --git a/hooks/pre-push b/hooks/pre-push index 047d38c0..10a7d80e 100755 --- a/hooks/pre-push +++ b/hooks/pre-push @@ -21,14 +21,10 @@ while read -r ref mine _ theirs; do esac if [ "${theirs:-$nothing}" != "$nothing" ]; then - range="$theirs..$mine" - elif base=$(git merge-base "$mine" origin/main 2> /dev/null) && [ -n "$base" ]; then - range="$base..$mine" + bash scripts/commits.sh "$theirs..$mine" || status=1 else - echo "! origin/main is not here, so the subjects were not looked at" - continue + bash scripts/commits.sh "$mine" --not --remotes || status=1 fi - bash scripts/commits.sh "$range" || status=1 done exit $status diff --git a/scripts/commits.sh b/scripts/commits.sh index f0e13ba2..833df0c8 100755 --- a/scripts/commits.sh +++ b/scripts/commits.sh @@ -2,7 +2,7 @@ set -uo pipefail shape='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9._-]+\))?: .+' -elsewhere='https?://|[[:alnum:]_.-]+/[[:alnum:]_.-]+#[0-9]+' +elsewhere='https?://[^[:space:]/]+\.|(^|[[:space:](<])www\.|(^|[[:space:](<])([[:alnum:]-]+\.)+[[:alpha:]]{2,}/|[[:alnum:]._-]+@[[:alnum:].-]+:|[[:alnum:]_.-]+/[[:alnum:]_.-]+#[0-9]+' most=120 status=0 @@ -40,27 +40,28 @@ weighed() { if [ -z "$strict" ] && git_wrote "$said"; then return fi + local broke="" case $said in 'Revert "'* | 'Reapply "'*) ;; *) if ! printf '%s' "$said" | grep -qE "$shape"; then + broke=1 amiss "$who does not follow the convention" - printf ' %s\n' "$said" - return fi ;; esac - if printf '%s' "${said% (#[0-9]*)}" | grep -qE "$elsewhere"; then + if printf '%s' "$said" | grep -qiE "$elsewhere"; then + broke=1 amiss "$who links outside this repository; only #123 of this one" - printf ' %s\n' "$said" fi # bytes minus UTF-8 continuation bytes: ${#said} counts bytes when a client spawns git with no locale local long long=$(printf '%s' "$said" | LC_ALL=C tr -d '\200-\277' | wc -c | tr -d ' ') if [ "$long" -gt "$most" ]; then + broke=1 amiss "$who is $long characters, and the subject goes under $most" - printf ' %s\n' "$said" fi + [ -z "$broke" ] || printf ' %s\n' "$said" } bodiless() { @@ -68,7 +69,8 @@ bodiless() { subject=$(trimmed "$2") body=$(trimmed "$3") if [ -n "$body" ] && ! git_wrote "$subject"; then - amiss "$who carries a body or a trailer; a commit is its subject line alone" + amiss "$who carries more than its subject line: a body, a trailer or a wrapped subject" + printf ' %s\n' "$subject" fi } @@ -83,13 +85,13 @@ how_it_reads() { } case ${1:-} in - --subject | --title) + --title) [ -n "${2:-}" ] || { - echo "usage: commits.sh --subject|--title " + echo "usage: commits.sh --title " exit 2 } - weighed "the subject" "$2" "$([ "$1" = "--title" ] && echo strict)" - [ "$status" -eq 0 ] && [ -z "${GITHUB_ACTIONS:-}" ] && printf 'ok the subject is well formed\n' + weighed "the title" "$2" strict + [ "$status" -eq 0 ] && [ -z "${GITHUB_ACTIONS:-}" ] && printf 'ok the title is well formed\n' how_it_reads ;; --message) @@ -97,7 +99,14 @@ case ${1:-} in echo "usage: commits.sh --message " exit 2 } - said=$(sed '/^# -* >8 -*$/,$d' "$2" | git stripspace --strip-comments) + # git strips its own comment lines after this hook runs, and only when an editor wrote them + mark=$(git config core.commentChar 2> /dev/null || echo '#') + case $mark in auto | "") mark='#' ;; esac + said=$(awk -v m="$mark" ' + index($0, m " ") == 1 && $0 ~ / -+ >8 -+$/ { exit } + $0 == m || index($0, m " ") == 1 || index($0, m "\t") == 1 { next } + { print } + ' "$2" | git stripspace) [ -n "$said" ] || exit 0 subject=$(printf '%s\n' "$said" | head -1) weighed "the subject" "$subject" @@ -107,30 +116,34 @@ case ${1:-} in ;; esac -range=${1:-} -[ -n "$range" ] || { - echo "usage: commits.sh | --subject | --title | --message " +[ $# -gt 0 ] || { + echo "usage: commits.sh | --title | --message " exit 2 } -# git prints hints on stderr while exiting 0, and folding them in would weigh them as subjects +# git prints hints on stderr while exiting 0, and folding them in would weigh them as messages trouble=$(mktemp) -if ! listed=$(git rev-list --no-merges "$range" 2> "$trouble"); then +if ! listed=$(git log --no-merges --format='%h%x1f%an%x1f%B%x1e' "$@" 2> "$trouble"); then cat "$trouble" >&2 rm -f "$trouble" - amiss "the commits between $range could not be listed, so no subject was looked at" + amiss "the commits in $* could not be listed, so no message was looked at" exit 1 fi rm -f "$trouble" seen=0 -while read -r sha; do +while IFS=$'\x1f' read -r -d $'\x1e' sha name message; do + sha=$(trimmed "$sha") [ -n "$sha" ] || continue seen=$((seen + 1)) - subject=$(git log -1 --format=%s "$sha") - weighed "${sha:0:8}" "$subject" - bodiless "${sha:0:8}" "$subject" "$(git log -1 --format=%b "$sha")" + subject=$(printf '%s\n' "$message" | head -1) + weighed "$sha" "$subject" + # a bot writes its own body with no way to leave it out; its squash title is what reaches main + case $name in + *"[bot]") ;; + *) bodiless "$sha" "$subject" "$(printf '%s\n' "$message" | tail -n +2)" ;; + esac done <<< "$listed" -[ "$status" -eq 0 ] && [ -z "${GITHUB_ACTIONS:-}" ] && printf 'ok %s commit subject(s) well formed\n' "$seen" +[ "$status" -eq 0 ] && [ -z "${GITHUB_ACTIONS:-}" ] && printf 'ok %s commit message(s) well formed\n' "$seen" how_it_reads