FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

check-branch accepts names it should reject: the regex has no end anchor, and the description part is unvalidated · Issue #550 · commit-check/commit-check · GitHub

check-branch accepts names it should reject: the regex has no end anchor, and the description part is unvalidated #550

Description

Found while building an enforcement page for conventionalbranch.org, where commit-check is the tool I wanted to recommend for the CI and local layers. Running it against the specification's conformance fixtures turned up 6 of 34 disagreements, plus a separate bypass that is a bug regardless of the spec.

Version tested: commit-check 2.15.0, default configuration.

Root cause

All of it comes from one line in rule_builder.py:

def _build_conventional_branch_regex(self, allowed_types, allowed_names) -> str:
    types_pattern = "|".join(allowed_types)
    base_names = ["master", "main", "HEAD", "PR-.+"]
    all_names = base_names + allowed_names
    names_pattern = ")|(".join(all_names)
    return rf"^({types_pattern})\/.+|({names_pattern})"

which produces:

^(feature|bugfix|...)\/.+|(master)|(main)|(HEAD)|(PR-.+)

1. No end anchor, and ^ binds to the first alternative only

| has the lowest precedence, so the ^ applies to ^(types)\/.+ alone. (master), (main), (HEAD) and (PR-.+) are unanchored on both sides, and nothing in the pattern has a $. Since the matcher is re.match, the effect is that any branch name beginning with main, master or HEAD skips validation entirely:

Branch commit-check
mainline accepted
masterkey accepted
main-foo accepted
HEADless accepted

These are ordinary names someone could plausibly pick, and they are silently exempted from the check the user enabled. This one is a bug on its own terms — it has nothing to do with which specification you follow.

2. The description is .+, so none of the naming rules are enforced

Everything after the slash is accepted, which means the lowercase/hyphen rules are not checked at all:

Branch Why the spec rejects it commit-check
feature/new--login consecutive separators accepted
feature/-new-login leading separator accepted
feature/new-login- trailing separator accepted
fix/header_bug underscore not allowed accepted
release/v1.-2.0 dot followed by separator accepted

3. develop is rejected

develop is one of the three trunk branches in the specification, alongside main and master, but only the latter two are in base_names. main and master pass, develop fails, and the user has to discover allow_branch_names to fix it — which is how #335 was resolved. Since conventional_branch: true is meant to mean "follow the specification", the three trunk branches arguably belong in the default.

Reproduction

#!/bin/sh
set -e
pip install --quiet commit-check
tmp=$(mktemp -d); cd "$tmp"
git init -q -b main . && git commit -q --allow-empty -m "feat: init"

check() {
  git checkout -q -B "$1" 2>/dev/null
  if commit-check --branch >/dev/null 2>&1; then got=accepted; else got=rejected; fi
  [ "$got" = "$2" ] && mark="ok  " || mark="FAIL"
  printf '%s  %-24s spec=%-8s commit-check=%s\n' "$mark" "$1" "$2" "$got"
}

check mainline rejected
check masterkey rejected
check main-foo rejected
check HEADless rejected
check feature/new--login rejected
check feature/-new-login rejected
check feature/new-login- rejected
check fix/header_bug rejected
check release/v1.-2.0 rejected
check develop accepted

All ten report FAIL on 2.15.0.

Not reporting: the extra branch types

docs/, ci/, refactor/, test/ and friends being accepted is deliberate — that was #494, aligning branch types with commit types. That is a reasonable product decision and this issue does not ask to revert it. It does mean the default config is intentionally broader than the specification, which is worth a sentence in the docs so nobody assumes "default" and "spec-conformant" are the same thing.

Suggested fix

The narrow fix is to group and anchor the whole thing, and to validate the description:

return rf"^(?:(?:{types_pattern})/{description_pattern}|(?:{names_pattern}))$"

The broader one, and the reason I am filing this rather than sending a one-line patch: the specification publishes fixtures.json precisely so implementations can prove they conform. Wiring it into commit-check's CI would have caught all three of these, and would keep catching them:

# 34 cases, language-agnostic, one URL, no dependency on the spec repo
spec = json.load(urlopen("https://conventionalbranch.org/v1.1.0/spec.json"))
cases = json.load(urlopen("https://conventionalbranch.org/...fixtures..."))

That would also make commit-check the reference implementation in a way that is verifiable rather than asserted, which is worth more than any wording on either website. Happy to send the PR for either or both if you would like.

Disclosure: I maintain both projects, so treat this as a note-to-self filed in public rather than an outside bug report.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


    Back | FazBrowse Home | New Git URL