diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e2deea82..aeed41ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,7 +29,7 @@ Commands below use `python3`. On Windows, run `python` instead. - **Scope** is each changed path without a leading `MobileGL/`, cut to its first two parts: `MG_Backend/DirectGLES`, `MG_Test/Wire`, `scripts/ci`, `docs/Disaggregated`. - A shorter path is used whole: `MG_Backend` for a file directly in a subsystem, `scripts` for a file directly in `scripts/`, and the file name for a file at the repository root (`CMakeLists.txt`). - A file directly in `MobileGL/` uses `MobileGL`, and a submodule uses its path (`3rdparty/glslang`). - - List every scope the commit touches, comma-separated: `(MG_Backend/DirectGLES, MG_Test/Wire)`. + - List every scope the commit touches, comma-separated: `(MG_Backend/DirectGLES, MG_Test/Wire)`. A commit that touches more than three scopes may write `(...)` instead. - `python3 scripts/check_text_hygiene.py scope` prints the scope for the staged changes. - **No** planning ids (phase or decision codes such as `P13` or `ID-54`), local paths, external project names or non-English text anywhere in the message. - **Merges** use `[Merge] (area): what the merge takes`. The scope is free text there. Write the subject yourself, since git's default merge message fails the check. The commits a merge brings in are not checked again. diff --git a/scripts/check_text_hygiene.py b/scripts/check_text_hygiene.py index 743724f5..229c0281 100644 --- a/scripts/check_text_hygiene.py +++ b/scripts/check_text_hygiene.py @@ -50,6 +50,8 @@ _spec.loader.exec_module(HEADERS) TYPES = {"Feat", "Fix", "Perf", "Refactor", "Test", "Docs", "Build", "CI", "Chore", "Tools"} MERGE_TYPE = "Merge" CONTENT_MAX = 72 +# A commit that touches more scopes than this may write the scope as `(...)`. +SCOPE_ABBREVIATE_OVER = 3 CONTINUATION_MAX = 2 COMMENT_BLOCK_MAX = 15 BASE_BRANCH = os.environ.get("TEXT_HYGIENE_BASE_BRANCH", "feat/disaggregated") @@ -176,7 +178,8 @@ def check_message(message, changed_paths, added_paths, submodules, is_merge=Fals if changed_paths: listed = [s.strip() for s in m.group("scopes").split(",")] expected = scopes_for(changed_paths, submodules) - if sorted(listed, key=str.lower) != expected: + abbreviated = listed == ["..."] and len(expected) > SCOPE_ABBREVIATE_OVER + if not abbreviated and sorted(listed, key=str.lower) != expected: problems.append("scope should be (%s), not (%s)" % (", ".join(expected), m.group("scopes"))) content = m.group("content") @@ -704,6 +707,12 @@ def self_test(): } for name, msg in cases.items(): expect("message rejects " + name, check_message(msg, paths, [], subs) != []) + wide = ["MobileGL/MG_Backend/DirectGLES/a.cpp", "MobileGL/MG_Test/Wire/b.cpp", + "MobileGL/MG_Impl/GLImpl/c.cpp", "MobileGL/MG_Remote/Client/d.cpp"] + expect("message: wide scope abbreviated", + check_message("[Chore] (...): touch four layers\n", wide, [], subs) == []) + expect("message: narrow scope not abbreviated", + check_message("[Chore] (...): touch two layers\n", paths, [], subs) != []) expect("message rejects new notes", check_message("[Docs] (docs/Disaggregated): add a review\n", ["docs/Disaggregated/notes/p9/r.md"], ["docs/Disaggregated/notes/p9/r.md"], subs) != [])