From cfd64884336f39e84422f20fb3b4cf2934824482 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 3 Aug 2026 16:35:59 +0000 Subject: [PATCH 1/4] docs: rebuild the documentation site on MkDocs Material MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The site read as a repository rather than a product. Its homepage was the GitHub README included verbatim — eight CI badges above a hand-written table of contents — and everything behind it was reference material, with no path for somebody meeting the tool for the first time. Move to MkDocs Material, which is what ruff, uv, Pydantic and Typer use, and which sphinx-immaterial was a partial port of. The toolchain stays Python, so contributors still need nothing but pip, and the docs extras shrink from five Sphinx packages to one. Replace the homepage with an actual landing page: a value proposition, the same policy shown running as a CLI, a pre-commit hook, a GitHub Action and an MCP server, and cards linking each rule family to its reference. Badges belong on the README and stay there. Add the pages the site never had. Installation, a quick start that ends with a working local policy, and the reasoning behind the tool; then task-oriented guides for pre-commit, GitHub Actions, organization-wide config, DCO signoff, and AI attribution. The reference pages carry over converted to Markdown. Replace the JPEG logo with an SVG mark and add a favicon, neither of which the site previously had. Rule anchors move from RST explicit targets to heading attributes, so rule URLs become /rules/#cc003 rather than /rules.html#cc003. Every URL the Sphinx site served now redirects to its replacement, since links to them exist in the README, on PyPI and elsewhere. The publish step pins the CNAME explicitly so the custom domain cannot be lost when the branch is replaced. The anti-drift tests follow the content: they parse Markdown tables and heading anchors now, and still verify every documented default against get_default_config(). --- .github/workflows/main.yml | 7 +- .gitignore | 4 + commit_check/rules_catalog.py | 2 +- docs/README.rst | 21 - docs/_static/extra_css.css | 71 --- docs/_static/logo.jpg | Bin 10250 -> 0 bytes docs/assets/extra.css | 89 +++ docs/assets/favicon.svg | 8 + docs/assets/logo.svg | 8 + docs/changelog.md | 187 ++++++ docs/changelog.rst | 227 ------- docs/conf.py | 161 ----- docs/configuration.md | 305 +++++++++ docs/configuration.rst | 476 -------------- docs/example.md | 374 +++++++++++ docs/example.rst | 397 ------------ docs/getting-started/installation.md | 78 +++ docs/getting-started/quickstart.md | 148 +++++ docs/getting-started/why.md | 56 ++ docs/guides/ai-attribution.md | 75 +++ docs/guides/github-actions.md | 81 +++ docs/guides/organization.md | 100 +++ docs/guides/pre-commit.md | 89 +++ docs/guides/signoff.md | 81 +++ docs/index.md | 189 +++++- docs/migration.md | 189 ++++++ docs/migration.rst | 203 ------ docs/rules.md | 695 +++++++++++++++++++++ docs/rules.rst | 891 --------------------------- docs/troubleshoot.md | 41 ++ docs/troubleshoot.rst | 45 -- docs/what-is-new.md | 366 +++++++++++ docs/what-is-new.rst | 420 ------------- mkdocs.yml | 128 ++++ noxfile.py | 4 +- pyproject.toml | 2 +- scripts/mkdocs_hooks.py | 89 +++ tests/rules_catalog_test.py | 48 +- 38 files changed, 3389 insertions(+), 2966 deletions(-) delete mode 100644 docs/README.rst delete mode 100644 docs/_static/extra_css.css delete mode 100644 docs/_static/logo.jpg create mode 100644 docs/assets/extra.css create mode 100644 docs/assets/favicon.svg create mode 100644 docs/assets/logo.svg create mode 100644 docs/changelog.md delete mode 100644 docs/changelog.rst delete mode 100644 docs/conf.py create mode 100644 docs/configuration.md delete mode 100644 docs/configuration.rst create mode 100644 docs/example.md delete mode 100644 docs/example.rst create mode 100644 docs/getting-started/installation.md create mode 100644 docs/getting-started/quickstart.md create mode 100644 docs/getting-started/why.md create mode 100644 docs/guides/ai-attribution.md create mode 100644 docs/guides/github-actions.md create mode 100644 docs/guides/organization.md create mode 100644 docs/guides/pre-commit.md create mode 100644 docs/guides/signoff.md create mode 100644 docs/migration.md delete mode 100644 docs/migration.rst create mode 100644 docs/rules.md delete mode 100644 docs/rules.rst create mode 100644 docs/troubleshoot.md delete mode 100644 docs/troubleshoot.rst create mode 100644 docs/what-is-new.md delete mode 100644 docs/what-is-new.rst create mode 100644 mkdocs.yml create mode 100644 scripts/mkdocs_hooks.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 487ea65b..fffc07db 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -107,7 +107,7 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: "commit-check_docs" - path: ${{ github.workspace }}/_build/html + path: ${{ github.workspace }}/site - name: Upload docs to github pages # only publish doc changes from main branch @@ -115,4 +115,7 @@ jobs: uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./_build/html + publish_dir: ./site + # Pinned explicitly so the custom domain cannot be lost when the + # publish branch is replaced. + cname: docs.commit-check.com diff --git a/.gitignore b/.gitignore index 3dfd5ade..546ac132 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,7 @@ _build/ docs/_build docs/cli_args.rst docs/__pycache__ + +# MkDocs +site/ +docs/cli.md diff --git a/commit_check/rules_catalog.py b/commit_check/rules_catalog.py index 71d4eb05..a912cc1c 100644 --- a/commit_check/rules_catalog.py +++ b/commit_check/rules_catalog.py @@ -21,7 +21,7 @@ from dataclasses import dataclass #: Base URL of the rules reference documentation. -RULES_DOCS_URL = "https://docs.commit-check.com/rules.html" +RULES_DOCS_URL = "https://docs.commit-check.com/rules/" @dataclass(frozen=True) diff --git a/docs/README.rst b/docs/README.rst deleted file mode 100644 index d2964103..00000000 --- a/docs/README.rst +++ /dev/null @@ -1,21 +0,0 @@ -:orphan: - -How to build the docs -===================== - -From the root directory of the repository, do the following to steps - -1. Install docs' dependencies - - .. code-block:: text - - pip install nox - -2. Build the docs - - .. code-block:: text - - nox -s docs - - Browse the files in /_build/html with your internet browser to see the rendered - output. diff --git a/docs/_static/extra_css.css b/docs/_static/extra_css.css deleted file mode 100644 index cc322201..00000000 --- a/docs/_static/extra_css.css +++ /dev/null @@ -1,71 +0,0 @@ -tbody .stub, -thead { - background-color: var(--md-accent-bg-color--light); - color: var(--md-default-bg-color); -} - -.md-header, -.md-nav--primary .md-nav__title[for="__drawer"] { - background-color: #2c9ccd; -} - -/* Sidebar section headings ("Getting started", "Reference", ...) */ -.md-nav__item--section > .md-nav__link { - font-weight: 700; - color: var(--md-default-fg-color); -} - -/* Fix table header visibility for both light and dark modes */ -.md-content table th { - color: var(--md-typeset-color) !important; -} - -/* Custom color scheme to match logo */ -:root { - --md-primary-fg-color: #2c9ccd; - --md-primary-fg-color--light: #5bb3d9; - --md-primary-fg-color--dark: #1e85a8; -} - -/* Navigation and links */ -.md-nav__link--active, -.md-nav__link:hover { - color: #2c9ccd; -} - -/* Buttons and accent elements */ -.md-button--primary { - background-color: #2c9ccd; - border-color: #2c9ccd; -} - -.md-button--primary:hover { - background-color: #1e85a8; - border-color: #1e85a8; -} - -/* Code blocks and syntax highlighting accents */ -.md-typeset .codehilite .hll, -.md-typeset .highlight .hll { - background-color: rgba(44, 156, 205, 0.1); -} - -/* Admonition titles with your brand color */ -.md-typeset .admonition.note > .admonition-title, -.md-typeset .admonition.tip > .admonition-title { - background-color: rgba(44, 156, 205, 0.1); - border-color: #2c9ccd; -} - -/* Align table cells to the top so short cells line up with a wrapped message. */ -.md-typeset table td, -.md-typeset table th { - vertical-align: top; -} - -/* In the rule index tables only, keep rule codes, names, and CLI flags on one - line so the tables stay scannable. Other tables (such as the configuration - options table) hold long regexes and lists that must stay wrappable. */ -.md-typeset table.rules-index td code { - white-space: nowrap; -} diff --git a/docs/_static/logo.jpg b/docs/_static/logo.jpg deleted file mode 100644 index ed4cd723de56127ed77189e523cf63ebd651020d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10250 zcmeHtdpwls`~O3QEzxI7vQD+_Zd)vEku#fJZ23?aGc*l4AUPk#VH`_`-4dM;<50<9 z4vsMf!{|f~BTWwmV@OOM$7s-K_}-8Ge18A@{{Q~<^_SOk-}iN2*ZVr$*J1uX>1elg z^LLve2-l&fo1Ia#xI=pQ|1EqL(iVIQYN6 z|1AW(_U9Zlg*du1E49*sz0l+Y7ycVAa*Z00tZb(axb_CJEwP&PQseU359+@0quU+l z0(X{wodIVcj;x?Wav7O{ML5q>0-9tB(SrB6Mg<(W(u>7mwdr~#dUe84{f2VM@TK^D zP5z&{Wft?S6`+92E7&!<1VPkm65^cA);vB6;K|vg%l4k7*Ud@|X zx?{vmpxzaZ%SMxP~}~Fg|nYjS3d$4J?n?N*!%;Y4AEv|h%Lj_{@*v{AAsm`&owk*SpuR`-*tJqgH8P0c)$wx7TsJ94 zs+BlGu#=a0kgj44d8Nk@U1Yv4QY!w{KHg3(qk5Q|DsXgnfEAS-jKxDqGv&;DJCf%> z%LJ@_i+;}%Jd!YBHMl9*l*a6EEMA2_ZiA|v6=agU#rm|_3*YViWRqOgfkeV=6xLu* zfw8F~r{d=;5T}wAFLr>Jj2N3s-!AFw!{j>&e!aTQO@{VaUKc7~oe>%|jx@s}Wp1Uq z5jMEER9$nU*Undt5Y+yjOtZr%2O73}!47bt{9yO> z?!s=~1C?L!95nD+wK5X4vh*z7EY3tIB;}q2(%DE7Y1+k&p%J$&B8lQ>sA5L|4`z?H z$wD{hj=@n_E9dx$Vcpni3IfBdf@EmwK47n7Ko!WFfFpJ_yN{1lB1_6sW?xkP-ztxZ zu{xdGL*iOBQY8f_Q3)Vgj}l#&&xNyKshxb*J%6e%PFWjp$V3Gz6R;(ZC`T8Tv~s*X zN=x*L6UI>UQP-6FXI(!&hWGMU<%`*dwW*~g9;c-Jz-ZQ_!@5w54X~}nHl|T}bK+ss z)*PvWcn-aD4BXjbkFGsS^f%RC{;?prgFi{DLU|oSdEEqz%~&h{l(Y-kM~&UeJru0! zY83-;(^msrm7&TF%Ed@A;$h0o^F4f_s})00cG4D5j2xv=J&RZKkyKz5ylR+C%Rec1 ziTDHvXeRcgY113SB5s@1*Gq85h&wxs|p+RX>T zU06>`(^lEjt`_wriXH~WTR7I}jU(LR1iD}S$GQQ%T%5b&1S$g6trop4@quBK49-T;Gvu~gUcnv|4uAwcHcQt z0G}h*{Cyx9LRU%}8~xI!{O#o3Zzsbh4g-H!9RiHM1=j4f@HG+cs#ZHJN3+C87kehD z&F)af4PdY2u_mmQXMFR=-CO2YDHY!~;RsgfAt!L?aL&p2L@{sOLw}wPG~FBHNaO)F z?2ql)m?Vz6PEkv?IxUC|$*}p-g5YQiRI8UaIo?h~SgEu4e)mxbdkHWzIS~ZwV}vg8_ENY=EFxgh#$smUx2fA}4%jfOAsY*tbUq;9+T5 zW=fNsfC;Z`-5(fX)@Y;|^w6Iu_j5LIFjl#E#@M+1u|~0$vqfUWwj@{Y_MahoqrU%l zjjOi;9o4?p-#?l(>H{=|9e54TVm)1R4}$#-6(x#|A?SWzbg38T+JuWIqPGu5_?!f{ zZ$KfS@Thi%>CYTOACfMJGl5spx8QAMyu@+#yqsqJ?=;U}T5a?re_XJYmvP&N1rk=r z#q!#Qe@bsQGGZtKmB#)XFbap%Bh4@$J_dk%i>Iv-Ed0U6#PcYl*W__c*mxk*md~Y# zD%A>&7zB54Xe`oNYyrCmV%6eKxTu+DvWB&`mRoYHu5+q&J6C!)a;CT-kCb5YpU~ zqDoiSD2JlcfDKQ~BfrA4fzn+`o4~8$)nI0r-ki^H)X4F^qetV?5~3zsAl(Om!E*wZ zWCVExq-toGn15jBs*O6?Ibit*xdf^1bO`q_Ct{RaS*GZzLS7Qh;uEhM3F->37jk|(pcl9P(FWjZ1wBO&p zj!t=UC z1fO~|V6SZcp<2C&XrER4#58!aTGeL!g^$hwL+PomSlt(XogO|x{?XDp3!Y=1uaT8@ z638IjepIIGm3q9JvF{mi43JQy?~y?tPqtb1bdHD_Wpd(T+m{ha4%79<_y~P*ym#z^ z)*;c7)u5^6cBmfYD7sDevX2jc>0BZ&^Ju-*E5RRBvlia(#2b%)aW69R@%Gou|0YQj zs3&6}@)-V|QSX4xHT{deg+b@JHuRlb+HinXi^AmczoDty)(Yhf+`vx*dzraCm^q~; z#^*n6MGdw5Fs-r}da=cmUz>uiC#;$eg?~6KQw|)36K2Zv+vNLXI~hBinsF{Gj9b4W z$aB03=Xmj}Nj)*{Cn)kaAfILX$;z7CzWbs-z5V%NdXIO^c=Ju7l!cSg^wGmI2lt-a z2Q1Njy7atez;~JzO@4noe-ysr*iQ;fBlmT7=8JGek?pP5V?={__}c9Qg>&x~6Ry0f znN}$u)#T4{GDL6Fi7^x@ydiSg&@7i|Ota~T-q^Ptus0v=W#-9XJ(*DdvXJ^IT9WbF0WV+7^8~Nk2`$2-;t^Fn>EWSnK^EkGe{wu84B{ zn*243SRr_N^IBG9bvJ=Hknw!6r^8({;8b<(2|ImvrnuS{SV{%uOIW1FY)+-MN4GEnCSDk)rOFwo=;iO0}wOIH6p|8MT^57S&c+D8-Y`+^qC3;E$(+S4GjGCnhnTTh=lZxiThduaWLI`EMSA5B4s&w_ekNbx{j zMMTe8qU}vq*xHoOz>!!|y^w3%9-i|qpy|4@pNSs9y~S5{*cjvu=Q^FA>U~_+_c8IP z+HqyN``ep`W?i4Tw;a@%R22N(StD5VT3Czs`0w`7oVNiwx&=*-EDQNqe(F*WFSqU? zf1EqLs4=n_oqCR+HyMrhVMmNjjW=y>NbTCl2}L2uDl9(z_AE}2dcG%T@3p9|dH-;8 z)%K247r034*y>XsJc0e>rOMj65ra2#mv4qjMz?pl5B{Y_dU!^cMa8K4&4ZH+ie108 z$z&(px8^}r<^ZbMK9GQU<+#fg&5kuR3$hEhrMAU{yy~*d+asPOe9Kdx33m1S6nbo1jIm}h1pY^GmOe#wmeG87y5Uk85WKAz6}{g+s-yX8uB zO-c{O)R&dwy%Hz*-REnE&8_Vwch~8mZzMlQ5a9b>>urE&Dq{rr$A4TgiQi#bd8TK& zq&;M!4Gkb>Ab_MCZa3n##4ZNv>&9|z5*9r=VLZhiw{E3(tQ8J&b$x{ykGAQ7#r#n7 zaOI(M{C~9NB?rvHxQgDO*;RXmeW)GjZq2DmIoj85@0d3fuva~(9d#lUaYbMZp+4@_ zq~SpqjR9TZ$Fw$+Nzn|^oj-k}|B&ZDn|=klAMG$b8UOjf$RSZq;WqB*>4DWq+~tff z|45L!j#SStq@pgvmlcFJ0m3n+MHv`Y>`#+!A+syZy+a59Wm=rz(cE$JME3=RlzLGyBHIK2y#Hs&Tl>vShecF{BK0p{TWt1pzEBm7_>>m_zH#hHxUpn}%M&4Vv zs7yfsq!T4f(RlWE~TNPQ@dedgzi?^Y%*SIRT>|*W^rj{#|eCj^lyI)ZGcJXoK zk+%15-p53mX^+j_*8E`3D1n03zUG!*u`K>oG@v(;;oPK`S-OteUuG#v5Qkx2EC`g3 z_hM>254libUNS1IiSc1JdVfw~ES;{&@imCtG}%CT7Q5Wme*h0+g4azDSWhcql|L?b zDa?%RURjX5NPpzr5o0^5Z)VbmL3;i0W2oul1JpLTSq^ynt2N446%1IWF#}BOGMfY2 z=O!z9QR4@G~$ ztJ;#oe;;niE>S$imQ5m{Z?2F>-rV1S8`1MSKvcdE^)kpz)F65{9o%M&9C3o&)B#c# zjr%Z9Yqs=y?0h73(79tqP%*zd-Mw+4HRhB9iRXw_y;f{3_xSmlL_Zl$;UP%3Yyh`bBFRD;S!ydM6J0@Z)HkdF(T%Te zOY)AWr?8TJu}j)H-u#9FQnYv+)DLd)pkXm%$*jYP)f@&UXHLa?i1)*>G-V79r(IS` z*8_!5e68Qj?{7v~i^t&KX)Yj{Jz|7ua%G#Zt!(sC{`m^0j^*N#RU0fegssYE+y(5q zfu5$dcfTjBs=!3p)nQ=P@-(4QELlMU@oQ5SaJ}mM#vF|FxR_Xwtoz8SEYF=_WeSQI z5Mg>=79COH@Tg_M2UPZP=ZN`{DT!-&h?h7GUPT@wZxN{jeyIqy?b8eWuPpqclQWnqb; zW$}cyT2N@9_%Eayb^u9vx$O>XFE~C{Tma)RQSKa(Abe6>2peHRU!t#}f)vWz$&321 z*pds%Fc)~qAaHW52Nal&JT+^m?=S$dZPAcm9`=@_xWp&L$BQ`>>*zD$#|Q&9BXfTa z8<@d>n(&;1$edMuliFgK>%S?<0vHwziKTfK&sdnY?y)>K2_TFzARJ%FM!aEs*#6Et zgC%}*U)9tq6Mn#9tUTFgBDO(2_pBA@kIPCED#s2aUtgg!di~%;ng@JankSK4ixgmQ{6>xa5v0U;9xoIcQzchW*ud$ls!ZTX#^6Ph$C1#N1g5E-)mqR;dY_B?f7&V=o=^@{lS z51-wKDZ*Ic<3W|(j#xP0AFnmm#f0pHdO+bpzpHz|?p7GDZD(*{%ov zWrGY!A?1?>Q$5Gqo#0_Xp9t6wb>sr(Zp0eo8Vy{rV$PhDdg9Aw0}fp43N#I0&b!1) zZ2@IbNn=^vD3EH($pLQ@tf9Fvsk8hWp_|O)h&`=KOY>}!KA&V!J29VCbR_UccJ0JZ zZLT4`NE2@D++<7KV|7|nV832mC#d=ZwgJmjSqiFZf5E2C2K76V9!ioa3)g~!SuNPX z3O}XM6WArS9Pcbbe?-lEASigt-GM2(#vNbnpw{_{gK1@ew-W)UOyYdonUxVMlz;89 zx_>BI_e4P5pRHIYXlI)djzg`*)68N+v}XDvUd`!HdohVa5!|`=2=x+6Nvz^f^?An3 zwDoSRjre|sXlZNG*2lo~-lsZj=wwxIrzWz+l2jziA@j}G-^HIUv1SO;vqXfWNE?2O zh_Y)|zxz#@^9*Q*TH7#Ysl!xs6p$>6{Za+t+1w`t*02U>c#(LKPN_5B!2VUPJCcr~ z{5L!rGuz9g&oIT3Qp(w7RR^7u+60cTkO6u(1FCcGw3#tj8>>Nr-1{pZ|DOSo-7UOChxLy5eX>a&`f=QqVWx%~R| zInsf&XV(yC3eAjEnzj87T+HMxo}TkA6@ecwg3 z@0T*36dhswmN1g{1RvasX*$I=#M{WY$)aAS$Ix;Yym{(u+;uo~GN^u6f*mh%(D%sJ z+9s0x?57xbZ_Uv4g8Gt)%lWu*AWp`W!@%zAo`N~ z0Xac=we#y^M!L}vscE7Y2myVdDM3Rvz_S=-nx(mMdtGg!m<->e%?6n&N8?}4xlr>N zjWNG=V~1|RCLoa9QH1PYWH@=HYtpq`2smjOQuXSUyZRbwQv4P<2HzxOy@UHCOZgb- zN%6z4YpryT(-vhDi8QF(;H$PtIb!KyQBy~i2w_p$tt#y5EM@v(A{?PJ?uo;Ju9NgP z;_R8%*!FswXKU*YCDA?%AlO5<-c+n~u0;}jOj%9-sKva|M)M2rx!OcXeG zofR$-KVL?!(|XgCp1tb#s7N4yC|CXh7#@LL>sL5uSdd(PXUIc(oX7{SLx=%T40n|x zsX$!8f>RKcOB>39k3tzafZ2K+*6NOP>wEJu2@mN9kK(W!ptHT9(r@LXj-C9O>G)#O zA4EUByE0yzz;j254m`eRfk+U)LmuA;9k?sj3ITU}4F#4Gr2D9wy{c;(wRD%}M#u+* zcd}1nkhZraw`AxZxDERk*!s8^V>R^j){ezzo|vw(QqX*YBEj6_2&_ZRFC98)s-QD_ z1EC|yAP{gIfz$mI>kb|qvaS3XZvsJjOxzQovdL=`SfIo@@p7Iq0RYEIc=6onA2x7q zfb;;_1b1O(ztg;u9u_ZNMx^ebyJZ8KH}|Z{5*kUukGtt9BHRgDsU~_cXlB>G)W$aD zwI4OEX{QRQdR@qFNC$M#YOBM^*KQ_8L~P6GU!ZNFse@Ta(kN&G5B9?j8(wL%#pjF% zUplD1UQUyP=;l|q4R0qnSv)2=6X9W9-Ao#-1v85(xhKh5A>@1}PMNm)xXveJXGY6a z2VVz<)`s$(pi&rE90J3boE;El;E%*2Q)7?^OsN1`ABRp$!^F6PQMSl)xb({3rrLa`2eHuhx|VVknZc1ADl$gk2Gq5zo(@*t%YCDfDRj*tA*=b6ik zcn(^Hn&`(;YC_>e&#}Ugcmm%be69^xh?fUoUhw5NUT1;+vHhoU`o(!p0-!sn15JTr zRY3!@H&y9b-b%Fi0i#Kl(+?Dr+C`( zS{%S%lIeqhL_@|!q%fl3<8BRRk!vs`2n=4Jag=rSUW43y4ZHVl#`5vm*WRpHZ1ma- z>W)|u8`gI%)6%>`h;fQr+ftFzsTbzxYaTvP|&m%}L6(9#DG@wVlICPV0cmCm* z1FVLLsbO@^!ucN9$U>-xOLKYu)9*a^22pdbP(kkiB429jCc^G!ipIOWqhiD@U*cuG zHlVu~QV~_!O*zr|X?|#U6aD4B9W`d~s3-q$o zM(-k@Kyra13W3gTBWdB|ZuO7j6uhU<0H{lM10ax+X_$Mao)mF=Yxpto3%Zi*Z-9rC z&&$YulM}T+8{tlr4jn=FZUKAgo+Tcc3s)8*!^Mk8DDbVMMDUcRh-Z*YY805nj0aKk zFl^MI+28qIFf@D!ncyc=>_U!th^O!_qytUyh&U! zDf-o(scJG=?#;or ze~JDUK!5NwyHO%QZ*m7ZY9H&T;O&$FXL3QnM&kp}vtWS7nP(LPEL*_Piq{BSDV@ojZ8(*FTRVzrgOW9jFNm zd$9l~BUBJGAP7quDt_0!2iqEXXSD#`uY!s_eFbq+bF-pNQmyEgsL*s-bgyO|@g1YU zE-IWDip9uy`2hAVMu+hhoq`4o;d@lTy(~lnG15&lE#TBI4QD_S7!fX3hPTU$prmww zKRuO+ZOZ18?7|$xz9wc0kQw?h!-}f6OY`jOfHEah^cx5W^o#*R!oh{b?xPv4hH%xq zKNzop$CREyrW#?J?%?evGsrgi7AR-m1I`RB_WxLn^iw>q18)XApk=8;FII$^-KBZH zJPp^SrYQ?y(}Bxp2T@lkqzGQkYljGexCj=m?+4v-x4;v{9s-|?XF9Y6yX#PrD|%f` zGOUlARo6V;Pwn|=ktH_ukyy#3AwG7s%yilW{O(b#(~e1p+rJdm5Q4 zWDFUawra#bmE1%l1OX^za+GorObR7n_>gs-Ox;2&LgAi7;np`Y==|yHWcShW;Wi`- zZb8E;5j+9({4`A3MYi4pRtd>o{21jv7o{Y+M&Vxp+*MvLrRL$;F{rKW1Ii@Tl}es-SWnv?x)1h1qI0(MgBBjTp38Qp`KglLTRa%3ma4hqn}4ovY{jv zXc_hl5aD1<<}Ia4dm~Ju3mZ(4AU8Xdbp0ceyZ8fkL3iXj%}NufqX$*TYH5iUPWt@g zZnFp%u>^5P16&;lI7goU2qu4e7u;3kX+=gVrywsBu+DP!aa0LD5#dB9th|g@Df;o# zCnSdw9@9S4MyX2E{tjg?3y?MmqI)=7ct_kmSIYu5?PXMY0niU+LiB+Zz7sI4%^C`yy%24<$%C*6~yr)y>{el;5P;lw|~K5 zx@fDR0GB?rW9$>_BK|;8dEPyQV_%3+fckzwKh{@HT+Wgvho2TiM9zWm195Btk!Vk1 z5J>ygEZx}A!=eowTL4sTjUO=xv!|X#au7c|ny(D0gH+)mB~)2t(E0P}r%SjlP?UgQ zmjJ;7Vu@nAEvp@5TCX=y?lqvUzQsEL3&NJK9ciwK`1lK#^1p!sz)i;QUOH5Lqe^V< zSRrG7l1Y*Qqb4PBH}W@pX;WN}7!Idq>J3*xNqYeeph+Jsb^|me16nWcr%zebjN-pXjxI}X(nM2EJ0Y}}0(BtC zKx*!enRjA%OSX@?EMyNn(QBU`U5h|qC6}>kNKWFXtRG~dNRSM8?JEta3;o;x6n&0G ztpoFJG1X)otc@sv0dIMjUYSuCpgv^KJ}_VQ+YWDl8i^{{CkuBmyv9 zA4=dIANc|s;g-rA8J2944S3tG!opjd75=w=;q-s^tRql ul > li { + border-radius: 0.4rem; + transition: border-color 125ms, box-shadow 125ms; +} + +.md-typeset .grid.cards > ul > li:hover { + border-color: var(--cc-brand); + box-shadow: 0 0 0 1px var(--cc-brand); +} + +.md-typeset .grid.cards > ul > li > hr { + margin: 0.6rem 0; +} + +/* Tables ------------------------------------------------------------------- + Cells align to the top so short values line up with a wrapped message. */ +.md-typeset table:not([class]) td, +.md-typeset table:not([class]) th { + vertical-align: top; +} + +/* In the rule index tables only, keep codes, names and CLI flags on one line + so the tables stay scannable. Other tables hold long regexes and lists that + must stay wrappable. */ +.md-typeset table.rules-index td code { + white-space: nowrap; +} + +/* Sidebar section headings ("Getting started", "Reference", ...) */ +.md-nav__item--section > .md-nav__link { + font-weight: 700; + color: var(--md-default-fg-color); +} diff --git a/docs/assets/favicon.svg b/docs/assets/favicon.svg new file mode 100644 index 00000000..896e52aa --- /dev/null +++ b/docs/assets/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg new file mode 100644 index 00000000..3aee9298 --- /dev/null +++ b/docs/assets/logo.svg @@ -0,0 +1,8 @@ + diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 00000000..d5178863 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,187 @@ +# Changelog + +All **notable changes** to this project will be documented in this file. + +Full changelog available at [GitHub releases](https://github.com/commit-check/commit-check/releases). + +## v2.11.0 (2026-07-06) + +### New Features + +* **AI attribution governance** — Added support for forbidding known AI tool + signatures (e.g., `Co-authored-by: Copilot`) in commit messages. New + `[commit]` config option `forbid_ai_attribution` (boolean, default + `false`) rejects commits co-authored by AI coding agents. See PR [#456](https://github.com/commit-check/commit-check/pull/456). + +### Bug Fixes + +* Fixed `MergeBaseValidator` branch detection — replaced `git branch -a` + regex matching with `git rev-parse --verify` to avoid false positives + (e.g., pattern `main` matching `main-staging`). See PR [#451](https://github.com/commit-check/commit-check/pull/451). + +### Chores + +* Added OpenSSF Scorecard workflow, badge, and pinned dependency SHAs for CI +* Migrated PyPI publishing to `pypa/gh-action-pypi-publish` +* Removed OpenSSF Scorecard badge after evaluation (moved to Scorecard dashboard) + +## v2.10.1 (2026-06-30) + +### Bug Fixes + +* **WIP detection case-insensitivity** — `WIP` (`[WIP]`, `WIP:`, `wip:`, + etc.) is now recognized regardless of case across all common patterns. + See PR [#448](https://github.com/commit-check/commit-check/pull/448). +* **Conventional commit special characters** — Allowed special characters + (parentheses, brackets, etc.) in the description part of conventional commit + messages. See PR [#447](https://github.com/commit-check/commit-check/pull/447). + +### Refactors + +* Extracted `_get_commit_message` to `BaseValidator` to remove code + duplication across validators. See PR [#445](https://github.com/commit-check/commit-check/pull/445). +* Removed legacy YAML config parsing code from `util.py`. + See PR [#444](https://github.com/commit-check/commit-check/pull/444). + +## v2.10.0 (2026-06-26) + +### New Features + +* **Dependabot / Renovate as default branch type** — `dependabot/` and + `renovate/` branch prefixes are now included in `DEFAULT_BRANCH_TYPES`, + so dependency update branches are automatically recognized. + See PR [#442](https://github.com/commit-check/commit-check/pull/442). + +## v2.9.0 (2026-06-22) + +### New Features + +* **AI agent branch prefixes (Conventional Branch v1.1.0)** — Added + `ai/`, `claude/`, `codex/`, `copilot/`, and `cursor/` to + `DEFAULT_BRANCH_TYPES` so branches created by AI coding agents are + recognized as valid. See PR [#438](https://github.com/commit-check/commit-check/pull/438). + +## v2.8.1 (2026-06-22) + +### Chores + +* Fixed 27 SonarQube code-quality issues across source and test files, + including path traversal vulnerability fix, cognitive complexity + reduction, and duplicate branch consolidation. See PR [#436](https://github.com/commit-check/commit-check/pull/436). +* Added SchemaStore IDE autocompletion support for `cchk.toml`. + See PR [#433](https://github.com/commit-check/commit-check/pull/433). + +## v2.8.0 (2026-06-13) + +### New Features + +* **Custom commit message pattern** — New `message_pattern` option in the + `[commit]` config section allows replacing the built-in Conventional Commits + regex with a user-defined regex pattern. Also supported via the + `CCHK_MESSAGE_PATTERN` environment variable. See PR [#427](https://github.com/commit-check/commit-check/pull/427). + +### Breaking Changes + +* **Dropped Python 3.9 support** — Minimum required Python version is now + 3.10. Type annotations have been modernized (PEP 604/585) and the + `py.typed` marker added for downstream type checkers. + See PR [#424](https://github.com/commit-check/commit-check/pull/424). + +## v2.7.1 (2026-06-08) + +### Chores + +* Added `auto` to the list of imperative verbs. See PR [#417](https://github.com/commit-check/commit-check/pull/417). +* Added commit-check vs GitHub Rulesets comparison table to the README. + See PR [#419](https://github.com/commit-check/commit-check/pull/419). + +## v2.7.0 (2026-05-16) + +### New Features + +* **Force push detection and blocking** — Added `--no-force-push` CLI flag and + `check-no-force-push` pre-push hook that inspect pushed ref ancestry via + `git merge-base --is-ancestor` to detect and block `git push --force` and + `git push -f`. A new `[push]` TOML config section with + `allow_force_push` (default `true`) controls the behavior. Environment + variable `CCHK_ALLOW_FORCE_PUSH` is also supported. + +* **`validate_push()` API** — New `commit_check.api.validate_push()` + function for programmatic push safety checks, matching the `--no-force-push` + CLI behavior without spawning a subprocess. + +* **Standalone mode** — When `--no-force-push` is run outside a pre-push hook + (no stdin), it checks whether pushing `HEAD` to its configured upstream + would require force, using `git ls-remote` and optional `git fetch` to + resolve the remote commit. + +* **Expanded imperative verbs** — Added 156 new imperative verbs across 10 + categories (auth/security, data ops, lifecycle, I/O, debugging, UI/UX, + engineering, general), growing the total from 234 to 390. + See PR [#414](https://github.com/commit-check/commit-check/pull/414). + +## v2.6.0 (2026-04-20) + +### New Features + +* **Lower-noise CLI failure output** — Added `--no-banner` to suppress the ASCII art header while preserving detailed errors and suggestions. +* **Compact failure mode** — Added `--compact` to print one `[FAIL]` line per failing check for CI logs and automation-friendly terminal output. This mode also suppresses the banner. + +### Bug Fixes + +* Fixed `print_error_header` state handling so repeated validations stay consistent when `--compact` is used. + +## v2.5.0 (2026-04-03) + +### New Features + +* **Co-author bypass in `ignore_authors`** — `_should_skip_commit_validation()` now parses `Co-authored-by:` trailers in the commit message body. If any co-author name matches `ignore_authors`, all commit checks are skipped. Useful for AI bots that co-author commits (e.g., `coderabbitai[bot]`). +* **Organization-level config inheritance via `inherit_from`** — New top-level TOML key that loads a parent config from a GitHub shorthand (`github:owner/repo:path`), a local file path, or an HTTPS URL, then deep-merges it with local settings. HTTP (non-TLS) URLs are rejected to prevent MITM attacks. +* **Git config author validation** — `AuthorValidator` now checks `git config user.name` / `user.email` first (the identity used for the *next* commit), falling back to `git log` if unset. Previously, a misconfigured identity would pass if the last commit had a valid author. + +### Bug Fixes + +* Fixed incorrect mock target in `test_main_with_message_empty_string_no_stdin_with_git`: was patching `commit_check.util.get_commit_info` (ineffective) instead of `commit_check.engine.get_commit_info`. + +## v2.0.0 (2025-10-01) + +.. Attention:: + This major release introduces significant architectural changes and breaking updates to commit-check. Please review carefully before upgrading. + +### What's New + +* **TOML Configuration** — Replaces the old `.commit-check.yml` with `cchk.toml` or `commit-check.toml` for clearer syntax. +* **Simplified CLI & Hooks** — Legacy pre-commit hooks and command-line options have been removed for a cleaner, more consistent interface. +* **New Validation Engine** — The validation system has been completely redesigned around a new ValidationEngine to improve maintainability and flexibility. + +#### Breaking Changes + +Configuration Format: + +* `.commit-check.yml` has been replaced with `cchk.toml` or `commit-check.toml`. +* All YAML configurations must be migrated to TOML from this version onward. +* See the [Migration Guide](migration.md) for step-by-step instructions. + +Removed Pre-commit Hooks and CLI Options: + +* Several legacy hooks and command-line flags have been removed in favor of a simplified interface. +* Removed hooks: `check-commit-signoff`, `check-merge-base`, `check-imperative`. +* Removed CLI options: `--signoff`, `--merge-base`, `--imperative`. + +Module Removal: + +* The following legacy modules have been removed: `author.py`, `branch.py`, `commit.py`, `error.py`. + +Architecture Redesign: + +* The validation system has been completely restructured around the new `ValidationEngine`, breaking compatibility with any code or integrations relying on the old module structure. + +See PR [#280](https://github.com/commit-check/commit-check/pull/280) + +## v0.10.2 (2025-08-26) + +Last release before the big v2.0 changes. + +## v0.1.0 (2022-11-02) + +Initial release of commit-check. \ No newline at end of file diff --git a/docs/changelog.rst b/docs/changelog.rst deleted file mode 100644 index a64330e8..00000000 --- a/docs/changelog.rst +++ /dev/null @@ -1,227 +0,0 @@ -Changelog -========= - -All **notable changes** to this project will be documented in this file. - -Full changelog available at `GitHub releases `_. - -v2.11.0 (2026-07-06) --------------------- - -New Features -~~~~~~~~~~~~ - -* **AI attribution governance** — Added support for forbidding known AI tool - signatures (e.g., ``Co-authored-by: Copilot``) in commit messages. New - ``[commit]`` config option ``forbid_ai_attribution`` (boolean, default - ``false``) rejects commits co-authored by AI coding agents. See PR :pr:`456`. - -Bug Fixes -~~~~~~~~~ - -* Fixed ``MergeBaseValidator`` branch detection — replaced ``git branch -a`` - regex matching with ``git rev-parse --verify`` to avoid false positives - (e.g., pattern ``main`` matching ``main-staging``). See PR :pr:`451`. - -Chores -~~~~~~ - -* Added OpenSSF Scorecard workflow, badge, and pinned dependency SHAs for CI -* Migrated PyPI publishing to ``pypa/gh-action-pypi-publish`` -* Removed OpenSSF Scorecard badge after evaluation (moved to Scorecard dashboard) - - -v2.10.1 (2026-06-30) --------------------- - -Bug Fixes -~~~~~~~~~ - -* **WIP detection case-insensitivity** — ``WIP`` (``[WIP]``, ``WIP:``, ``wip:``, - etc.) is now recognized regardless of case across all common patterns. - See PR :pr:`448`. -* **Conventional commit special characters** — Allowed special characters - (parentheses, brackets, etc.) in the description part of conventional commit - messages. See PR :pr:`447`. - -Refactors -~~~~~~~~~ - -* Extracted ``_get_commit_message`` to ``BaseValidator`` to remove code - duplication across validators. See PR :pr:`445`. -* Removed legacy YAML config parsing code from ``util.py``. - See PR :pr:`444`. - - -v2.10.0 (2026-06-26) --------------------- - -New Features -~~~~~~~~~~~~ - -* **Dependabot / Renovate as default branch type** — ``dependabot/`` and - ``renovate/`` branch prefixes are now included in ``DEFAULT_BRANCH_TYPES``, - so dependency update branches are automatically recognized. - See PR :pr:`442`. - - -v2.9.0 (2026-06-22) -------------------- - -New Features -~~~~~~~~~~~~ - -* **AI agent branch prefixes (Conventional Branch v1.1.0)** — Added - ``ai/``, ``claude/``, ``codex/``, ``copilot/``, and ``cursor/`` to - ``DEFAULT_BRANCH_TYPES`` so branches created by AI coding agents are - recognized as valid. See PR :pr:`438`. - - -v2.8.1 (2026-06-22) -------------------- - -Chores -~~~~~~ - -* Fixed 27 SonarQube code-quality issues across source and test files, - including path traversal vulnerability fix, cognitive complexity - reduction, and duplicate branch consolidation. See PR :pr:`436`. -* Added SchemaStore IDE autocompletion support for ``cchk.toml``. - See PR :pr:`433`. - - -v2.8.0 (2026-06-13) -------------------- - -New Features -~~~~~~~~~~~~ - -* **Custom commit message pattern** — New ``message_pattern`` option in the - ``[commit]`` config section allows replacing the built-in Conventional Commits - regex with a user-defined regex pattern. Also supported via the - ``CCHK_MESSAGE_PATTERN`` environment variable. See PR :pr:`427`. - -Breaking Changes -~~~~~~~~~~~~~~~~ - -* **Dropped Python 3.9 support** — Minimum required Python version is now - 3.10. Type annotations have been modernized (PEP 604/585) and the - ``py.typed`` marker added for downstream type checkers. - See PR :pr:`424`. - - -v2.7.1 (2026-06-08) -------------------- - -Chores -~~~~~~ - -* Added ``auto`` to the list of imperative verbs. See PR :pr:`417`. -* Added commit-check vs GitHub Rulesets comparison table to the README. - See PR :pr:`419`. - - -v2.7.0 (2026-05-16) -------------------- - -New Features -~~~~~~~~~~~~ - -* **Force push detection and blocking** — Added ``--no-force-push`` CLI flag and - ``check-no-force-push`` pre-push hook that inspect pushed ref ancestry via - ``git merge-base --is-ancestor`` to detect and block ``git push --force`` and - ``git push -f``. A new ``[push]`` TOML config section with - ``allow_force_push`` (default ``true``) controls the behavior. Environment - variable ``CCHK_ALLOW_FORCE_PUSH`` is also supported. - -* **``validate_push()`` API** — New ``commit_check.api.validate_push()`` - function for programmatic push safety checks, matching the ``--no-force-push`` - CLI behavior without spawning a subprocess. - -* **Standalone mode** — When ``--no-force-push`` is run outside a pre-push hook - (no stdin), it checks whether pushing ``HEAD`` to its configured upstream - would require force, using ``git ls-remote`` and optional ``git fetch`` to - resolve the remote commit. - -* **Expanded imperative verbs** — Added 156 new imperative verbs across 10 - categories (auth/security, data ops, lifecycle, I/O, debugging, UI/UX, - engineering, general), growing the total from 234 to 390. - See PR :pr:`414`. - - -v2.6.0 (2026-04-20) -------------------- - -New Features -~~~~~~~~~~~~ - -* **Lower-noise CLI failure output** — Added ``--no-banner`` to suppress the ASCII art header while preserving detailed errors and suggestions. -* **Compact failure mode** — Added ``--compact`` to print one ``[FAIL]`` line per failing check for CI logs and automation-friendly terminal output. This mode also suppresses the banner. - -Bug Fixes -~~~~~~~~~ - -* Fixed ``print_error_header`` state handling so repeated validations stay consistent when ``--compact`` is used. - -v2.5.0 (2026-04-03) -------------------- - -New Features -~~~~~~~~~~~~ - -* **Co-author bypass in ``ignore_authors``** — ``_should_skip_commit_validation()`` now parses ``Co-authored-by:`` trailers in the commit message body. If any co-author name matches ``ignore_authors``, all commit checks are skipped. Useful for AI bots that co-author commits (e.g., ``coderabbitai[bot]``). -* **Organization-level config inheritance via ``inherit_from``** — New top-level TOML key that loads a parent config from a GitHub shorthand (``github:owner/repo:path``), a local file path, or an HTTPS URL, then deep-merges it with local settings. HTTP (non-TLS) URLs are rejected to prevent MITM attacks. -* **Git config author validation** — ``AuthorValidator`` now checks ``git config user.name`` / ``user.email`` first (the identity used for the *next* commit), falling back to ``git log`` if unset. Previously, a misconfigured identity would pass if the last commit had a valid author. - -Bug Fixes -~~~~~~~~~ - -* Fixed incorrect mock target in ``test_main_with_message_empty_string_no_stdin_with_git``: was patching ``commit_check.util.get_commit_info`` (ineffective) instead of ``commit_check.engine.get_commit_info``. - -v2.0.0 (2025-10-01) -------------------- - -.. Attention:: - This major release introduces significant architectural changes and breaking updates to commit-check. Please review carefully before upgrading. - -What's New -~~~~~~~~~~ - -* **TOML Configuration** — Replaces the old ``.commit-check.yml`` with ``cchk.toml`` or ``commit-check.toml`` for clearer syntax. -* **Simplified CLI & Hooks** — Legacy pre-commit hooks and command-line options have been removed for a cleaner, more consistent interface. -* **New Validation Engine** — The validation system has been completely redesigned around a new ValidationEngine to improve maintainability and flexibility. - -Breaking Changes -^^^^^^^^^^^^^^^^ - -Configuration Format: - -* ``.commit-check.yml`` has been replaced with ``cchk.toml`` or ``commit-check.toml``. -* All YAML configurations must be migrated to TOML from this version onward. -* See the `Migration Guide `_ for step-by-step instructions. - -Removed Pre-commit Hooks and CLI Options: - -* Several legacy hooks and command-line flags have been removed in favor of a simplified interface. -* Removed hooks: ``check-commit-signoff``, ``check-merge-base``, ``check-imperative``. -* Removed CLI options: ``--signoff``, ``--merge-base``, ``--imperative``. - -Module Removal: - -* The following legacy modules have been removed: ``author.py``, ``branch.py``, ``commit.py``, ``error.py``. - -Architecture Redesign: - -* The validation system has been completely restructured around the new ``ValidationEngine``, breaking compatibility with any code or integrations relying on the old module structure. - -See PR :pr:`280` - -v0.10.2 (2025-08-26) --------------------- - -Last release before the big v2.0 changes. - -v0.1.0 (2022-11-02) -------------------- - -Initial release of commit-check. diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 405949dc..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,161 +0,0 @@ -# pylint: disable=all -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html -import re -import datetime -from pathlib import Path -import subprocess -from sphinx.application import Sphinx - -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = "commit-check" -copyright = f"{datetime.date.today().year}, shenxianpeng" -author = "shenxianpeng" - -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = [ - "myst_parser", - "sphinx_immaterial", - "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", - "sphinx.ext.viewcode", - "sphinx_issues", -] - -source_suffix = { - ".rst": "restructuredtext", - ".md": "markdown", -} - -# Treat bare URLs as external links so MyST does not warn about TOC -# anchor references (e.g. `(#overview)`) being unresolved cross-references. -myst_all_links_external = True - -autodoc_member_order = "bysource" - -templates_path = ["_templates"] -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - -default_role = "any" - -# -- Options for sphinx_issues -------------------------------------------------------- -issues_default_group_project = "commit-check/commit-check" - -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output - -html_theme = "sphinx_immaterial" -html_static_path = ["_static"] -# html_logo = "_static/logo.jpg" can not display well in blue background -# html_favicon = "_static/favicon.ico" -html_css_files = ["extra_css.css"] -html_title = "Commit Check" - -html_theme_options = { - "repo_url": "https://github.com/commit-check/commit-check", - "repo_name": "commit-check", - "icon": { - "logo": "material/git", - }, - "palette": [ - { - "media": "(prefers-color-scheme: light)", - "scheme": "default", - "primary": "blue", - "accent": "light-blue", - "toggle": { - "icon": "material/lightbulb-outline", - "name": "Switch to dark mode", - }, - }, - { - "media": "(prefers-color-scheme: dark)", - "scheme": "slate", - "primary": "blue", - "accent": "light-blue", - "toggle": { - "icon": "material/lightbulb", - "name": "Switch to light mode", - }, - }, - ], - # The global navigation lives in the left sidebar (no top tab bar), with - # each toctree caption rendered as a section heading. This keeps every - # page one click away and leaves the right-hand column for the page's own - # table of contents. - "features": [ - "navigation.sections", - "navigation.top", - "navigation.tracking", - "toc.sticky", - "toc.follow", - "search.highlight", - "search.share", - ], - # Keep the sidebar sections expanded rather than collapsing everything but - # the current page, so the whole documentation set is visible at a glance. - "globaltoc_collapse": False, -} - -object_description_options = [ - ("py:parameter", {"include_in_toc": False}), -] - -sphinx_immaterial_custom_admonitions = [ - { - "name": "seealso", - "color": (215, 59, 205), - "icon": "octicons/eye-24", - "override": True, - }, - { - "name": "note", - "icon": "material/file-document-edit-outline", - "override": True, - }, -] -for name in ("hint", "tip", "important"): - sphinx_immaterial_custom_admonitions.append( - {"name": name, "icon": "material/school", "override": True} - ) - - -def setup(app: Sphinx): - """Generate a doc from the executable script's ``--help`` output.""" - - result = subprocess.run( - ["commit-check", "--help"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding="utf-8", - ) - doc = "commit-check --help\n==============================\n\n" - CLI_OPT_NAME = re.compile(r"^\s*(\-\w)(?:\s+[A-Z_\[\]]*)?(?:,\s+(\-\-[a-z\-]+))?") - in_options_section = False - - for line in result.stdout.splitlines(): - # Start processing options when we see the "options:" line - if line.strip() == "options:": - in_options_section = True - doc += line + "\n" - continue - - # Only process option patterns in the options section - if in_options_section: - match = CLI_OPT_NAME.search(line) - if match is not None: - short_opt = match.group(1) - long_opt = match.group(2) - if short_opt and long_opt: - doc += "\n.. std:option:: " + short_opt + ", " + long_opt + "\n\n" - elif short_opt: - doc += "\n.. std:option:: " + short_opt + "\n\n" - - doc += line + "\n" - cli_doc = Path(app.srcdir, "cli_args.rst") - cli_doc.unlink(missing_ok=True) - cli_doc.write_text(doc) diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..ac72ea3f --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,305 @@ +# Configuration + +`commit-check` can be configured in three ways with the following priority (highest to lowest): + +1. **Command-line arguments** (`--subject-imperative=true`) +2. **Environment variables** (`CCHK_SUBJECT_IMPERATIVE=true`) +3. **Configuration files** (`cchk.toml` or `commit-check.toml`) +4. **Built-in defaults** + +This flexibility allows you to: + +* Use configuration files for project-wide settings +* Override with environment variables in CI/CD pipelines +* Override specific settings via CLI for one-off checks +* Use without any configuration files (relies on defaults) + +## Configuration Files + +`commit-check` configuration files support the TOML format. See `cchk.toml` for an example configuration. + +!!! tip "Default Behavior" + + * When no configuration file exists, commit-check uses sensible defaults with minimal restrictions. + * Enforced by default: the Conventional Commits format ([CC001](rules.md#cc001)), the Conventional Branch format ([CC201](rules.md#cc201)), the subject length limits of 5–80 characters ([CC004](rules.md#cc004), [CC005](rules.md#cc005)), and the author name and email patterns ([CC101](rules.md#cc101), [CC102](rules.md#cc102)). + * **Off** by default: subject capitalization, imperative mood, body and signoff requirements, rebase requirements, and every `allow_*` restriction. + + See [rules](rules.md) for the default state of every rule. + +commit-check can be configured via a `cchk.toml` or `commit-check.toml` file. + +The file should be placed in the root of your repository or in the `.github` folder. + +## Configuration File Locations + +commit-check searches for configuration files in the following order (first found is used): + +1. `cchk.toml` (root directory) +2. `commit-check.toml` (root directory) +3. `.github/cchk.toml` +4. `.github/commit-check.toml` + +!!! tip "GitHub Best Practice" + + Placing configuration files in the `.github` folder helps keep your repository root clean and follows GitHub conventions used by tools like Dependabot and Renovate. + +!!! tip "IDE Autocompletion" + + commit-check's TOML schema is published on [SchemaStore](https://www.schemastore.org/), + so editors like VS Code (via [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml)), + PyCharm, and IntelliJ provide autocompletion, validation, and documentation + tooltips for `cchk.toml` out of the box — no manual schema path configuration needed. + +## Organization-Level Configuration (inherit_from) + +For organizations that want to share a common base configuration across many repositories, commit-check supports an `inherit_from` directive at the top level of your TOML config file. + +**How it works:** + +1. The `inherit_from` value can be a `github:` shorthand, a local file path, or an HTTPS URL. +2. The parent (inherited) configuration is loaded first. +3. Local settings in the current config file **override** the parent values. +4. The `inherit_from` key itself is not passed to the validation engine. + +**Example — inherit from a GitHub repository (recommended):** + +```toml +# .github/cchk.toml +inherit_from = "github:my-org/.github:cchk.toml" + +[commit] +subject_max_length = 72 # Overrides parent value +``` + +**GitHub shorthand format:** + +* `github:owner/repo:path/to/cchk.toml` — uses `HEAD` (default branch) +* `github:owner/repo@main:path/to/cchk.toml` — pins to the `main` branch + +**Example — inherit from a local file:** + +```toml +# repo/.github/cchk.toml +inherit_from = "../../shared/org-cchk.toml" + +[commit] +allow_wip_commits = true # Override for this project only +``` + +**Example — inherit from an HTTPS URL:** + +```toml +# .github/cchk.toml +inherit_from = "https://example.com/shared/cchk.toml" +``` + +!!! note + + If the `inherit_from` target is unreachable or the format is unrecognized, commit-check silently ignores the inheritance and uses only the local configuration. HTTP (non-TLS) URLs are rejected for security. + +## Example Configuration + +```toml +[commit] +# https://www.conventionalcommits.org +conventional_commits = true +# message_pattern = "" # Optional - custom regex (overrides conventional_commits) +subject_capitalized = false +subject_imperative = false +subject_max_length = 80 # Default - set to your own limit +subject_min_length = 5 # Default - set to your own minimum +allow_commit_types = ["feat", "fix", "docs", "style", "refactor", "test", "chore"] +allow_merge_commits = true +allow_revert_commits = true +allow_empty_commits = false +allow_fixup_commits = true +allow_wip_commits = false +require_body = false +# ignore_authors = [] # Optional - bypass checks for these commit/co-authors +require_signed_off_by = false +ai_attribution = "forbid" # "ignore" (default) or "forbid" — rejects AI tool trailers + +[push] +# Block force pushes when used as a pre-push hook or with --no-force-push +allow_force_push = true # Set to false to block force pushes + +[branch] +# https://conventionalbranch.org +conventional_branch = true +# Optional: defaults are a superset of the Conventional Branch spec — the +# spec types plus Conventional Commit types, AI agent prefixes and bot +# prefixes (see the Options table below for the full list). Omit this +# option to use the defaults, or set your own list for a strict subset. +allow_branch_types = [ + "feature", + "bugfix", + "hotfix", + "release", + "chore", + "feat", + "fix", + "build", + "ci", + "docs", + "perf", + "refactor", + "style", + "test", +] +# allow_branch_names = [] # Optional - additional standalone branch names (e.g., ["develop", "staging"]) +# require_rebase_target = "main" # Optional - no rebase requirement by default +# ignore_authors = [] # Optional - no authors ignored by default +``` + +## Command-Line Arguments + +All configuration options can be specified via command-line arguments, which take precedence over environment variables and configuration files. + +**Syntax:** + +* Boolean options: `--option-name=true` or `--option-name=false` +* Integer options: `--option-name=80` +* List options: `--option-name=value1,value2,value3` (comma-separated) +* String options: `--option-name=value` + +**Examples:** + +```bash +# Disable imperative mood check +commit-check --message --subject-imperative=false + +# Set custom subject length limit +commit-check --message --subject-max-length=72 + +# Restrict allowed commit types +commit-check --message --allow-commit-types=feat,fix,docs + +# Combine multiple options +commit-check --message --subject-imperative=true --subject-max-length=50 --allow-commit-types=feat,fix + +# Branch configuration via CLI +commit-check --branch --allow-branch-types=feature,bugfix,hotfix +``` + +**Pre-commit Hook Usage:** + +The primary use case for CLI arguments is configuring commit-check in `.pre-commit-config.yaml` without requiring a TOML file: + +```yaml +repos: + - repo: https://github.com/commit-check/commit-check + rev: v2.5.0 + hooks: + - id: check-message + args: + - --subject-imperative=false + - --subject-max-length=100 + - --allow-merge-commits=false +``` + +## Environment Variables + +Configuration can also be set via environment variables with the `CCHK_` prefix. This is useful for CI/CD pipelines and temporary overrides. + +**Naming Convention:** + +* Convert option name to uppercase +* Replace hyphens with underscores +* Add `CCHK_` prefix + +**Examples:** + +```bash +# Set boolean options +export CCHK_SUBJECT_IMPERATIVE=true +export CCHK_SUBJECT_CAPITALIZED=false + +# Set integer options +export CCHK_SUBJECT_MAX_LENGTH=72 +export CCHK_SUBJECT_MIN_LENGTH=10 + +# Set list options (comma-separated) +export CCHK_ALLOW_COMMIT_TYPES=feat,fix,docs,chore +export CCHK_ALLOW_BRANCH_TYPES=feature,bugfix,hotfix + +# Set string options +export CCHK_REQUIRE_REBASE_TARGET=main + +# Use in CI/CD +CCHK_SUBJECT_MAX_LENGTH=100 commit-check --message +``` + +**Complete Mapping:** + +| TOML Config | Environment Variable | CLI Argument | +|---|---|---| +| `conventional_commits = true` | `CCHK_CONVENTIONAL_COMMITS=true` | `--conventional-commits=true` | +| `message_pattern = "^PROJ-\\d+: .+"` | `CCHK_MESSAGE_PATTERN=^PROJ-\\d+: .+` | N/A (config file only) | +| `subject_capitalized = false` | `CCHK_SUBJECT_CAPITALIZED=false` | `--subject-capitalized=false` | +| `subject_imperative = true` | `CCHK_SUBJECT_IMPERATIVE=true` | `--subject-imperative=true` | +| `subject_max_length = 80` | `CCHK_SUBJECT_MAX_LENGTH=80` | `--subject-max-length=80` | +| `subject_min_length = 5` | `CCHK_SUBJECT_MIN_LENGTH=5` | `--subject-min-length=5` | +| `allow_commit_types = ["feat", "fix"]` | `CCHK_ALLOW_COMMIT_TYPES=feat,fix` | `--allow-commit-types=feat,fix` | +| `allow_merge_commits = true` | `CCHK_ALLOW_MERGE_COMMITS=true` | `--allow-merge-commits=true` | +| `allow_revert_commits = true` | `CCHK_ALLOW_REVERT_COMMITS=true` | `--allow-revert-commits=true` | +| `allow_empty_commits = false` | `CCHK_ALLOW_EMPTY_COMMITS=false` | `--allow-empty-commits=false` | +| `allow_fixup_commits = true` | `CCHK_ALLOW_FIXUP_COMMITS=true` | `--allow-fixup-commits=true` | +| `allow_wip_commits = false` | `CCHK_ALLOW_WIP_COMMITS=false` | `--allow-wip-commits=false` | +| `require_body = false` | `CCHK_REQUIRE_BODY=false` | `--require-body=false` | +| `require_signed_off_by = false` | `CCHK_REQUIRE_SIGNED_OFF_BY=false` | `--require-signed-off-by=false` | +| `ignore_authors = ["bot"]` | `CCHK_IGNORE_AUTHORS=bot,user` | `--ignore-authors=bot,user` | +| `author_email_pattern=^.+@example\.com$` | `CCHK_AUTHOR_EMAIL_PATTERN=^.+@example\.com$` | `--author-email-pattern=^.+@example\.com$` | +| `author_name_pattern=^.+ .+$` | `CCHK_AUTHOR_NAME_PATTERN=^.+ .+$` | `--author-name-pattern=^.+ .+$` | +| `conventional_branch = true` | `CCHK_CONVENTIONAL_BRANCH=true` | `--conventional-branch=true` | +| `allow_branch_types = ["feature"]` | `CCHK_ALLOW_BRANCH_TYPES=feature,bugfix` | `--allow-branch-types=feature,bugfix` | +| `allow_branch_names = ["develop"]` | `CCHK_ALLOW_BRANCH_NAMES=develop,staging` | `--allow-branch-names=develop,staging` | +| `require_rebase_target = "main"` | `CCHK_REQUIRE_REBASE_TARGET=main` | `--require-rebase-target=main` | +| `allow_force_push = true` | `CCHK_ALLOW_FORCE_PUSH=false` | `--no-force-push` (enable via `--no-force-push` flag) | +| `ai_attribution = "forbid"` | `CCHK_AI_ATTRIBUTION=forbid` | `--ai-attribution=forbid` | +| `ignore_authors = ["bot"]` (in branch section) | `CCHK_BRANCH_IGNORE_AUTHORS=bot,user` | `--branch-ignore-authors=bot,user` | + +## Configuration Priority Example + +When the same option is specified in multiple places, the priority determines which value is used: + +```bash +# In cchk.toml: +# subject_max_length = 100 + +# Set via environment: +export CCHK_SUBJECT_MAX_LENGTH=80 + +# Override via CLI: +commit-check --message --subject-max-length=50 + +# Result: subject_max_length = 50 (CLI wins) +``` + +## Options Table Description + +| Section | Option | Type | Default | Description | +|---|---|---|---|---| +| commit | conventional_commits | bool | true | Enforce Conventional Commits specification. | +| commit | message_pattern | str | "" (disabled) | Custom regex pattern for commit message validation. When set, this pattern replaces the auto-generated Conventional Commits regex entirely, making it possible to enforce custom formats such as JIRA smart commits (e.g., `"^PROJ-\\d+: .+"`). When `message_pattern` is set (non-empty) it takes precedence over `conventional_commits`. | +| commit | subject_capitalized | bool | false | Subject must start with a capital letter. | +| commit | subject_imperative | bool | false | Subject must be in imperative mood. Forms of verbs can be found at [imperatives.py](https://github.com/commit-check/commit-check/blob/main/commit_check/imperatives.py) | +| commit | subject_max_length | int | 80 | Maximum length of the subject line. | +| commit | subject_min_length | int | 5 | Minimum length of the subject line. | +| commit | allow_commit_types | list[str] | ["feat", "fix", "docs", "style", "refactor", "test", "chore", "perf", "build", "ci"] | Allowed commit types when conventional_commits is true. | +| commit | allow_merge_commits | bool | true | Allow merge commits. | +| commit | allow_revert_commits | bool | true | Allow revert commits. | +| commit | allow_empty_commits | bool | true | Allow empty commits. | +| commit | allow_fixup_commits | bool | true | Allow fixup commits (e.g., "fixup! "). | +| commit | allow_wip_commits | bool | true | Allow work-in-progress commits (e.g., "WIP: "). | +| commit | require_body | bool | false | Require a body in the commit message. | +| commit | ignore_authors | list[str] | [] (none ignored) | List of commit authors **or co-authors** (`Co-authored-by:` lines) to bypass all commit checks. Useful for bots (e.g., `"dependabot[bot]"`, `"coderabbitai[bot]"`). | +| commit | author_email_pattern | str | `^.+@.+$` | Custom regex for the author email check. When empty, the built-in default pattern is used. This option only takes effect when the author_email check is enabled (`-e` / `--author-email`). | +| commit | author_name_pattern | str | "" (built-in default) | Custom regex for the author name check. When empty, the built-in default pattern is used (it is not disabled). This option only takes effect when the author_name check is enabled (`-n` / `--author-name`). | +| commit | require_signed_off_by | bool | false | Require "Signed-off-by" line in the commit message footer. | +| commit | ai_attribution | str | "ignore" | AI attribution policy. `"forbid"` rejects any commit containing known AI tool signatures (Claude Code, Copilot, Codex, Gemini, Cursor, Devin, Aider, Windsurf, Tabby, and generic AI model patterns). `"ignore"` disables the check. This feature is a response to the industry-wide discussion on AI disclosure in open source (Linux kernel `Assisted-by:` trailer, CPython, VS Code, Apache, Fedora policies). | +| branch | conventional_branch | bool | true | Enforce Conventional Branch specification. | +| branch | allow_branch_types | list[str] | ["feature", "bugfix", "hotfix", "release", "chore", "feat", "fix", "build", "ci", "docs", "perf", "refactor", "style", "test", "ai", "claude", "codex", "copilot", "cursor", "dependabot", "renovate"] | Allowed branch types when `conventional_branch` is true. The default is a superset of the [Conventional Branch spec](https://conventionalbranch.org/): the spec types (`feature`, `bugfix`, `hotfix`, `release`, `chore`) plus the Conventional Commit types (`build`, `ci`, `docs`, `perf`, `refactor`, `style`, `test`), AI agent prefixes (`ai`, `claude`, `codex`, `copilot`, `cursor`) and bot prefixes (`dependabot`, `renovate`). For strict spec-only validation, set this option explicitly (e.g. `["feature", "bugfix", "hotfix", "release", "chore"]`). | +| branch | allow_branch_names | list[str] | [] (empty list) | Additional standalone branch names allowed when conventional_branch is true (e.g., ["develop", "staging"]). By default, master, main, HEAD, and PR-* are always allowed. | +| branch | require_rebase_target | str | "" (no requirement) | Target branch for rebase requirement. If not set, no rebase validation is performed. | +| push | allow_force_push | bool | true | Allow force pushes. Set to `false` to block force pushes when used as a pre-push hook or with `--no-force-push`. | +| branch | ignore_authors | list[str] | [] (none ignored) | List of authors to ignore (i.e., always allow). | diff --git a/docs/configuration.rst b/docs/configuration.rst deleted file mode 100644 index 27e05772..00000000 --- a/docs/configuration.rst +++ /dev/null @@ -1,476 +0,0 @@ -Configuration -============= - -``commit-check`` can be configured in three ways with the following priority (highest to lowest): - -1. **Command-line arguments** (``--subject-imperative=true``) -2. **Environment variables** (``CCHK_SUBJECT_IMPERATIVE=true``) -3. **Configuration files** (``cchk.toml`` or ``commit-check.toml``) -4. **Built-in defaults** - -This flexibility allows you to: - -* Use configuration files for project-wide settings -* Override with environment variables in CI/CD pipelines -* Override specific settings via CLI for one-off checks -* Use without any configuration files (relies on defaults) - -Configuration Files -------------------- - -``commit-check`` configuration files support the TOML format. See ``cchk.toml`` for an example configuration. - -.. tip:: - **Default Behavior** - - * When no configuration file exists, commit-check uses sensible defaults with minimal restrictions. - * Enforced by default: the Conventional Commits format (:ref:`CC001 `), the Conventional Branch format (:ref:`CC201 `), the subject length limits of 5–80 characters (:ref:`CC004 `, :ref:`CC005 `), and the author name and email patterns (:ref:`CC101 `, :ref:`CC102 `). - * **Off** by default: subject capitalization, imperative mood, body and signoff requirements, rebase requirements, and every ``allow_*`` restriction. - - See :doc:`rules` for the default state of every rule. - -commit-check can be configured via a ``cchk.toml`` or ``commit-check.toml`` file. - -The file should be placed in the root of your repository or in the ``.github`` folder. - -Configuration File Locations ------------------------------ - -commit-check searches for configuration files in the following order (first found is used): - -1. ``cchk.toml`` (root directory) -2. ``commit-check.toml`` (root directory) -3. ``.github/cchk.toml`` -4. ``.github/commit-check.toml`` - -.. tip:: - **GitHub Best Practice** - - Placing configuration files in the ``.github`` folder helps keep your repository root clean and follows GitHub conventions used by tools like Dependabot and Renovate. - -.. tip:: - **IDE Autocompletion** - - commit-check's TOML schema is published on `SchemaStore `_, - so editors like VS Code (via `Even Better TOML `_), - PyCharm, and IntelliJ provide autocompletion, validation, and documentation - tooltips for ``cchk.toml`` out of the box — no manual schema path configuration needed. - -Organization-Level Configuration (inherit_from) -------------------------------------------------- - -For organizations that want to share a common base configuration across many repositories, commit-check supports an ``inherit_from`` directive at the top level of your TOML config file. - -**How it works:** - -1. The ``inherit_from`` value can be a ``github:`` shorthand, a local file path, or an HTTPS URL. -2. The parent (inherited) configuration is loaded first. -3. Local settings in the current config file **override** the parent values. -4. The ``inherit_from`` key itself is not passed to the validation engine. - -**Example — inherit from a GitHub repository (recommended):** - -.. code-block:: toml - - # .github/cchk.toml - inherit_from = "github:my-org/.github:cchk.toml" - - [commit] - subject_max_length = 72 # Overrides parent value - -**GitHub shorthand format:** - -* ``github:owner/repo:path/to/cchk.toml`` — uses ``HEAD`` (default branch) -* ``github:owner/repo@main:path/to/cchk.toml`` — pins to the ``main`` branch - -**Example — inherit from a local file:** - -.. code-block:: toml - - # repo/.github/cchk.toml - inherit_from = "../../shared/org-cchk.toml" - - [commit] - allow_wip_commits = true # Override for this project only - -**Example — inherit from an HTTPS URL:** - -.. code-block:: toml - - # .github/cchk.toml - inherit_from = "https://example.com/shared/cchk.toml" - -.. note:: - If the ``inherit_from`` target is unreachable or the format is unrecognized, commit-check silently ignores the inheritance and uses only the local configuration. HTTP (non-TLS) URLs are rejected for security. - -Example Configuration ---------------------- - -.. code-block:: toml - :class: copy - - [commit] - # https://www.conventionalcommits.org - conventional_commits = true - # message_pattern = "" # Optional - custom regex (overrides conventional_commits) - subject_capitalized = false - subject_imperative = false - subject_max_length = 80 # Default - set to your own limit - subject_min_length = 5 # Default - set to your own minimum - allow_commit_types = ["feat", "fix", "docs", "style", "refactor", "test", "chore"] - allow_merge_commits = true - allow_revert_commits = true - allow_empty_commits = false - allow_fixup_commits = true - allow_wip_commits = false - require_body = false - # ignore_authors = [] # Optional - bypass checks for these commit/co-authors - require_signed_off_by = false - ai_attribution = "forbid" # "ignore" (default) or "forbid" — rejects AI tool trailers - - [push] - # Block force pushes when used as a pre-push hook or with --no-force-push - allow_force_push = true # Set to false to block force pushes - - [branch] - # https://conventionalbranch.org - conventional_branch = true - # Optional: defaults are a superset of the Conventional Branch spec — the - # spec types plus Conventional Commit types, AI agent prefixes and bot - # prefixes (see the Options table below for the full list). Omit this - # option to use the defaults, or set your own list for a strict subset. - allow_branch_types = [ - "feature", - "bugfix", - "hotfix", - "release", - "chore", - "feat", - "fix", - "build", - "ci", - "docs", - "perf", - "refactor", - "style", - "test", - ] - # allow_branch_names = [] # Optional - additional standalone branch names (e.g., ["develop", "staging"]) - # require_rebase_target = "main" # Optional - no rebase requirement by default - # ignore_authors = [] # Optional - no authors ignored by default - - -Command-Line Arguments ----------------------- - -All configuration options can be specified via command-line arguments, which take precedence over environment variables and configuration files. - -**Syntax:** - -* Boolean options: ``--option-name=true`` or ``--option-name=false`` -* Integer options: ``--option-name=80`` -* List options: ``--option-name=value1,value2,value3`` (comma-separated) -* String options: ``--option-name=value`` - -**Examples:** - -.. code-block:: bash - - # Disable imperative mood check - commit-check --message --subject-imperative=false - - # Set custom subject length limit - commit-check --message --subject-max-length=72 - - # Restrict allowed commit types - commit-check --message --allow-commit-types=feat,fix,docs - - # Combine multiple options - commit-check --message --subject-imperative=true --subject-max-length=50 --allow-commit-types=feat,fix - - # Branch configuration via CLI - commit-check --branch --allow-branch-types=feature,bugfix,hotfix - -**Pre-commit Hook Usage:** - -The primary use case for CLI arguments is configuring commit-check in ``.pre-commit-config.yaml`` without requiring a TOML file: - -.. code-block:: yaml - - repos: - - repo: https://github.com/commit-check/commit-check - rev: v2.5.0 - hooks: - - id: check-message - args: - - --subject-imperative=false - - --subject-max-length=100 - - --allow-merge-commits=false - -Environment Variables ---------------------- - -Configuration can also be set via environment variables with the ``CCHK_`` prefix. This is useful for CI/CD pipelines and temporary overrides. - -**Naming Convention:** - -* Convert option name to uppercase -* Replace hyphens with underscores -* Add ``CCHK_`` prefix - -**Examples:** - -.. code-block:: bash - - # Set boolean options - export CCHK_SUBJECT_IMPERATIVE=true - export CCHK_SUBJECT_CAPITALIZED=false - - # Set integer options - export CCHK_SUBJECT_MAX_LENGTH=72 - export CCHK_SUBJECT_MIN_LENGTH=10 - - # Set list options (comma-separated) - export CCHK_ALLOW_COMMIT_TYPES=feat,fix,docs,chore - export CCHK_ALLOW_BRANCH_TYPES=feature,bugfix,hotfix - - # Set string options - export CCHK_REQUIRE_REBASE_TARGET=main - - # Use in CI/CD - CCHK_SUBJECT_MAX_LENGTH=100 commit-check --message - -**Complete Mapping:** - -.. list-table:: - :header-rows: 1 - - * - TOML Config - - Environment Variable - - CLI Argument - * - ``conventional_commits = true`` - - ``CCHK_CONVENTIONAL_COMMITS=true`` - - ``--conventional-commits=true`` - * - ``message_pattern = "^PROJ-\\d+: .+"`` - - ``CCHK_MESSAGE_PATTERN=^PROJ-\\d+: .+`` - - N/A (config file only) - * - ``subject_capitalized = false`` - - ``CCHK_SUBJECT_CAPITALIZED=false`` - - ``--subject-capitalized=false`` - * - ``subject_imperative = true`` - - ``CCHK_SUBJECT_IMPERATIVE=true`` - - ``--subject-imperative=true`` - * - ``subject_max_length = 80`` - - ``CCHK_SUBJECT_MAX_LENGTH=80`` - - ``--subject-max-length=80`` - * - ``subject_min_length = 5`` - - ``CCHK_SUBJECT_MIN_LENGTH=5`` - - ``--subject-min-length=5`` - * - ``allow_commit_types = ["feat", "fix"]`` - - ``CCHK_ALLOW_COMMIT_TYPES=feat,fix`` - - ``--allow-commit-types=feat,fix`` - * - ``allow_merge_commits = true`` - - ``CCHK_ALLOW_MERGE_COMMITS=true`` - - ``--allow-merge-commits=true`` - * - ``allow_revert_commits = true`` - - ``CCHK_ALLOW_REVERT_COMMITS=true`` - - ``--allow-revert-commits=true`` - * - ``allow_empty_commits = false`` - - ``CCHK_ALLOW_EMPTY_COMMITS=false`` - - ``--allow-empty-commits=false`` - * - ``allow_fixup_commits = true`` - - ``CCHK_ALLOW_FIXUP_COMMITS=true`` - - ``--allow-fixup-commits=true`` - * - ``allow_wip_commits = false`` - - ``CCHK_ALLOW_WIP_COMMITS=false`` - - ``--allow-wip-commits=false`` - * - ``require_body = false`` - - ``CCHK_REQUIRE_BODY=false`` - - ``--require-body=false`` - * - ``require_signed_off_by = false`` - - ``CCHK_REQUIRE_SIGNED_OFF_BY=false`` - - ``--require-signed-off-by=false`` - * - ``ignore_authors = ["bot"]`` - - ``CCHK_IGNORE_AUTHORS=bot,user`` - - ``--ignore-authors=bot,user`` - * - ``author_email_pattern=^.+@example\.com$`` - - ``CCHK_AUTHOR_EMAIL_PATTERN=^.+@example\.com$`` - - ``--author-email-pattern=^.+@example\.com$`` - * - ``author_name_pattern=^.+ .+$`` - - ``CCHK_AUTHOR_NAME_PATTERN=^.+ .+$`` - - ``--author-name-pattern=^.+ .+$`` - * - ``conventional_branch = true`` - - ``CCHK_CONVENTIONAL_BRANCH=true`` - - ``--conventional-branch=true`` - * - ``allow_branch_types = ["feature"]`` - - ``CCHK_ALLOW_BRANCH_TYPES=feature,bugfix`` - - ``--allow-branch-types=feature,bugfix`` - * - ``allow_branch_names = ["develop"]`` - - ``CCHK_ALLOW_BRANCH_NAMES=develop,staging`` - - ``--allow-branch-names=develop,staging`` - * - ``require_rebase_target = "main"`` - - ``CCHK_REQUIRE_REBASE_TARGET=main`` - - ``--require-rebase-target=main`` - * - ``allow_force_push = true`` - - ``CCHK_ALLOW_FORCE_PUSH=false`` - - ``--no-force-push`` (enable via ``--no-force-push`` flag) - * - ``ai_attribution = "forbid"`` - - ``CCHK_AI_ATTRIBUTION=forbid`` - - ``--ai-attribution=forbid`` - * - ``ignore_authors = ["bot"]`` (in branch section) - - ``CCHK_BRANCH_IGNORE_AUTHORS=bot,user`` - - ``--branch-ignore-authors=bot,user`` - - -Configuration Priority Example -------------------------------- - -When the same option is specified in multiple places, the priority determines which value is used: - -.. code-block:: bash - - # In cchk.toml: - # subject_max_length = 100 - - # Set via environment: - export CCHK_SUBJECT_MAX_LENGTH=80 - - # Override via CLI: - commit-check --message --subject-max-length=50 - - # Result: subject_max_length = 50 (CLI wins) - - -Options Table Description -------------------------- - -.. list-table:: - :header-rows: 1 - - * - Section - - Option - - Type - - Default - - Description - * - commit - - conventional_commits - - bool - - true - - Enforce Conventional Commits specification. - * - commit - - message_pattern - - str - - "" (disabled) - - Custom regex pattern for commit message validation. When set, this pattern replaces the auto-generated Conventional Commits regex entirely, making it possible to enforce custom formats such as JIRA smart commits (e.g., ``"^PROJ-\\d+: .+"``). When ``message_pattern`` is set (non-empty) it takes precedence over ``conventional_commits``. - * - commit - - subject_capitalized - - bool - - false - - Subject must start with a capital letter. - * - commit - - subject_imperative - - bool - - false - - Subject must be in imperative mood. Forms of verbs can be found at `imperatives.py `_ - * - commit - - subject_max_length - - int - - 80 - - Maximum length of the subject line. - * - commit - - subject_min_length - - int - - 5 - - Minimum length of the subject line. - * - commit - - allow_commit_types - - list[str] - - ["feat", "fix", "docs", "style", "refactor", "test", "chore", "perf", "build", "ci"] - - Allowed commit types when conventional_commits is true. - * - commit - - allow_merge_commits - - bool - - true - - Allow merge commits. - * - commit - - allow_revert_commits - - bool - - true - - Allow revert commits. - * - commit - - allow_empty_commits - - bool - - true - - Allow empty commits. - * - commit - - allow_fixup_commits - - bool - - true - - Allow fixup commits (e.g., "fixup! "). - * - commit - - allow_wip_commits - - bool - - true - - Allow work-in-progress commits (e.g., "WIP: "). - * - commit - - require_body - - bool - - false - - Require a body in the commit message. - * - commit - - ignore_authors - - list[str] - - [] (none ignored) - - List of commit authors **or co-authors** (``Co-authored-by:`` lines) to bypass all commit checks. Useful for bots (e.g., ``"dependabot[bot]"``, ``"coderabbitai[bot]"``). - * - commit - - author_email_pattern - - str - - ``^.+@.+$`` - - Custom regex for the author email check. When empty, the built-in default pattern is used. - This option only takes effect when the author_email check is enabled (``-e`` / ``--author-email``). - * - commit - - author_name_pattern - - str - - "" (built-in default) - - Custom regex for the author name check. When empty, the built-in default pattern is used (it is not disabled). - This option only takes effect when the author_name check is enabled (``-n`` / ``--author-name``). - * - commit - - require_signed_off_by - - bool - - false - - Require "Signed-off-by" line in the commit message footer. - * - commit - - ai_attribution - - str - - "ignore" - - AI attribution policy. ``"forbid"`` rejects any commit containing known AI tool signatures (Claude Code, Copilot, Codex, Gemini, Cursor, Devin, Aider, Windsurf, Tabby, and generic AI model patterns). ``"ignore"`` disables the check. This feature is a response to the industry-wide discussion on AI disclosure in open source (Linux kernel ``Assisted-by:`` trailer, CPython, VS Code, Apache, Fedora policies). - * - branch - - conventional_branch - - bool - - true - - Enforce Conventional Branch specification. - * - branch - - allow_branch_types - - list[str] - - ["feature", "bugfix", "hotfix", "release", "chore", "feat", "fix", "build", "ci", "docs", "perf", "refactor", "style", "test", "ai", "claude", "codex", "copilot", "cursor", "dependabot", "renovate"] - - Allowed branch types when ``conventional_branch`` is true. The default is a superset of the `Conventional Branch spec `_: the spec types (``feature``, ``bugfix``, ``hotfix``, ``release``, ``chore``) plus the Conventional Commit types (``build``, ``ci``, ``docs``, ``perf``, ``refactor``, ``style``, ``test``), AI agent prefixes (``ai``, ``claude``, ``codex``, ``copilot``, ``cursor``) and bot prefixes (``dependabot``, ``renovate``). For strict spec-only validation, set this option explicitly (e.g. ``["feature", "bugfix", "hotfix", "release", "chore"]``). - * - branch - - allow_branch_names - - list[str] - - [] (empty list) - - Additional standalone branch names allowed when conventional_branch is true (e.g., ["develop", "staging"]). By default, master, main, HEAD, and PR-* are always allowed. - * - branch - - require_rebase_target - - str - - "" (no requirement) - - Target branch for rebase requirement. If not set, no rebase validation is performed. - * - push - - allow_force_push - - bool - - true - - Allow force pushes. Set to ``false`` to block force pushes when used as a pre-push hook or with ``--no-force-push``. - * - branch - - ignore_authors - - list[str] - - [] (none ignored) - - List of authors to ignore (i.e., always allow). diff --git a/docs/example.md b/docs/example.md new file mode 100644 index 00000000..371e079f --- /dev/null +++ b/docs/example.md @@ -0,0 +1,374 @@ +# Usage Examples + +This guide demonstrates how to use commit-check to validate commit messages, branch names, and author information. + +There are several ways to use commit-check: as a pre-commit hook, via STDIN, or directly with files. + +## Running as GitHub Action + +Please see [commit-check/commit-check-action](https://github.com/commit-check/commit-check-action) + +## Running as pre-commit hook + +1. **Install pre-commit:** + +!!! tip + + Make sure `pre-commit` is [installed](https://pre-commit.com/#install). + +```bash +pip install pre-commit +``` + +2. **Create .pre-commit-config.yaml:** + +```yaml +- repo: https://github.com/commit-check/commit-check + rev: the tag or revision + hooks: + - id: check-message + stages: [commit-msg] + - id: check-branch + - id: check-author-name + - id: check-author-email +``` + +3. **Install the hooks:** + +```bash +pre-commit install --hook-type pre-commit --hook-type commit-msg +``` + +4. **Test the integration:** + +```bash +# This will trigger validation automatically +git commit -m "feat: add new user authentication system" +``` + +### Pre-commit Validation Examples + +**✅ Successful Validation:** + +```text +$ git commit -m "feat: add user authentication system" + +check commit message.....................................................Passed +check committer name.....................................................Passed +check committer email....................................................Passed +[main abc1234] feat: add user authentication system +``` + +**❌ Failed Validation:** + +```text +$ git commit -m "bad commit message" + +check commit message.....................................................Failed +- hook id: check-message +- exit code: 1 + +Commit rejected by Commit-Check. + + (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) + / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ + __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ +(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) + || E || || R || || R || || O || || R || + _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ +(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) + `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ + +Commit rejected. + +Type message check failed ==> bad commit message +It doesn't match regex: ^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+ +The commit message should follow Conventional Commits. See https://www.conventionalcommits.org +Suggest: Use (): with allowed types +``` + +## Running as CLI + +Commit-check provides several command-line options for different validation scenarios. via options or STDIN + +.. tip :: + Validate commit messages by piping them through STDIN. This is useful for testing or scripting. + +Available Commands see [commit-check --help](cli.md) + +### Message Validation Examples + +```bash +# Validate message from STDIN +echo "feat: new feature" | commit-check -m + +# Validate message from file +commit-check -m commit_message.txt + +# Validate current git commit message (from git log) +commit-check -m +``` + +**Reading from file:** + +```bash +# Create a commit message file +cat > commit_message.txt << EOF +fix(auth): resolve login timeout issue + +Users were experiencing timeouts during login. +Increased session timeout and improved error handling. + +Fixes #123 +EOF + +# Validate from file +commit-check -m commit_message.txt + +# Or pipe file content +cat commit_message.txt | commit-check -m +``` + +### Branch Validation Examples + +```bash +# Check current branch name +commit-check --branch + +# Example valid branch names: +# - feature/user-auth +# - fix/login-bug +# - hotfix/security-patch +# - release/v1.2.0 +``` + +### Push Validation Examples + +```bash +# Check whether pushing HEAD to its configured upstream would require force +commit-check --no-force-push +``` + +```yaml +# Configure the dedicated pre-push hook +- repo: https://github.com/commit-check/commit-check + rev: the tag or revision + hooks: + - id: check-no-force-push + stages: [pre-push] +``` + +`git push | commit-check --no-force-push` is not a prevention mechanism. The +push has already started, and normal `git push` output does not include the +pre-push ref lines that Git provides to hooks. + +### Author Validation Examples + +```bash +# Check author name +commit-check --author-name + +# Check author email +commit-check --author-email + +# Check both author name and email +commit-check --author-name --author-email +``` + +### Configuration Examples + +```bash +# Use custom configuration file +echo "feat: test" | commit-check --config my-config.toml -m + +# Use configuration from different directory +commit-check --config /path/to/config/cchk.toml -m +``` + +### Valid Commit Message Examples + +```bash +# Basic feature +echo "feat: add user registration" | commit-check -m + +# Feature with scope +echo "feat(auth): implement OAuth2 login" | commit-check -m + +# Bug fix +echo "fix: resolve memory leak in parser" | commit-check -m + +# Documentation update +echo "docs: add installation guide" | commit-check -m + +# Breaking change +echo "feat!: redesign API endpoints" | commit-check -m + +# Merge commit (automatically allowed) +echo "Merge pull request #123 from feature/new-api" | commit-check -m +``` + +### Invalid Commit Message Examples + +```bash +# No type prefix +echo "added new feature" | commit-check -m + +# Capitalized (if configured to disallow) +echo "feat: Add new feature" | commit-check -m + +# Too short +echo "fix" | commit-check -m + +# Non-imperative mood +echo "feat: added login functionality" | commit-check -m + +# Unknown type +echo "unknown: some changes" | commit-check -m +``` + +### Error Output Examples + +**Commit Message Validation Failure:** + +```text +Commit rejected by Commit-Check. + + (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) + / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ + __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ +(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) + || E || || R || || R || || O || || R || + _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ +(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) + `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ + +Commit rejected. + +Type message check failed ==> test commit message check +It doesn't match regex: ^(chore|ci|docs|feat|fix|refactor|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)|(Merge).*|(fixup!.*) +The commit message should follow Conventional Commits. See https://www.conventionalcommits.org +Suggest: Use (): with allowed types +``` + +**Branch Name Validation Failure:** + +```text +Commit rejected by Commit-Check. + + (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) + / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ + __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ +(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) + || E || || R || || R || || O || || R || + _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ +(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) + `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ + +Commit rejected. + +Type branch check failed ==> test-branch +It doesn't match regex: ^(feature|bugfix|hotfix|release|chore|feat|fix)\/.+|(master)|(main)|(HEAD)|(PR-.+) +The branch should follow Conventional Branch. See https://conventionalbranch.org +Suggest: Use / with allowed types or ignore_authors in config branch section to bypass +``` + +**Commit Signature Validation Failure:** + +```text +Commit rejected by Commit-Check. + + (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) + / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ + __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ +(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) + || E || || R || || R || || O || || R || + _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ +(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) + `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ + +Commit rejected. + +Type require_signed_off_by check failed ==> fix: add missing file +It doesn't match regex: Signed-off-by:.*[A-Za-z0-9]\s+<.+@.+> +Signed-off-by not found in latest commit +Suggest: git commit --amend --signoff or use --signoff on commit +``` + +**Commit Message Validation Failure without ASCII Banner (`--no-banner`):** + +```text +Type message check failed ==> test commit message check +It doesn't match regex: ^(chore|ci|docs|feat|fix|refactor|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)|(Merge).*|(fixup!.*) +The commit message should follow Conventional Commits. See https://www.conventionalcommits.org +Suggest: Use (): with allowed types +``` + +**Compact Failure Output (`--compact`):** + +```text +[FAIL] message: test commit message check +``` + +**Imperative Mood Validation Failure:** + +```text +Commit rejected by Commit-Check. + + (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) + / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ + __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ +(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) + || E || || R || || R || || O || || R || + _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ +(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) + `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ + +Commit rejected. + +Type imperative check failed ==> fix: added missing file +It doesn't match regex: +Commit message should use imperative mood (e.g., 'Add feature' not 'Added feature') +Suggest: Use imperative mood in the subject line +``` + +## Integration Tips + +### CI/CD Integration + +You can use commit-check in CI/CD pipelines: + +```bash +# In your CI script +git log --format="%s" -n 1 | commit-check -m + +# or just +commit-check -m + +# Keep plain-text output but remove the ASCII art banner +git log --format="%s" -n 1 | commit-check -m --no-banner + +# Emit one machine-friendly line per failure without switching to JSON +git log --format="%s" -n 1 | commit-check -m --compact +``` + +### Scripting + +Use commit-check in scripts to validate commit messages programmatically: + +```bash +#!/bin/bash +# validate-commits.sh + +# Get all commit messages from last 10 commits +for i in {0..9}; do + msg=$(git log --format="%s" -n 1 --skip=$i) + if [ -n "$msg" ]; then + echo "Validating: $msg" + echo "$msg" | commit-check -m || exit 1 + fi +done + +echo "All commits are valid!" +``` + +For more configuration options, see the [Configuration Documentation](configuration.md). \ No newline at end of file diff --git a/docs/example.rst b/docs/example.rst deleted file mode 100644 index 4ac73653..00000000 --- a/docs/example.rst +++ /dev/null @@ -1,397 +0,0 @@ -Usage Examples -============== - -This guide demonstrates how to use commit-check to validate commit messages, branch names, and author information. - -There are several ways to use commit-check: as a pre-commit hook, via STDIN, or directly with files. - -Running as GitHub Action ------------------------- - -Please see `commit-check/commit-check-action `_ - -Running as pre-commit hook ---------------------------- - -1. **Install pre-commit:** - -.. tip:: - - Make sure ``pre-commit`` is `installed `_. - -.. code-block:: bash - - pip install pre-commit - -2. **Create .pre-commit-config.yaml:** - -.. code-block:: yaml - - - repo: https://github.com/commit-check/commit-check - rev: the tag or revision - hooks: - - id: check-message - stages: [commit-msg] - - id: check-branch - - id: check-author-name - - id: check-author-email - -3. **Install the hooks:** - -.. code-block:: bash - - pre-commit install --hook-type pre-commit --hook-type commit-msg - -4. **Test the integration:** - -.. code-block:: bash - - # This will trigger validation automatically - git commit -m "feat: add new user authentication system" - - -Pre-commit Validation Examples -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**✅ Successful Validation:** - -.. code-block:: text - - $ git commit -m "feat: add user authentication system" - - check commit message.....................................................Passed - check committer name.....................................................Passed - check committer email....................................................Passed - [main abc1234] feat: add user authentication system - -**❌ Failed Validation:** - -.. code-block:: text - - $ git commit -m "bad commit message" - - check commit message.....................................................Failed - - hook id: check-message - - exit code: 1 - - Commit rejected by Commit-Check. - - (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) - / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ - __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ - (_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) - || E || || R || || R || || O || || R || - _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ - (.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) - `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ - - Commit rejected. - - Type message check failed ==> bad commit message - It doesn't match regex: ^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+ - The commit message should follow Conventional Commits. See https://www.conventionalcommits.org - Suggest: Use (): with allowed types - - -Running as CLI --------------- - -Commit-check provides several command-line options for different validation scenarios. via options or STDIN - -.. tip :: - Validate commit messages by piping them through STDIN. This is useful for testing or scripting. - -Available Commands see `commit-check --help `_ - -Message Validation Examples -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - # Validate message from STDIN - echo "feat: new feature" | commit-check -m - - # Validate message from file - commit-check -m commit_message.txt - - # Validate current git commit message (from git log) - commit-check -m - - -**Reading from file:** - -.. code-block:: bash - - # Create a commit message file - cat > commit_message.txt << EOF - fix(auth): resolve login timeout issue - - Users were experiencing timeouts during login. - Increased session timeout and improved error handling. - - Fixes #123 - EOF - - # Validate from file - commit-check -m commit_message.txt - - # Or pipe file content - cat commit_message.txt | commit-check -m - - -Branch Validation Examples -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - # Check current branch name - commit-check --branch - - # Example valid branch names: - # - feature/user-auth - # - fix/login-bug - # - hotfix/security-patch - # - release/v1.2.0 - -Push Validation Examples -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - # Check whether pushing HEAD to its configured upstream would require force - commit-check --no-force-push - -.. code-block:: yaml - - # Configure the dedicated pre-push hook - - repo: https://github.com/commit-check/commit-check - rev: the tag or revision - hooks: - - id: check-no-force-push - stages: [pre-push] - -``git push | commit-check --no-force-push`` is not a prevention mechanism. The -push has already started, and normal ``git push`` output does not include the -pre-push ref lines that Git provides to hooks. - -Author Validation Examples -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - # Check author name - commit-check --author-name - - # Check author email - commit-check --author-email - - # Check both author name and email - commit-check --author-name --author-email - - -Configuration Examples -~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - # Use custom configuration file - echo "feat: test" | commit-check --config my-config.toml -m - - # Use configuration from different directory - commit-check --config /path/to/config/cchk.toml -m - - -Valid Commit Message Examples -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - # Basic feature - echo "feat: add user registration" | commit-check -m - - # Feature with scope - echo "feat(auth): implement OAuth2 login" | commit-check -m - - # Bug fix - echo "fix: resolve memory leak in parser" | commit-check -m - - # Documentation update - echo "docs: add installation guide" | commit-check -m - - # Breaking change - echo "feat!: redesign API endpoints" | commit-check -m - - # Merge commit (automatically allowed) - echo "Merge pull request #123 from feature/new-api" | commit-check -m - -Invalid Commit Message Examples -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - # No type prefix - echo "added new feature" | commit-check -m - - # Capitalized (if configured to disallow) - echo "feat: Add new feature" | commit-check -m - - # Too short - echo "fix" | commit-check -m - - # Non-imperative mood - echo "feat: added login functionality" | commit-check -m - - # Unknown type - echo "unknown: some changes" | commit-check -m - -Error Output Examples -~~~~~~~~~~~~~~~~~~~~~ - -**Commit Message Validation Failure:** - -.. code-block:: text - - Commit rejected by Commit-Check. - - (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) - / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ - __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ - (_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) - || E || || R || || R || || O || || R || - _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ - (.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) - `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ - - Commit rejected. - - Type message check failed ==> test commit message check - It doesn't match regex: ^(chore|ci|docs|feat|fix|refactor|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)|(Merge).*|(fixup!.*) - The commit message should follow Conventional Commits. See https://www.conventionalcommits.org - Suggest: Use (): with allowed types - -**Branch Name Validation Failure:** - -.. code-block:: text - - Commit rejected by Commit-Check. - - (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) - / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ - __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ - (_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) - || E || || R || || R || || O || || R || - _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ - (.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) - `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ - - Commit rejected. - - Type branch check failed ==> test-branch - It doesn't match regex: ^(feature|bugfix|hotfix|release|chore|feat|fix)\/.+|(master)|(main)|(HEAD)|(PR-.+) - The branch should follow Conventional Branch. See https://conventionalbranch.org - Suggest: Use / with allowed types or ignore_authors in config branch section to bypass - -**Commit Signature Validation Failure:** - -.. code-block:: text - - Commit rejected by Commit-Check. - - (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) - / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ - __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ - (_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) - || E || || R || || R || || O || || R || - _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ - (.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) - `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ - - Commit rejected. - - Type require_signed_off_by check failed ==> fix: add missing file - It doesn't match regex: Signed-off-by:.*[A-Za-z0-9]\s+<.+@.+> - Signed-off-by not found in latest commit - Suggest: git commit --amend --signoff or use --signoff on commit - -**Commit Message Validation Failure without ASCII Banner (``--no-banner``):** - -.. code-block:: text - - Type message check failed ==> test commit message check - It doesn't match regex: ^(chore|ci|docs|feat|fix|refactor|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)|(Merge).*|(fixup!.*) - The commit message should follow Conventional Commits. See https://www.conventionalcommits.org - Suggest: Use (): with allowed types - -**Compact Failure Output (``--compact``):** - -.. code-block:: text - - [FAIL] message: test commit message check - -**Imperative Mood Validation Failure:** - -.. code-block:: text - - Commit rejected by Commit-Check. - - (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) - / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ - __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ - (_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) - || E || || R || || R || || O || || R || - _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ - (.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) - `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ - - Commit rejected. - - Type imperative check failed ==> fix: added missing file - It doesn't match regex: - Commit message should use imperative mood (e.g., 'Add feature' not 'Added feature') - Suggest: Use imperative mood in the subject line - - -Integration Tips ----------------- - -CI/CD Integration -~~~~~~~~~~~~~~~~~ - -You can use commit-check in CI/CD pipelines: - -.. code-block:: bash - - # In your CI script - git log --format="%s" -n 1 | commit-check -m - - # or just - commit-check -m - - # Keep plain-text output but remove the ASCII art banner - git log --format="%s" -n 1 | commit-check -m --no-banner - - # Emit one machine-friendly line per failure without switching to JSON - git log --format="%s" -n 1 | commit-check -m --compact - -Scripting -~~~~~~~~~ - -Use commit-check in scripts to validate commit messages programmatically: - -.. code-block:: bash - - #!/bin/bash - # validate-commits.sh - - # Get all commit messages from last 10 commits - for i in {0..9}; do - msg=$(git log --format="%s" -n 1 --skip=$i) - if [ -n "$msg" ]; then - echo "Validating: $msg" - echo "$msg" | commit-check -m || exit 1 - fi - done - - echo "All commits are valid!" - -For more configuration options, see the `Configuration Documentation `_. diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 00000000..c915fc6a --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,78 @@ +# Installation + +Commit Check runs anywhere Python does, and ships as a GitHub Action and an MCP +server for the places it doesn't. + +## Command line + +=== "pip" + + ```console + $ pip install commit-check + ``` + +=== "uv" + + ```console + $ uv tool install commit-check + ``` + +=== "pipx" + + ```console + $ pipx install commit-check + ``` + +Verify the install: + +```console +$ commit-check --version +``` + +The CLI is also available as `cchk`, which is the same program under a shorter +name. + +!!! tip "Supported Python versions" + + Commit Check supports Python 3.10 through 3.14, on Linux, macOS and Windows. + +## As a pre-commit hook + +No installation step — [pre-commit](https://pre-commit.com) fetches it for you. +See the [pre-commit guide](../guides/pre-commit.md). + +## As a GitHub Action + +No installation step. See the +[GitHub Actions guide](../guides/github-actions.md). + +## Verifying the download + +Releases are built with [SLSA Level 3](https://slsa.dev) provenance. To verify a +release artifact came from this repository's build pipeline: + +```console +$ gh attestation verify commit_check-*.whl --repo commit-check/commit-check +``` + +## Next steps + +
+ +- :material-rocket-launch-outline:{ .lg .middle } __Quick start__ + + --- + + Catch your first bad commit in five minutes. + + [:octicons-arrow-right-24: Quick start](quickstart.md) + +- :material-book-open-variant:{ .lg .middle } __Rules reference__ + + --- + + Every rule, what it does, and why it matters. + + [:octicons-arrow-right-24: Rules](../rules.md) + +
diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md new file mode 100644 index 00000000..11eef029 --- /dev/null +++ b/docs/getting-started/quickstart.md @@ -0,0 +1,148 @@ +# Quick start + +By the end of this page you will have Commit Check rejecting a bad commit +message on your machine, and you will understand what it is telling you. + +It takes about five minutes and needs nothing but a Git repository. + +## 1. Install + +```console +$ pip install commit-check +``` + +## 2. Watch it reject something + +Commit Check works with no configuration at all. Make a deliberately bad commit +in a scratch repository: + +```console +$ git init demo && cd demo +$ git commit --allow-empty -m "updated the parser" +``` + +Now check it: + +```console +$ commit-check --message +``` + +```text +CC001 message check failed ==> updated the parser +The commit message should follow Conventional Commits. See https://www.conventionalcommits.org +Suggest: Use (): , where is one of: feat, fix, docs, ... +Docs: https://docs.commit-check.com/rules/#cc001 +``` + +Four things are happening in that output, and each is deliberate: + +| Part | What it gives you | +|---|---| +| `CC001` | A stable rule ID. It will mean the same thing in five years. | +| `==> updated the parser` | The exact value that failed, not just "invalid message". | +| `Suggest:` | What to do about it. | +| `Docs:` | Why the rule exists, and how to configure or disable it. | + +## 3. Fix it + +```console +$ git commit --amend -m "fix(parser): handle empty input" +$ commit-check --message +``` + +No output and an exit code of `0`. Commit Check is quiet when it is happy. + +## 4. Check the branch too + +```console +$ git switch -c my-changes +$ commit-check --branch +``` + +```text +CC201 branch check failed ==> my-changes +The branch should follow Conventional Branch. See https://conventionalbranch.org +Suggest: Use / with allowed types +Docs: https://docs.commit-check.com/rules/#cc201 +``` + +Rename it to something structured and it passes: + +```console +$ git branch -m fix/empty-input +$ commit-check --branch +``` + +!!! tip "Checks are opt-in per run" + + `commit-check --message` never evaluates branch rules, and vice versa. Each + check is selected by its own flag, so you can run exactly what a given hook + or CI job needs. The + [rules reference](../rules.md) lists which flag activates each rule. + +## 5. Write down your policy + +So far you have been running the defaults. Create a `cchk.toml` in the +repository root — or in `.github/` — to make the policy explicit: + +```toml title="cchk.toml" +[commit] +conventional_commits = true +subject_imperative = true # (1)! +subject_max_length = 72 +allow_wip_commits = false # (2)! + +[branch] +conventional_branch = true +``` + +1. Off by default. Turning it on rejects `fixed a bug` in favour of `fix a bug`. +2. `allow_*` options describe what is *permitted*. Set to `false` to enforce. + +Run it again and the new rules apply: + +```console +$ commit-check --message +``` + +!!! warning "Defaults are not "nothing"" + + Even with no config file, Conventional Commits, Conventional Branch, subject + length limits of 5–80 characters, and author name/email patterns are + enforced. Check the *Default* column in the + [rules reference](../rules.md) before assuming a rule is off. + +## 6. Make it automatic + +Running the command by hand does not scale. Wire it into the two places it +belongs: + +
+ +- :material-git:{ .lg .middle } __Before the commit lands__ + + --- + + A pre-commit hook rejects the message as you write it, so nothing bad + reaches the branch in the first place. + + [:octicons-arrow-right-24: Pre-commit guide](../guides/pre-commit.md) + +- :material-github:{ .lg .middle } __On every pull request__ + + --- + + A GitHub Action checks every commit in the PR and can comment on the PR + with what needs fixing. + + [:octicons-arrow-right-24: GitHub Actions guide](../guides/github-actions.md) + +
+ +## Where to go next + +- **[Rules reference](../rules.md)** — every rule, what it does, why it matters, + and how to configure it. +- **[Configuration](../configuration.md)** — every option, its type and default, + plus the environment variable and CLI flag that override it. +- **[Why Commit Check](why.md)** — the reasoning behind the tool. diff --git a/docs/getting-started/why.md b/docs/getting-started/why.md new file mode 100644 index 00000000..f1143126 --- /dev/null +++ b/docs/getting-started/why.md @@ -0,0 +1,56 @@ +# Why Commit Check + +## The problem + +Git history is a database that every team writes to and almost nobody validates. + +The cost shows up later, and indirectly. Release notes get written by hand +because commit subjects cannot be grouped. `git bisect` walks through merge +commits that record nothing but a sync. A commit is attributed to `ec2-user` +because a build box had no `user.name`. A contribution has to be rejected +months after the fact because it never carried a `Signed-off-by` trailer. + +None of these are caught by a linter, a type checker, or a test suite. They are +all caught by review, which means they are caught inconsistently, by whoever +happens to be looking, and only after the work is done. + +## The approach + +Commit Check treats commit metadata the way linters treat code: a policy written +down once, enforced identically everywhere, with a stable identifier for every +diagnostic so that findings can be discussed, suppressed, and tracked. + +**One config.** A single `cchk.toml` drives the CLI, the pre-commit hook, the +GitHub Action, and the MCP server. There is no second place where the rules can +disagree with themselves. + +**Fails where it is cheap.** The same check that runs in CI runs in your +`commit-msg` hook. Finding out that a subject is malformed takes a second +locally and a full CI cycle plus a force-push remotely. + +**Stable rule IDs.** Every rule has an ID like [CC003](../rules.md#cc003) that +never changes once released. You can cite it in a review comment, link to its +documentation, and eventually suppress it per-rule. + +**Explains itself.** A failure names the rule, quotes the offending value, says +how to fix it, and links to the reasoning. + +## Where it fits + +Commit Check is deliberately narrow: it validates *metadata*, not code. It is a +lightweight, open alternative to +[GitHub Enterprise metadata restrictions](https://docs.github.com/en/enterprise-server@3.11/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#metadata-restrictions) +and Bitbucket's paid +[Yet Another Commit Checker](https://marketplace.atlassian.com/apps/1211854/yet-another-commit-checker), +without requiring a particular forge or an enterprise plan. + +If you already run `ruff`, `eslint`, or `golangci-lint` on your source, Commit +Check is the equivalent for the commits that carry it. + +## What it is not + +- **Not a code linter.** It never reads your source files. +- **Not a replacement for review.** It enforces the mechanical rules so review + can spend its attention on the change itself. +- **Not opinionated by default.** Most rules are off until you turn them on. See + the [rules reference](../rules.md) for what applies out of the box. diff --git a/docs/guides/ai-attribution.md b/docs/guides/ai-attribution.md new file mode 100644 index 00000000..3f627e16 --- /dev/null +++ b/docs/guides/ai-attribution.md @@ -0,0 +1,75 @@ +# Set an AI attribution policy + +AI coding tools add trailers to commit messages identifying themselves. Whether +that is welcome, required, or unacceptable is a decision each project makes for +itself — and the industry has landed in different places: + +- The **Linux kernel** added an `Assisted-by:` trailer, treating AI assistance + as something to disclose. +- **Some projects disallow AI-assisted contributions outright**, usually over + provenance and licensing. +- **Most projects have no stated position**, which means the question resurfaces + in every code review. + +Commit Check does not take a side. It gives you a way to enforce whichever +position your project has already taken, so it stops being relitigated. + +## The default: no opinion + +```toml +[commit] +ai_attribution = "ignore" # the default +``` + +[CC013](../rules.md#cc013) is off. Commits carrying AI trailers pass, and so do +commits without them. + +## Forbidding AI-attributed commits + +```toml title="cchk.toml" +[commit] +ai_attribution = "forbid" +``` + +Commits carrying a recognised AI signature now fail: + +```text +CC013 ai_attribution check failed ==> feat: add caching layer +AI attribution policy violation +Suggest: This project forbids AI-assisted commits. Remove AI trailers and re-commit. +Docs: https://docs.commit-check.com/rules/#cc013 +``` + +### What counts as a signature + +Trailers and co-author lines naming Claude Code, GitHub Copilot, Codex, Gemini, +Cursor, Devin, Aider, Windsurf and Tabby, plus generic AI model patterns. + +!!! warning "This checks disclosure, not authorship" + + CC013 reads commit metadata. It detects a commit that *says* it was + AI-assisted; it cannot detect one that was AI-assisted and did not say so. + + Set against a policy of "no AI contributions", it is an honesty check on + contributors who are already following the rules — not an enforcement + mechanism against those who aren't. Be clear with yourself about which of + those you are buying. + +## Exempting automation + +Bots that legitimately carry AI trailers can be excluded: + +```toml title="cchk.toml" +[commit] +ai_attribution = "forbid" +ignore_authors = ["dependabot[bot]", "renovate[bot]"] +``` + +## Documenting the decision + +Whichever way you go, the config file is not where contributors look. State the +policy where they will see it — `CONTRIBUTING.md`, the pull request template — +and let Commit Check be the mechanism rather than the announcement. + +Enforcing an undocumented policy produces a confusing failure for somebody +acting in good faith. diff --git a/docs/guides/github-actions.md b/docs/guides/github-actions.md new file mode 100644 index 00000000..23c9a9e0 --- /dev/null +++ b/docs/guides/github-actions.md @@ -0,0 +1,81 @@ +# Run in GitHub Actions + +Local hooks can be skipped with `--no-verify`. A CI check cannot, which makes +GitHub Actions the place where your policy is actually a policy. + +## Minimal setup + +```yaml title=".github/workflows/commit-check.yml" +name: Commit Check + +on: + push: + pull_request: + branches: [main] + +jobs: + commit-check: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 # (1)! + - uses: commit-check/commit-check-action@v1 + with: + message: true + branch: true + author-name: true + author-email: true +``` + +1. Commit Check needs the full history to inspect every commit in the pull + request. Without this it only sees the most recent one. + +## Commenting on the pull request + +Instead of making contributors open the job log, have the Action post what +needs fixing directly on the PR: + +```yaml + - uses: commit-check/commit-check-action@v1 + with: + message: true + branch: true + pr-comments: ${{ github.event_name == 'pull_request' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +This needs extra permissions on the job: + +```yaml + permissions: + contents: read + pull-requests: write +``` + +## Reporting without failing + +While a team is adopting the policy, it is often better to report problems +without blocking merges. `dry-run` always exits `0`: + +```yaml + - uses: commit-check/commit-check-action@v1 + with: + message: true + dry-run: true +``` + +Turn it off once the history is clean. + +## Sharing config with local hooks + +The Action reads the same `cchk.toml` as the CLI, so a repository that already +has one needs no Action-specific configuration. That is the point: the rules +cannot drift between what a developer sees locally and what CI enforces. + +See [Configuration](../configuration.md) for where the file may live, and +[Organization-wide policy](organization.md) for sharing one across repositories. diff --git a/docs/guides/organization.md b/docs/guides/organization.md new file mode 100644 index 00000000..38b69956 --- /dev/null +++ b/docs/guides/organization.md @@ -0,0 +1,100 @@ +# Enforce one policy across an organization + +Copying `cchk.toml` into forty repositories works until the day you want to +change it. `inherit_from` lets each repository pull a shared base config and +override only what it genuinely needs. + +## The shared config + +Put the policy in a repository every project can read — GitHub's `.github` +repository is the conventional home: + +```toml title="my-org/.github → cchk.toml" +[commit] +conventional_commits = true +subject_imperative = true +subject_max_length = 72 +allow_merge_commits = false + +[branch] +conventional_branch = true +allow_branch_types = ["feature", "bugfix", "hotfix", "release", "chore"] +``` + +## Inheriting it + +Each repository then needs three lines: + +```toml title="any-repo → .github/cchk.toml" +inherit_from = "github:my-org/.github:cchk.toml" +``` + +Local settings win, so a project with a different constraint overrides just that +one option: + +```toml title="a-repo-with-longer-subjects → .github/cchk.toml" +inherit_from = "github:my-org/.github:cchk.toml" + +[commit] +subject_max_length = 100 # everything else comes from the org config +``` + +## Pinning the version + +By default the shorthand resolves to the parent repository's default branch, +which means a change to the org config takes effect everywhere on the next run. +That is usually what you want. When it isn't, pin to a ref: + +```toml +inherit_from = "github:my-org/.github@v1:cchk.toml" +``` + +## Other sources + +=== "GitHub shorthand" + + ```toml + inherit_from = "github:my-org/.github:cchk.toml" + ``` + +=== "Local path" + + ```toml + inherit_from = "../../shared/org-cchk.toml" + ``` + + Useful in a monorepo, where the shared config is already checked out. + +=== "HTTPS URL" + + ```toml + inherit_from = "https://example.com/shared/cchk.toml" + ``` + + Plain HTTP is rejected. + +!!! warning "Inheritance fails open" + + If the parent config is unreachable — a network blip, a renamed file, a + private repository — Commit Check silently falls back to the local config + rather than failing the build. This keeps CI green during an outage, but it + also means a typo in `inherit_from` is easy to miss. Verify the merged + result when you first set it up: + + ```console + $ commit-check --message --format json + ``` + +## Rolling it out + +Turning on a strict policy across an organization at once produces a wall of +red. A gentler sequence: + +1. Ship the org config with `dry-run` enabled in CI, so violations are reported + but nothing blocks. +2. Look at what actually fails. Some rules will turn out to be wrong for some + teams — that is information, not an obstacle. +3. Turn off `dry-run` for repositories whose history is already clean. +4. Tighten the shared config over time. + +See the [GitHub Actions guide](github-actions.md) for the `dry-run` input. diff --git a/docs/guides/pre-commit.md b/docs/guides/pre-commit.md new file mode 100644 index 00000000..193f0f97 --- /dev/null +++ b/docs/guides/pre-commit.md @@ -0,0 +1,89 @@ +# Run as a pre-commit hook + +A pre-commit hook is the cheapest place to enforce commit policy: the developer +finds out while they are still writing the message, not after a CI round trip. + +## Setup + +Add Commit Check to `.pre-commit-config.yaml`: + +```yaml title=".pre-commit-config.yaml" +repos: + - repo: https://github.com/commit-check/commit-check + rev: v2.11.0 + hooks: + - id: check-message + - id: check-branch + - id: check-author-name + - id: check-author-email +``` + +Then install the hooks. `check-message` runs at the `commit-msg` stage, so it +needs its own install step: + +```console +$ pre-commit install --hook-type commit-msg +$ pre-commit install +``` + +That is it. The next malformed commit message is rejected before it exists. + +## Available hooks + +| Hook ID | Stage | Rules | +|---|---|---| +| `check-message` | `commit-msg` | [CC001–CC013](../rules.md#commit-message-rules) | +| `check-branch` | `pre-commit` | [CC201–CC202](../rules.md#branch-rules) | +| `check-author-name` | `pre-commit` | [CC101](../rules.md#cc101) | +| `check-author-email` | `pre-commit` | [CC102](../rules.md#cc102) | +| `check-no-force-push` | `pre-push` | [CC301](../rules.md#cc301) | + +`check-no-force-push` also needs its own install: + +```console +$ pre-commit install --hook-type pre-push +``` + +## Configuring without a TOML file + +Options can be passed as hook arguments, which keeps everything in one file: + +```yaml title=".pre-commit-config.yaml" +repos: + - repo: https://github.com/commit-check/commit-check + rev: v2.11.0 + hooks: + - id: check-message + args: + - --subject-imperative=true + - --subject-max-length=72 + - --allow-merge-commits=false +``` + +A `cchk.toml` is usually the better choice once you have more than a couple of +options, because CI and the CLI read it too. See +[Configuration](../configuration.md) for the precedence rules. + +## Skipping a hook + +Occasionally you need to get a commit through — a mid-rebase fixup, an +automated migration. `pre-commit` supports this natively: + +```console +$ SKIP=check-message git commit -m "wip" +``` + +!!! warning "Local hooks are not a policy boundary" + + Anyone can pass `--no-verify`. Hooks exist to give fast feedback to people + who want to follow the policy, not to stop people who don't. Pair them with + the [GitHub Action](github-actions.md), which runs where it cannot be + skipped. + +## Troubleshooting + +If `check-message` never seems to run, it is almost always because +`pre-commit install --hook-type commit-msg` was not run — a plain +`pre-commit install` only wires up the `pre-commit` stage. + +More in [Troubleshooting](../troubleshoot.md). diff --git a/docs/guides/signoff.md b/docs/guides/signoff.md new file mode 100644 index 00000000..578c37fb --- /dev/null +++ b/docs/guides/signoff.md @@ -0,0 +1,81 @@ +# Require signoff (DCO) + +Projects that use the [Developer Certificate of Origin](https://developercertificate.org/) +require every commit to carry a `Signed-off-by` trailer. The Linux kernel and +much of the CNCF work this way. + +A DCO bot rejecting a pull request after the fact is a poor experience: the +contributor has to rewrite history for every commit in the branch. Checking +locally fixes it before it becomes a problem. + +## Turn it on + +```toml title="cchk.toml" +[commit] +require_signed_off_by = true +``` + +This enables [CC012](../rules.md#cc012), which is off by default. + +## Signing off + +```console +$ git commit --signoff -m "fix: handle an empty config file" +``` + +The trailer is appended automatically from your `user.name` and `user.email`: + +```text +fix: handle an empty config file + +Signed-off-by: Your Name +``` + +Forgot it? Fix the last commit in place: + +```console +$ git commit --amend --signoff --no-edit +``` + +Fix a whole branch: + +```console +$ git rebase --signoff main +``` + +!!! tip "Make it automatic" + + Signing off is easy to forget. Combine this rule with the + [pre-commit hook](pre-commit.md) so a missing trailer is caught at commit + time, not at review time. + +## Identity matters + +The DCO is a statement about who wrote the code, so it only means something if +the identity is real. [CC101](../rules.md#cc101) and +[CC102](../rules.md#cc102) check the committer name and email, and are enabled +by default when their check runs: + +```console +$ commit-check --author-name --author-email +``` + +To require a company address: + +```toml title="cchk.toml" +[commit] +author_email_pattern = "^.+@example\\.com$" +``` + +## Bots + +Automation cannot meaningfully sign the DCO, and forcing it to produces +meaningless trailers. Exempt bots instead: + +```toml title="cchk.toml" +[commit] +require_signed_off_by = true +ignore_authors = ["dependabot[bot]", "renovate[bot]"] +``` + +`ignore_authors` matches the commit author and any `Co-authored-by:` trailers. diff --git a/docs/index.md b/docs/index.md index b7b74a4f..6be368cc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,31 +1,168 @@ -```{include} ../README.md -``` +--- +title: Commit Check +description: Enforce commit message, branch naming, author and signoff standards across your CLI, pre-commit hooks, CI, and AI agents. +hide: + - navigation + - toc +--- -```{toctree} -:hidden: -:caption: Getting started -self -what-is-new -example -``` +
-```{toctree} -:hidden: -:caption: Configuring -configuration -migration -``` +# Clean commits. Clear standards. -```{toctree} -:hidden: -:caption: Reference -rules -cli_args -``` +Commit Check enforces the rules your Git history already depends on — commit +messages, branch names, committer identity, signoff — from one config, in every +place your team writes code. + +[Get started :octicons-arrow-right-24:](getting-started/quickstart.md){ .md-button .md-button--primary } +[Browse the rules](rules.md){ .md-button } + +
+ +--- + +## One config, enforced everywhere + +Write the policy once. The same rules run on a developer's laptop, in CI, and in +whatever your AI agent is committing on your behalf. + +=== "Command line" + + ```console + $ commit-check --message --branch + CC003 subject_imperative check failed ==> docs: revamped the profile + Commit message should use imperative mood (e.g., 'fix bug' not 'fixed bug') + Suggest: Change the first verb to imperative form + Docs: https://docs.commit-check.com/rules/#cc003 + ``` + +=== "pre-commit" + + ```yaml title=".pre-commit-config.yaml" + repos: + - repo: https://github.com/commit-check/commit-check + rev: v2.11.0 + hooks: + - id: check-message + - id: check-branch + - id: check-author-email + ``` + +=== "GitHub Actions" + + ```yaml title=".github/workflows/commit-check.yml" + - uses: commit-check/commit-check-action@v1 + with: + message: true + branch: true + pr-comments: ${{ github.event_name == 'pull_request' }} + ``` + +=== "AI agents" + + ```json title="MCP server" + { + "mcpServers": { + "commit-check": { "command": "commit-check-mcp" } + } + } + ``` + +## What it checks + +
+ +- :material-message-text-outline:{ .lg .middle } __Commit messages__ + + --- + + Conventional Commits by default, or your own pattern. Subject length, mood, + capitalisation, required body, forbidden merge/fixup/WIP commits. + + [:octicons-arrow-right-24: CC001–CC013](rules.md#commit-message-rules) + +- :material-source-branch:{ .lg .middle } __Branch names__ + + --- + + Conventional Branch naming, plus rebase checks that catch a branch drifting + behind its target before CI wastes a run on stale code. + + [:octicons-arrow-right-24: CC201–CC202](rules.md#branch-rules) + +- :material-account-check-outline:{ .lg .middle } __Committer identity__ + + --- + + Catch commits authored by `ec2-user` on a build box, or require everyone to + contribute from a company address. -```{toctree} -:hidden: -:caption: About -troubleshoot -changelog + [:octicons-arrow-right-24: CC101–CC102](rules.md#author-rules) + +- :material-file-sign:{ .lg .middle } __Signoff and DCO__ + + --- + + Require the `Signed-off-by` trailer locally, so contributors find out before + CI rejects the pull request. + + [:octicons-arrow-right-24: Signoff guide](guides/signoff.md) + +- :material-robot-outline:{ .lg .middle } __AI attribution__ + + --- + + Whatever your project has decided about AI-assisted commits, enforce it + mechanically instead of relitigating it in review. + + [:octicons-arrow-right-24: AI attribution guide](guides/ai-attribution.md) + +- :material-office-building-outline:{ .lg .middle } __Org-wide policy__ + + --- + + Inherit a base config from a shared repository, then let each project + override only what it needs. + + [:octicons-arrow-right-24: Organization guide](guides/organization.md) + +
+ +## Built to be trusted + +
+ +- :material-shield-check:{ .lg .middle } __SLSA Level 3__ + + --- + + Build provenance with artifact attestation verified at install time. + +- :material-tag-outline:{ .lg .middle } __Stable rule IDs__ + + --- + + Every diagnostic carries an ID like `CC003` that never changes, so you can + cite it in review, suppress it, or feed it to tooling. + +- :material-source-commit:{ .lg .middle } __Used in production__ + + --- + + Running at Apache, Texas Instruments, Mila, and + [many more](https://github.com/commit-check/commit-check-action/network/dependents). + +
+ +## Ready in two minutes + +```console +$ pip install commit-check +$ commit-check --message --branch ``` + +No configuration file needed to start — sensible defaults apply immediately, and +you tighten them when you are ready. + +[Install :octicons-arrow-right-24:](getting-started/installation.md){ .md-button .md-button--primary } +[Why Commit Check?](getting-started/why.md){ .md-button } diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 00000000..2c572b2b --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,189 @@ +# Migration Guide + +This guide helps you migrate from commit-check v1.x (YAML configuration) to v2.0+ (TOML configuration). + +## Overview + +Version 2.0 introduces significant changes to commit-check: + +* **Configuration format**: `.commit-check.yml` → `cchk.toml` or `commit-check.toml` +* **Simplified architecture**: New validation engine with cleaner design +* **Enhanced functionality**: Better error messages and more flexible configuration options + +## Quick Migration Steps + +1. **Backup your existing configuration**: + +```bash +cp .commit-check.yml .commit-check.yml.backup +``` + +2. **Create new TOML configuration**: + +```bash +touch cchk.toml # or commit-check.toml +``` + +3. **Convert YAML to TOML format** (see examples below) + +4. **Test the new configuration**: + +```bash +commit-check --help +commit-check --message --branch --author-name --author-email --dry-run +``` + +5. **Remove old YAML file**: + +```bash +rm .commit-check.yml.backup # after confirming everything works +``` + +## Configuration Format Changes + +The configuration structure has changed from YAML to TOML format + +### YAML (v1.x) vs TOML (v2.0+) + +**Old YAML format** (`.commit-check.yml`): + +```yaml +checks: +- check: message + regex: '^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)|(Merge).*|(fixup!.*)' + error: "The commit message should be structured as follows:\n\n + [optional scope]: \n + [optional body]\n + [optional footer(s)]\n\n + More details please refer to https://www.conventionalcommits.org" + suggest: please check your commit message whether matches above regex + +- check: branch + regex: ^(bugfix|feature|release|hotfix|task|chore)\/.+|(master)|(main)|(HEAD)|(PR-.+) + error: "Branches must begin with these types: bugfix/ feature/ release/ hotfix/ task/ chore/" + suggest: run command `git checkout -b type/branch_name` + +- check: author_name + regex: ^[A-Za-zÀ-ÖØ-öø-ÿ\u0100-\u017F\u0180-\u024F ,.\'-]+$|.*(\[bot]) + error: The committer name seems invalid + suggest: run command `git config user.name "Your Name"` + +- check: author_email + regex: ^.+@.+$ + error: The committer email seems invalid + suggest: run command `git config user.email yourname@example.com` + +- check: commit_signoff + regex: Signed-off-by:.*[A-Za-z0-9]\s+<.+@.+> + error: Signed-off-by not found in latest commit + suggest: run command `git commit -m "conventional commit message" --signoff` + +- check: merge_base + regex: main # it can be master, develop, devel etc based on your project. + error: Current branch is not rebased onto target branch + suggest: Please ensure your branch is rebased with the target branch + +- check: imperative + regex: '' # Not used for imperative mood check + error: 'Commit message should use imperative mood (e.g., "Add feature" not "Added feature")' + suggest: 'Use imperative mood in commit message like "Add", "Fix", "Update", "Remove"' +``` + +**New TOML format** (`cchk.toml` or `commit-check.toml`): + +```toml +[commit] +# https://www.conventionalcommits.org +conventional_commits = true +subject_capitalized = false +subject_imperative = true +subject_max_length = 80 +subject_min_length = 5 +allow_commit_types = ["feat", "fix", "docs", "style", "refactor", "test", "chore", "ci"] +allow_merge_commits = true +allow_revert_commits = true +allow_empty_commits = false +allow_fixup_commits = true +allow_wip_commits = false +require_body = false +require_signed_off_by = false +ignore_authors = ["dependabot[bot]", "copilot[bot]"] + +[branch] +# https://conventionalbranch.org +conventional_branch = true +allow_branch_types = ["feature", "bugfix", "hotfix", "release", "chore", "feat", "fix"] +require_rebase_target = "main" +``` + +### CLI Changes + +The command-line interface has been simplified: + +**Old CLI** (v1.x): + +```bash +commit-check --config .commit-check.yml +``` + +**New CLI** (v2.0+): + +```bash +commit-check --config cchk.toml # or commit-check.toml +# Or use defaults (no config file needed) +commit-check --message --branch +``` + +### Custom Regex (`message_pattern`) + +If you relied on the custom `regex` field in v1.x to enforce a non-Conventional-Commits +format (e.g. JIRA smart commits `PROJ-123: description`), use the `message_pattern` +option in the `[commit]` section: + +```toml +[commit] +message_pattern = "^PROJ-\\d+: .+" +``` + +When `message_pattern` is set (non-empty), it replaces the auto-generated Conventional +Commits regex entirely, giving you full control over the accepted message format. + +## Troubleshooting + +### Common Issues + +**Issue**: "Configuration file not found" + +**Solution**: Ensure your file is named `cchk.toml` or `commit-check.toml` and placed in the repository root or in the `.github` folder. + +**Issue**: "Invalid TOML syntax" + +**Solution**: Use a TOML validator or check the syntax. Common issues include: + +* Missing quotes around strings +* Incorrect boolean values (use `true`/`false`, not `True`/`False`) +* Invalid array syntax + +**Issue**: "Validation rules not working as expected" + +**Solution**: Check the [Configuration Documentation](configuration.md) for the correct option names and formats. + +### Validation and Testing + +After migration, test your configuration: + +```bash +# Test commit message validation +echo "feat: test commit message" | commit-check --message + +# Test branch validation +commit-check --branch + +# Test with dry-run flag +commit-check --message --branch --author-name --author-email --dry-run +``` + +## Getting Help + +* **Documentation**: Check the [Configuration Guide](configuration.md) +* **Issues**: Report problems on [GitHub Issues](https://github.com/commit-check/commit-check/issues) \ No newline at end of file diff --git a/docs/migration.rst b/docs/migration.rst deleted file mode 100644 index 5dd13895..00000000 --- a/docs/migration.rst +++ /dev/null @@ -1,203 +0,0 @@ -Migration Guide -=============== - -This guide helps you migrate from commit-check v1.x (YAML configuration) to v2.0+ (TOML configuration). - -Overview --------- - -Version 2.0 introduces significant changes to commit-check: - -* **Configuration format**: ``.commit-check.yml`` → ``cchk.toml`` or ``commit-check.toml`` -* **Simplified architecture**: New validation engine with cleaner design -* **Enhanced functionality**: Better error messages and more flexible configuration options - -Quick Migration Steps ---------------------- - -1. **Backup your existing configuration**: - - .. code-block:: bash - - cp .commit-check.yml .commit-check.yml.backup - -2. **Create new TOML configuration**: - - .. code-block:: bash - - touch cchk.toml # or commit-check.toml - -3. **Convert YAML to TOML format** (see examples below) - -4. **Test the new configuration**: - - .. code-block:: bash - - commit-check --help - commit-check --message --branch --author-name --author-email --dry-run - -5. **Remove old YAML file**: - - .. code-block:: bash - - rm .commit-check.yml.backup # after confirming everything works - -Configuration Format Changes ----------------------------- - -The configuration structure has changed from YAML to TOML format - -YAML (v1.x) vs TOML (v2.0+) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Old YAML format** (``.commit-check.yml``): - -.. code-block:: yaml - - checks: - - check: message - regex: '^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)|(Merge).*|(fixup!.*)' - error: "The commit message should be structured as follows:\n\n - [optional scope]: \n - [optional body]\n - [optional footer(s)]\n\n - More details please refer to https://www.conventionalcommits.org" - suggest: please check your commit message whether matches above regex - - - check: branch - regex: ^(bugfix|feature|release|hotfix|task|chore)\/.+|(master)|(main)|(HEAD)|(PR-.+) - error: "Branches must begin with these types: bugfix/ feature/ release/ hotfix/ task/ chore/" - suggest: run command `git checkout -b type/branch_name` - - - check: author_name - regex: ^[A-Za-zÀ-ÖØ-öø-ÿ\u0100-\u017F\u0180-\u024F ,.\'-]+$|.*(\[bot]) - error: The committer name seems invalid - suggest: run command `git config user.name "Your Name"` - - - check: author_email - regex: ^.+@.+$ - error: The committer email seems invalid - suggest: run command `git config user.email yourname@example.com` - - - check: commit_signoff - regex: Signed-off-by:.*[A-Za-z0-9]\s+<.+@.+> - error: Signed-off-by not found in latest commit - suggest: run command `git commit -m "conventional commit message" --signoff` - - - check: merge_base - regex: main # it can be master, develop, devel etc based on your project. - error: Current branch is not rebased onto target branch - suggest: Please ensure your branch is rebased with the target branch - - - check: imperative - regex: '' # Not used for imperative mood check - error: 'Commit message should use imperative mood (e.g., "Add feature" not "Added feature")' - suggest: 'Use imperative mood in commit message like "Add", "Fix", "Update", "Remove"' - -**New TOML format** (``cchk.toml`` or ``commit-check.toml``): - -.. code-block:: toml - - [commit] - # https://www.conventionalcommits.org - conventional_commits = true - subject_capitalized = false - subject_imperative = true - subject_max_length = 80 - subject_min_length = 5 - allow_commit_types = ["feat", "fix", "docs", "style", "refactor", "test", "chore", "ci"] - allow_merge_commits = true - allow_revert_commits = true - allow_empty_commits = false - allow_fixup_commits = true - allow_wip_commits = false - require_body = false - require_signed_off_by = false - ignore_authors = ["dependabot[bot]", "copilot[bot]"] - - [branch] - # https://conventionalbranch.org - conventional_branch = true - allow_branch_types = ["feature", "bugfix", "hotfix", "release", "chore", "feat", "fix"] - require_rebase_target = "main" - - - -CLI Changes -~~~~~~~~~~~ - -The command-line interface has been simplified: - -**Old CLI** (v1.x): - -.. code-block:: bash - - commit-check --config .commit-check.yml - -**New CLI** (v2.0+): - -.. code-block:: bash - - commit-check --config cchk.toml # or commit-check.toml - # Or use defaults (no config file needed) - commit-check --message --branch - - -Custom Regex (``message_pattern``) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you relied on the custom ``regex`` field in v1.x to enforce a non-Conventional-Commits -format (e.g. JIRA smart commits ``PROJ-123: description``), use the ``message_pattern`` -option in the ``[commit]`` section: - -.. code-block:: toml - - [commit] - message_pattern = "^PROJ-\\d+: .+" - -When ``message_pattern`` is set (non-empty), it replaces the auto-generated Conventional -Commits regex entirely, giving you full control over the accepted message format. - -Troubleshooting ---------------- - -Common Issues -~~~~~~~~~~~~~ - -**Issue**: "Configuration file not found" - -**Solution**: Ensure your file is named ``cchk.toml`` or ``commit-check.toml`` and placed in the repository root or in the ``.github`` folder. - -**Issue**: "Invalid TOML syntax" - -**Solution**: Use a TOML validator or check the syntax. Common issues include: - -* Missing quotes around strings -* Incorrect boolean values (use ``true``/``false``, not ``True``/``False``) -* Invalid array syntax - -**Issue**: "Validation rules not working as expected" - -**Solution**: Check the `Configuration Documentation `_ for the correct option names and formats. - -Validation and Testing -~~~~~~~~~~~~~~~~~~~~~~ - -After migration, test your configuration: - -.. code-block:: bash - - # Test commit message validation - echo "feat: test commit message" | commit-check --message - - # Test branch validation - commit-check --branch - - # Test with dry-run flag - commit-check --message --branch --author-name --author-email --dry-run - -Getting Help ------------- - -* **Documentation**: Check the `Configuration Guide `_ -* **Issues**: Report problems on `GitHub Issues `_ diff --git a/docs/rules.md b/docs/rules.md new file mode 100644 index 00000000..51fbc5fe --- /dev/null +++ b/docs/rules.md @@ -0,0 +1,695 @@ +# Rules + +Every check that can report a failure has a **stable rule ID**. Rule IDs never +change once released, so they are safe to reference in commit messages, code +review comments, issue templates, and tooling. + +Rule IDs appear in commit-check's output and in `--format json` results: + +```text +CC003 subject_imperative check failed ==> docs: revamped the profile +Commit message should use imperative mood (e.g., 'fix bug' not 'fixed bug') +Suggest: Change the first verb to imperative form, e.g., 'fix' instead of 'fixed' +Docs: https://docs.commit-check.com/rules.html#cc003 +``` + +`--compact` prints one line per failure, keeping the rule ID and dropping the +explanation, suggestion, and documentation link: + +```text +[FAIL] CC003 subject_imperative: docs: revamped the profile +``` + +## How to read this page + +Rule IDs are grouped by what they inspect: + +| Range | Category | Inspects | +|---|---|---| +| `CC0xx` | [Commit message](#commit-message-rules) | The subject, body, and trailers of a commit message | +| `CC1xx` | [Author](#author-rules) | The committer's configured name and email | +| `CC2xx` | [Branch](#branch-rules) | The current branch's name and its position relative to a target branch | +| `CC3xx` | [Push](#push-rules) | The push operation itself | + +Two things determine whether a rule runs: + +**The check you select.** commit-check only evaluates the checks you ask for on +the command line. `commit-check --message` never reports a branch rule. The +*Check* column in the tables below shows which flag activates each rule. + +**Its configuration.** Within a selected check, the *Default* column shows +whether the rule is active with no configuration at all: + +| Default | Meaning | +|---|---| +| ✅ On | Enforced out of the box. Disable it through the listed option. | +| ⚪ Off | Not enforced until you opt in through the listed option. | + +Rules that are off by default are not lesser rules — they encode conventions +that are right for some projects and wrong for others. Turning on +[CC002](#cc002) makes sense for a project that capitalizes subjects, and is +actively harmful for one that does not. + +!!! tip + + Every option named below is documented in full — with its type, default, and + the matching environment variable and CLI flag — in + [configuration](configuration.md). + +## Rule index + +### Commit message rules (`CC0xx`) { #commit-message-rules } + +Run with `-m` / `--message`. + +| Code | Name | Message | Check | Default | +|---|---|---|---|---| +| [CC001](#cc001) | `message` | The commit message should follow Conventional Commits | `-m` | ✅ On | +| [CC002](#cc002) | `subject-capitalized` | Subject must start with a capital letter | `-m` | ⚪ Off | +| [CC003](#cc003) | `subject-imperative` | Commit message should use imperative mood | `-m` | ⚪ Off | +| [CC004](#cc004) | `subject-max-length` | Subject must be at most `{max_len}` characters | `-m` | ✅ On | +| [CC005](#cc005) | `subject-min-length` | Subject must be at least `{min_len}` characters | `-m` | ✅ On | +| [CC006](#cc006) | `allow-merge-commits` | Merge commits are not allowed | `-m` | ⚪ Off | +| [CC007](#cc007) | `allow-revert-commits` | Revert commits are not allowed | `-m` | ⚪ Off | +| [CC008](#cc008) | `allow-empty-commits` | Empty commit messages are not allowed | `-m` | ⚪ Off | +| [CC009](#cc009) | `allow-fixup-commits` | Fixup commits are not allowed | `-m` | ⚪ Off | +| [CC010](#cc010) | `allow-wip-commits` | WIP commits are not allowed | `-m` | ⚪ Off | +| [CC011](#cc011) | `require-body` | Commit body is required | `-m` | ⚪ Off | +| [CC012](#cc012) | `require-signed-off-by` | Signed-off-by not found in latest commit | `-m` | ⚪ Off | +| [CC013](#cc013) | `ai-attribution` | AI attribution policy violation | `-m` | ⚪ Off | + +### Author rules (`CC1xx`) { #author-rules } + +| Code | Name | Message | Check | Default | +|---|---|---|---|---| +| [CC101](#cc101) | `author-name` | The committer name seems invalid | `-n` | ✅ On | +| [CC102](#cc102) | `author-email` | The committer's email seems invalid | `-e` | ✅ On | + +### Branch rules (`CC2xx`) { #branch-rules } + +Run with `-b` / `--branch`. + +| Code | Name | Message | Check | Default | +|---|---|---|---|---| +| [CC201](#cc201) | `branch` | The branch should follow Conventional Branch | `-b` | ✅ On | +| [CC202](#cc202) | `merge-base` | Current branch is not rebased onto target branch | `-b` | ⚪ Off | + +### Push rules (`CC3xx`) { #push-rules } + +| Code | Name | Message | Check | Default | +|---|---|---|---|---| +| [CC301](#cc301) | `no-force-push` | Force push is not allowed | `--no-force-push` | ⚪ Off | + +## Commit message rules + +### message (CC001) { #cc001 } + +**What it does** + +Checks that the commit message subject follows the +[Conventional Commits](https://www.conventionalcommits.org) specification: +`()!: `. + +**Why is this bad?** + +A free-form subject can only be read by a human. A structured one can be read by +tooling: release-drafting can group changes by type, semantic versioning can +infer whether a release is a patch, minor, or major, and `git log` becomes +filterable by area of the codebase. Once a fraction of the history is +unstructured, every consumer of that history needs a fallback path. + +**Example** + +```text +updated the parser +``` + +Use instead: + +```text +fix(parser): handle empty input +``` + +**Options** + +* `commit.conventional_commits` — set to `false` to disable this rule. +* `commit.allow_commit_types` — the accepted `` values. +* `commit.message_pattern` — a custom regex that replaces the generated + Conventional Commits pattern entirely, for formats such as JIRA smart commits + (`"^PROJ-\\d+: .+"`). + +### subject-capitalized (CC002) { #cc002 } + +**What it does** + +Checks that the description in the subject line starts with a capital letter. + +**Why is this bad?** + +Nothing is inherently wrong with either casing — but mixing them is. A history +where half the subjects read `fix: handle empty input` and the other half read +`fix: Handle empty input` looks careless in `git log --oneline`, and gives +reviewers a pointless thing to comment on. This rule picks the capitalized +convention and enforces it. + +Leave it off if your project deliberately uses lowercase descriptions, which is +the more common convention among projects that follow Conventional Commits. + +**Example** + +```text +fix: handle empty input +``` + +Use instead: + +```text +fix: Handle empty input +``` + +**Options** + +* `commit.subject_capitalized` — set to `true` to enable this rule. + +### subject-imperative (CC003) { #cc003 } + +**What it does** + +Checks that the first word of the description is in the imperative mood — +`fix`, not `fixed`, `fixes`, or `fixing`. + +**Why is this bad?** + +This is Git's own convention: a subject should complete the sentence *"If +applied, this commit will ___"*. `If applied, this commit will fixed a crash` +does not read as English. Beyond grammar, the imperative form is the shortest of +the three, which matters on a line that tooling truncates around 50 characters. + +**Example** + +```text +fix: fixed a crash when the config file is empty +``` + +Use instead: + +```text +fix: handle an empty config file +``` + +**Options** + +* `commit.subject_imperative` — set to `true` to enable this rule. + +The list of recognised non-imperative verb forms lives in +[imperatives.py](https://github.com/commit-check/commit-check/blob/main/commit_check/imperatives.py). + +### subject-max-length (CC004) { #cc004 } + +**What it does** + +Checks that the subject line is at most a configured number of characters. + +**Why is this bad?** + +Long subjects get truncated by the tools that display them — +`git log --oneline`, `git shortlog`, GitHub's commit list, and most Git +GUIs all cut off somewhere between 50 and 72 columns. A subject that carries its +meaning past that point loses it exactly where people skim. Detail belongs in +the body, which nothing truncates. + +**Example** + +```text +fix: handle an empty config file and also fix the unrelated crash in the branch parser that happens on Windows +``` + +Use instead: + +```text +fix: handle an empty config file + +Also fixes the branch parser crash on Windows, which shared the +same root cause. +``` + +**Options** + +* `commit.subject_max_length` — the limit, in characters. Defaults to `80`. + `50` and `72` are the other conventional choices. + +### subject-min-length (CC005) { #cc005 } + +**What it does** + +Checks that the subject line is at least a configured number of characters. + +**Why is this bad?** + +Subjects like `fix`, `wip`, or `.` describe nothing. They are invisible in +a blame view and useless in a bisect session, and they are almost always the +result of a hurried commit rather than a deliberate one. + +**Example** + +```text +fix: bug +``` + +Use instead: + +```text +fix: reject config files with a null inherit_from +``` + +**Options** + +* `commit.subject_min_length` — the minimum, in characters. Defaults to `5`. + +### allow-merge-commits (CC006) { #cc006 } + +**What it does** + +Rejects merge commits — the `Merge branch '...'` commits that `git pull` +creates. + +**Why is this bad?** + +Merge commits created by `git pull` carry no information: they record that +someone synced, not that anything was decided. On a busy repository they can +outnumber real commits, which makes `git log` unreadable, adds branches for +`git bisect` to walk, and breaks the assumption behind +`git log --first-parent`. Projects that want a linear history rebase instead. + +**Example** + +```bash +git pull +``` + +Use instead: + +```bash +git pull --rebase + +# or make it the default +git config --global pull.rebase true +``` + +**Options** + +* `commit.allow_merge_commits` — set to `false` to enable this rule. + +### allow-revert-commits (CC007) { #cc007 } + +**What it does** + +Rejects the `Revert "..."` commits that `git revert` generates. + +**Why is this bad?** + +A generated revert subject describes the mechanics of the change and nothing +about the reason for it. Six months later, `Revert "feat: add caching layer"` +answers "what happened" but not the only question that matters: why the feature +was backed out, and whether it is safe to try again. + +**Example** + +```text +Revert "feat: add caching layer" +``` + +Use instead: + +```text +fix: remove the caching layer + +The cache served stale permissions after a role change (#412). +Reverts 4a1c9f2; re-land once invalidation is keyed on role version. +``` + +**Options** + +* `commit.allow_revert_commits` — set to `false` to enable this rule. + +### allow-empty-commits (CC008) { #cc008 } + +**What it does** + +Rejects commits with an empty message. + +**Why is this bad?** + +A commit with no subject cannot be searched for, summarised, or reviewed. It is +a gap in the history that nobody can fill in later. + +**Options** + +* `commit.allow_empty_commits` — set to `false` to enable this rule. + +### allow-fixup-commits (CC009) { #cc009 } + +**What it does** + +Rejects `fixup!` and `squash!` commits. + +**Why is this bad?** + +These commits exist to be consumed by `git rebase --autosquash` before a +branch is merged. One that survives to the target branch means the autosquash +was forgotten — leaving behind a commit that, by construction, does not stand on +its own. + +**Example** + +```text +fixup! feat: add the caching layer +``` + +Use instead: + +```bash +git rebase -i --autosquash main +``` + +**Options** + +* `commit.allow_fixup_commits` — set to `false` to enable this rule. + +### allow-wip-commits (CC010) { #cc010 } + +**What it does** + +Rejects work-in-progress commits — subjects beginning with `WIP`. + +**Why is this bad?** + +A WIP commit is an explicit statement that the change is not finished. That is +useful on a local branch and wrong on a shared one, where every commit is +something another developer may bisect through or build on. + +**Example** + +```text +WIP: caching +``` + +Use instead: + +```bash +# keep the work, drop the marker +git commit --amend -m "feat: add a caching layer for role lookups" +``` + +**Options** + +* `commit.allow_wip_commits` — set to `false` to enable this rule. + +### require-body (CC011) { #cc011 } + +**What it does** + +Requires a non-empty body after the subject line. + +**Why is this bad?** + +The subject says *what* changed; the diff already says that too. The body says +*why* — the constraint, the bug report, the rejected alternative. That reasoning +is the one thing that cannot be recovered from the code later, and it is exactly +what the next person to touch the change needs. + +**Example** + +```text +fix: cap the retry backoff at 30s +``` + +Use instead: + +```text +fix: cap the retry backoff at 30s + +The unbounded exponential backoff reached 45 minutes during the +incident on 2026-05-11, long after the upstream had recovered. +``` + +**Options** + +* `commit.require_body` — set to `true` to enable this rule. + +### require-signed-off-by (CC012) { #cc012 } + +**What it does** + +Requires a `Signed-off-by:` trailer in the commit message. + +**Why is this bad?** + +Projects that use the `Developer Certificate of Origin +`_ — the Linux kernel, and much of the +CNCF — treat that trailer as the contributor's statement that they have the +right to submit the code. A commit without it cannot be merged, so catching it +locally saves a round trip through CI. + +**Example** + +```bash +git commit -m "fix: handle an empty config file" +``` + +Use instead: + +```bash +git commit --signoff -m "fix: handle an empty config file" + +# or fix the commit you already made +git commit --amend --signoff +``` + +**Options** + +* `commit.require_signed_off_by` — set to `true` to enable this rule. + +### ai-attribution (CC013) { #cc013 } + +**What it does** + +Rejects commits carrying the signatures that AI coding tools add to commit +messages — trailers naming Claude Code, Copilot, Codex, Gemini, Cursor, Devin, +Aider, Windsurf, Tabby, and generic AI model patterns. + +**Why is this bad?** + +Whether AI-assisted commits are acceptable is a policy question, and projects +have landed on different answers: the Linux kernel added an `Assisted-by:` +trailer, while others disallow the practice outright. This rule exists for +projects that have made that decision and want it enforced mechanically rather +than relitigated in every code review. + +It is off by default, and the default policy is `"ignore"`. Enable it only if +your project has a stated position. + +**Options** + +* `commit.ai_attribution` — `"forbid"` enables this rule, `"ignore"` + (the default) disables it. + +## Author rules + +### author-name (CC101) { #cc101 } + +**What it does** + +Checks the committer's configured name against a pattern. The built-in pattern +accepts letters (including accented Latin characters), spaces, and +`, . ' -`, and always allows `[bot]` accounts. + +**Why is this bad?** + +When `user.name` is unset, Git falls back to the machine's account name. +Histories built in CI containers and on fresh VMs fill up with commits by +machine accounts — authorship that cannot be traced back to a person, which +matters for both code archaeology and compliance. + +**Example** + +```bash +git config user.name ec2-user +``` + +Use instead: + +```bash +git config --global user.name "Your Name" +``` + +**Options** + +* `commit.author_name_pattern` — a custom regex replacing the built-in + pattern. For example, `"^.+ .+$"` to require a full name. + +!!! note + + The built-in pattern accepts any name made of letters, spaces, and + `, . ' -`, so plain account names such as `root` or `ubuntu` still + pass it — only names containing digits or other symbols are rejected. Set + `author_name_pattern` if you need something stricter. + +### author-email (CC102) { #cc102 } + +**What it does** + +Checks the committer's configured email against a pattern. The built-in pattern +is `^.+@.+$`, which only requires an `@` with something on either side. + +**Why is this bad?** + +An address with no `@` is not routable, so it breaks the link between a commit +and its author: forges cannot attribute the commit to an account, and +mailmap-based tooling cannot merge identities. + +The built-in pattern is deliberately permissive — it is a sanity check, not a +policy. Its real value comes from replacing it, which is how organisations +require contributions to come from a corporate address. + +**Example** + +With `author_email_pattern = "^.+@example\\.com$"` configured: + +```bash +git config user.email you@gmail.com +``` + +Use instead: + +```bash +git config --global user.email you@example.com +``` + +**Options** + +* `commit.author_email_pattern` — the regex to match against. Defaults to + `^.+@.+$`; set something like `"^.+@example\\.com$"` to require a company + domain. + +!!! note + + Because the built-in pattern only looks for an `@`, local and placeholder + addresses such as `root@localhost` pass it. Set `author_email_pattern` + if you need to reject those. + +## Branch rules + +### branch (CC201) { #cc201 } + +**What it does** + +Checks that the current branch name follows the +[Conventional Branch](https://conventionalbranch.org/) specification: +`/`. + +`master`, `main`, `HEAD`, and `PR-*` are always accepted. + +**Why is this bad?** + +A predictable prefix is something automation can act on: CI can skip expensive +jobs for `docs/` branches, deployment workflows can key off `release/`, and +branch protection rules can be written per type. It also makes a list of a +hundred open branches scannable, which an unstructured list never is. + +**Example** + +```text +my-fix +johns-branch-2 +``` + +Use instead: + +```text +fix/empty-config-crash +feature/role-caching +``` + +**Options** + +* `branch.conventional_branch` — set to `false` to disable this rule. +* `branch.allow_branch_types` — the accepted `` values. The default is + a superset of the specification: the spec types plus the Conventional Commit + types, AI agent prefixes (`ai`, `claude`, `codex`, `copilot`, + `cursor`), and bot prefixes (`dependabot`, `renovate`). Set it + explicitly for strict spec-only validation. +* `branch.allow_branch_names` — additional standalone names to accept, such as + `["develop", "staging"]`. +* `branch.ignore_authors` — bypass the check for specific authors. + +### merge-base (CC202) { #cc202 } + +**What it does** + +Checks that the current branch is rebased onto a target branch. + +**Why is this bad?** + +A branch that has fallen behind is tested against code that no longer exists on +the target. CI passing on it says little about whether it will pass after +merging, and the failures it hides — a renamed function, a changed migration — +surface on the target branch instead of the pull request. + +**Example** + +```bash +# branch was cut from main three weeks ago +git push +``` + +Use instead: + +```bash +git fetch origin +git rebase origin/main +git push --force-with-lease +``` + +**Options** + +* `branch.require_rebase_target` — the target branch, for example `"main"`. + Unset by default, meaning no rebase requirement. + +## Push rules + +### no-force-push (CC301) { #cc301 } + +**What it does** + +Blocks force pushes. Run it as a `pre-push` hook, where it reads the push +details from stdin, or with `--no-force-push`, where it compares the current +branch against its upstream. + +**Why is this bad?** + +A force push to a shared branch rewrites history that other people have already +based work on. Their next pull produces conflicts against commits that no longer +exist, and any commit pushed between their fetch and the force push is silently +dropped. On a personal branch this is a routine part of rebasing; on a shared +one it is a data-loss event. + +**Example** + +```bash +git push --force +``` + +Use instead: + +```bash +# on a shared branch, add a commit rather than rewriting +git revert + +# on your own branch, at least refuse to clobber someone else's work +git push --force-with-lease +``` + +**Options** + +* `push.allow_force_push` — set to `false` to enable this rule. \ No newline at end of file diff --git a/docs/rules.rst b/docs/rules.rst deleted file mode 100644 index 0f968695..00000000 --- a/docs/rules.rst +++ /dev/null @@ -1,891 +0,0 @@ -Rules -===== - -Every check that can report a failure has a **stable rule ID**. Rule IDs never -change once released, so they are safe to reference in commit messages, code -review comments, issue templates, and tooling. - -Rule IDs appear in commit-check's output and in ``--format json`` results: - -.. code-block:: text - - CC003 subject_imperative check failed ==> docs: revamped the profile - Commit message should use imperative mood (e.g., 'fix bug' not 'fixed bug') - Suggest: Change the first verb to imperative form, e.g., 'fix' instead of 'fixed' - Docs: https://docs.commit-check.com/rules.html#cc003 - -``--compact`` prints one line per failure, keeping the rule ID and dropping the -explanation, suggestion, and documentation link: - -.. code-block:: text - - [FAIL] CC003 subject_imperative: docs: revamped the profile - -How to read this page ---------------------- - -Rule IDs are grouped by what they inspect: - -.. list-table:: - :header-rows: 1 - :widths: 12 25 63 - - * - Range - - Category - - Inspects - * - ``CC0xx`` - - :ref:`Commit message ` - - The subject, body, and trailers of a commit message - * - ``CC1xx`` - - :ref:`Author ` - - The committer's configured name and email - * - ``CC2xx`` - - :ref:`Branch ` - - The current branch's name and its position relative to a target branch - * - ``CC3xx`` - - :ref:`Push ` - - The push operation itself - -Two things determine whether a rule runs: - -**The check you select.** commit-check only evaluates the checks you ask for on -the command line. ``commit-check --message`` never reports a branch rule. The -*Check* column in the tables below shows which flag activates each rule. - -**Its configuration.** Within a selected check, the *Default* column shows -whether the rule is active with no configuration at all: - -.. list-table:: - :header-rows: 1 - :widths: 20 80 - - * - Default - - Meaning - * - ✅ On - - Enforced out of the box. Disable it through the listed option. - * - ⚪ Off - - Not enforced until you opt in through the listed option. - -Rules that are off by default are not lesser rules — they encode conventions -that are right for some projects and wrong for others. Turning on -:ref:`CC002 ` makes sense for a project that capitalizes subjects, and is -actively harmful for one that does not. - -.. tip:: - - Every option named below is documented in full — with its type, default, and - the matching environment variable and CLI flag — in - :doc:`configuration`. - -Rule index ----------- - -.. _commit-message-rules: - -Commit message rules (``CC0xx``) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Run with ``-m`` / ``--message``. - -.. list-table:: - :header-rows: 1 - :widths: 10 26 44 10 10 - :class: rules-index - - * - Code - - Name - - Message - - Check - - Default - * - :ref:`CC001 ` - - ``message`` - - The commit message should follow Conventional Commits - - ``-m`` - - ✅ On - * - :ref:`CC002 ` - - ``subject-capitalized`` - - Subject must start with a capital letter - - ``-m`` - - ⚪ Off - * - :ref:`CC003 ` - - ``subject-imperative`` - - Commit message should use imperative mood - - ``-m`` - - ⚪ Off - * - :ref:`CC004 ` - - ``subject-max-length`` - - Subject must be at most ``{max_len}`` characters - - ``-m`` - - ✅ On - * - :ref:`CC005 ` - - ``subject-min-length`` - - Subject must be at least ``{min_len}`` characters - - ``-m`` - - ✅ On - * - :ref:`CC006 ` - - ``allow-merge-commits`` - - Merge commits are not allowed - - ``-m`` - - ⚪ Off - * - :ref:`CC007 ` - - ``allow-revert-commits`` - - Revert commits are not allowed - - ``-m`` - - ⚪ Off - * - :ref:`CC008 ` - - ``allow-empty-commits`` - - Empty commit messages are not allowed - - ``-m`` - - ⚪ Off - * - :ref:`CC009 ` - - ``allow-fixup-commits`` - - Fixup commits are not allowed - - ``-m`` - - ⚪ Off - * - :ref:`CC010 ` - - ``allow-wip-commits`` - - WIP commits are not allowed - - ``-m`` - - ⚪ Off - * - :ref:`CC011 ` - - ``require-body`` - - Commit body is required - - ``-m`` - - ⚪ Off - * - :ref:`CC012 ` - - ``require-signed-off-by`` - - Signed-off-by not found in latest commit - - ``-m`` - - ⚪ Off - * - :ref:`CC013 ` - - ``ai-attribution`` - - AI attribution policy violation - - ``-m`` - - ⚪ Off - -.. _author-rules: - -Author rules (``CC1xx``) -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. list-table:: - :header-rows: 1 - :widths: 10 26 44 10 10 - :class: rules-index - - * - Code - - Name - - Message - - Check - - Default - * - :ref:`CC101 ` - - ``author-name`` - - The committer name seems invalid - - ``-n`` - - ✅ On - * - :ref:`CC102 ` - - ``author-email`` - - The committer's email seems invalid - - ``-e`` - - ✅ On - -.. _branch-rules: - -Branch rules (``CC2xx``) -~~~~~~~~~~~~~~~~~~~~~~~~ - -Run with ``-b`` / ``--branch``. - -.. list-table:: - :header-rows: 1 - :widths: 10 26 44 10 10 - :class: rules-index - - * - Code - - Name - - Message - - Check - - Default - * - :ref:`CC201 ` - - ``branch`` - - The branch should follow Conventional Branch - - ``-b`` - - ✅ On - * - :ref:`CC202 ` - - ``merge-base`` - - Current branch is not rebased onto target branch - - ``-b`` - - ⚪ Off - -.. _push-rules: - -Push rules (``CC3xx``) -~~~~~~~~~~~~~~~~~~~~~~ - -.. list-table:: - :header-rows: 1 - :widths: 10 26 44 10 10 - :class: rules-index - - * - Code - - Name - - Message - - Check - - Default - * - :ref:`CC301 ` - - ``no-force-push`` - - Force push is not allowed - - ``--no-force-push`` - - ⚪ Off - -Commit message rules --------------------- - -.. _cc001: - -message (CC001) -~~~~~~~~~~~~~~~ - -**What it does** - -Checks that the commit message subject follows the -`Conventional Commits `_ specification: -``()!: ``. - -**Why is this bad?** - -A free-form subject can only be read by a human. A structured one can be read by -tooling: release-drafting can group changes by type, semantic versioning can -infer whether a release is a patch, minor, or major, and ``git log`` becomes -filterable by area of the codebase. Once a fraction of the history is -unstructured, every consumer of that history needs a fallback path. - -**Example** - -.. code-block:: text - - updated the parser - -Use instead: - -.. code-block:: text - - fix(parser): handle empty input - -**Options** - -* ``commit.conventional_commits`` — set to ``false`` to disable this rule. -* ``commit.allow_commit_types`` — the accepted ```` values. -* ``commit.message_pattern`` — a custom regex that replaces the generated - Conventional Commits pattern entirely, for formats such as JIRA smart commits - (``"^PROJ-\\d+: .+"``). - -.. _cc002: - -subject-capitalized (CC002) -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Checks that the description in the subject line starts with a capital letter. - -**Why is this bad?** - -Nothing is inherently wrong with either casing — but mixing them is. A history -where half the subjects read ``fix: handle empty input`` and the other half read -``fix: Handle empty input`` looks careless in ``git log --oneline``, and gives -reviewers a pointless thing to comment on. This rule picks the capitalized -convention and enforces it. - -Leave it off if your project deliberately uses lowercase descriptions, which is -the more common convention among projects that follow Conventional Commits. - -**Example** - -.. code-block:: text - - fix: handle empty input - -Use instead: - -.. code-block:: text - - fix: Handle empty input - -**Options** - -* ``commit.subject_capitalized`` — set to ``true`` to enable this rule. - -.. _cc003: - -subject-imperative (CC003) -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Checks that the first word of the description is in the imperative mood — -``fix``, not ``fixed``, ``fixes``, or ``fixing``. - -**Why is this bad?** - -This is Git's own convention: a subject should complete the sentence *"If -applied, this commit will ___"*. ``If applied, this commit will fixed a crash`` -does not read as English. Beyond grammar, the imperative form is the shortest of -the three, which matters on a line that tooling truncates around 50 characters. - -**Example** - -.. code-block:: text - - fix: fixed a crash when the config file is empty - -Use instead: - -.. code-block:: text - - fix: handle an empty config file - -**Options** - -* ``commit.subject_imperative`` — set to ``true`` to enable this rule. - -The list of recognised non-imperative verb forms lives in -`imperatives.py `_. - -.. _cc004: - -subject-max-length (CC004) -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Checks that the subject line is at most a configured number of characters. - -**Why is this bad?** - -Long subjects get truncated by the tools that display them — -``git log --oneline``, ``git shortlog``, GitHub's commit list, and most Git -GUIs all cut off somewhere between 50 and 72 columns. A subject that carries its -meaning past that point loses it exactly where people skim. Detail belongs in -the body, which nothing truncates. - -**Example** - -.. code-block:: text - - fix: handle an empty config file and also fix the unrelated crash in the branch parser that happens on Windows - -Use instead: - -.. code-block:: text - - fix: handle an empty config file - - Also fixes the branch parser crash on Windows, which shared the - same root cause. - -**Options** - -* ``commit.subject_max_length`` — the limit, in characters. Defaults to ``80``. - ``50`` and ``72`` are the other conventional choices. - -.. _cc005: - -subject-min-length (CC005) -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Checks that the subject line is at least a configured number of characters. - -**Why is this bad?** - -Subjects like ``fix``, ``wip``, or ``.`` describe nothing. They are invisible in -a blame view and useless in a bisect session, and they are almost always the -result of a hurried commit rather than a deliberate one. - -**Example** - -.. code-block:: text - - fix: bug - -Use instead: - -.. code-block:: text - - fix: reject config files with a null inherit_from - -**Options** - -* ``commit.subject_min_length`` — the minimum, in characters. Defaults to ``5``. - -.. _cc006: - -allow-merge-commits (CC006) -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Rejects merge commits — the ``Merge branch '...'`` commits that ``git pull`` -creates. - -**Why is this bad?** - -Merge commits created by ``git pull`` carry no information: they record that -someone synced, not that anything was decided. On a busy repository they can -outnumber real commits, which makes ``git log`` unreadable, adds branches for -``git bisect`` to walk, and breaks the assumption behind -``git log --first-parent``. Projects that want a linear history rebase instead. - -**Example** - -.. code-block:: bash - - git pull - -Use instead: - -.. code-block:: bash - - git pull --rebase - - # or make it the default - git config --global pull.rebase true - -**Options** - -* ``commit.allow_merge_commits`` — set to ``false`` to enable this rule. - -.. _cc007: - -allow-revert-commits (CC007) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Rejects the ``Revert "..."`` commits that ``git revert`` generates. - -**Why is this bad?** - -A generated revert subject describes the mechanics of the change and nothing -about the reason for it. Six months later, ``Revert "feat: add caching layer"`` -answers "what happened" but not the only question that matters: why the feature -was backed out, and whether it is safe to try again. - -**Example** - -.. code-block:: text - - Revert "feat: add caching layer" - -Use instead: - -.. code-block:: text - - fix: remove the caching layer - - The cache served stale permissions after a role change (#412). - Reverts 4a1c9f2; re-land once invalidation is keyed on role version. - -**Options** - -* ``commit.allow_revert_commits`` — set to ``false`` to enable this rule. - -.. _cc008: - -allow-empty-commits (CC008) -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Rejects commits with an empty message. - -**Why is this bad?** - -A commit with no subject cannot be searched for, summarised, or reviewed. It is -a gap in the history that nobody can fill in later. - -**Options** - -* ``commit.allow_empty_commits`` — set to ``false`` to enable this rule. - -.. _cc009: - -allow-fixup-commits (CC009) -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Rejects ``fixup!`` and ``squash!`` commits. - -**Why is this bad?** - -These commits exist to be consumed by ``git rebase --autosquash`` before a -branch is merged. One that survives to the target branch means the autosquash -was forgotten — leaving behind a commit that, by construction, does not stand on -its own. - -**Example** - -.. code-block:: text - - fixup! feat: add the caching layer - -Use instead: - -.. code-block:: bash - - git rebase -i --autosquash main - -**Options** - -* ``commit.allow_fixup_commits`` — set to ``false`` to enable this rule. - -.. _cc010: - -allow-wip-commits (CC010) -~~~~~~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Rejects work-in-progress commits — subjects beginning with ``WIP``. - -**Why is this bad?** - -A WIP commit is an explicit statement that the change is not finished. That is -useful on a local branch and wrong on a shared one, where every commit is -something another developer may bisect through or build on. - -**Example** - -.. code-block:: text - - WIP: caching - -Use instead: - -.. code-block:: bash - - # keep the work, drop the marker - git commit --amend -m "feat: add a caching layer for role lookups" - -**Options** - -* ``commit.allow_wip_commits`` — set to ``false`` to enable this rule. - -.. _cc011: - -require-body (CC011) -~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Requires a non-empty body after the subject line. - -**Why is this bad?** - -The subject says *what* changed; the diff already says that too. The body says -*why* — the constraint, the bug report, the rejected alternative. That reasoning -is the one thing that cannot be recovered from the code later, and it is exactly -what the next person to touch the change needs. - -**Example** - -.. code-block:: text - - fix: cap the retry backoff at 30s - -Use instead: - -.. code-block:: text - - fix: cap the retry backoff at 30s - - The unbounded exponential backoff reached 45 minutes during the - incident on 2026-05-11, long after the upstream had recovered. - -**Options** - -* ``commit.require_body`` — set to ``true`` to enable this rule. - -.. _cc012: - -require-signed-off-by (CC012) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Requires a ``Signed-off-by:`` trailer in the commit message. - -**Why is this bad?** - -Projects that use the `Developer Certificate of Origin -`_ — the Linux kernel, and much of the -CNCF — treat that trailer as the contributor's statement that they have the -right to submit the code. A commit without it cannot be merged, so catching it -locally saves a round trip through CI. - -**Example** - -.. code-block:: bash - - git commit -m "fix: handle an empty config file" - -Use instead: - -.. code-block:: bash - - git commit --signoff -m "fix: handle an empty config file" - - # or fix the commit you already made - git commit --amend --signoff - -**Options** - -* ``commit.require_signed_off_by`` — set to ``true`` to enable this rule. - -.. _cc013: - -ai-attribution (CC013) -~~~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Rejects commits carrying the signatures that AI coding tools add to commit -messages — trailers naming Claude Code, Copilot, Codex, Gemini, Cursor, Devin, -Aider, Windsurf, Tabby, and generic AI model patterns. - -**Why is this bad?** - -Whether AI-assisted commits are acceptable is a policy question, and projects -have landed on different answers: the Linux kernel added an ``Assisted-by:`` -trailer, while others disallow the practice outright. This rule exists for -projects that have made that decision and want it enforced mechanically rather -than relitigated in every code review. - -It is off by default, and the default policy is ``"ignore"``. Enable it only if -your project has a stated position. - -**Options** - -* ``commit.ai_attribution`` — ``"forbid"`` enables this rule, ``"ignore"`` - (the default) disables it. - -Author rules ------------- - -.. _cc101: - -author-name (CC101) -~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Checks the committer's configured name against a pattern. The built-in pattern -accepts letters (including accented Latin characters), spaces, and -``, . ' -``, and always allows ``[bot]`` accounts. - -**Why is this bad?** - -When ``user.name`` is unset, Git falls back to the machine's account name. -Histories built in CI containers and on fresh VMs fill up with commits by -machine accounts — authorship that cannot be traced back to a person, which -matters for both code archaeology and compliance. - -**Example** - -.. code-block:: bash - - git config user.name ec2-user - -Use instead: - -.. code-block:: bash - - git config --global user.name "Your Name" - -**Options** - -* ``commit.author_name_pattern`` — a custom regex replacing the built-in - pattern. For example, ``"^.+ .+$"`` to require a full name. - -.. note:: - - The built-in pattern accepts any name made of letters, spaces, and - ``, . ' -``, so plain account names such as ``root`` or ``ubuntu`` still - pass it — only names containing digits or other symbols are rejected. Set - ``author_name_pattern`` if you need something stricter. - -.. _cc102: - -author-email (CC102) -~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Checks the committer's configured email against a pattern. The built-in pattern -is ``^.+@.+$``, which only requires an ``@`` with something on either side. - -**Why is this bad?** - -An address with no ``@`` is not routable, so it breaks the link between a commit -and its author: forges cannot attribute the commit to an account, and -mailmap-based tooling cannot merge identities. - -The built-in pattern is deliberately permissive — it is a sanity check, not a -policy. Its real value comes from replacing it, which is how organisations -require contributions to come from a corporate address. - -**Example** - -With ``author_email_pattern = "^.+@example\\.com$"`` configured: - -.. code-block:: bash - - git config user.email you@gmail.com - -Use instead: - -.. code-block:: bash - - git config --global user.email you@example.com - -**Options** - -* ``commit.author_email_pattern`` — the regex to match against. Defaults to - ``^.+@.+$``; set something like ``"^.+@example\\.com$"`` to require a company - domain. - -.. note:: - - Because the built-in pattern only looks for an ``@``, local and placeholder - addresses such as ``root@localhost`` pass it. Set ``author_email_pattern`` - if you need to reject those. - -Branch rules ------------- - -.. _cc201: - -branch (CC201) -~~~~~~~~~~~~~~ - -**What it does** - -Checks that the current branch name follows the -`Conventional Branch `_ specification: -``/``. - -``master``, ``main``, ``HEAD``, and ``PR-*`` are always accepted. - -**Why is this bad?** - -A predictable prefix is something automation can act on: CI can skip expensive -jobs for ``docs/`` branches, deployment workflows can key off ``release/``, and -branch protection rules can be written per type. It also makes a list of a -hundred open branches scannable, which an unstructured list never is. - -**Example** - -.. code-block:: text - - my-fix - johns-branch-2 - -Use instead: - -.. code-block:: text - - fix/empty-config-crash - feature/role-caching - -**Options** - -* ``branch.conventional_branch`` — set to ``false`` to disable this rule. -* ``branch.allow_branch_types`` — the accepted ```` values. The default is - a superset of the specification: the spec types plus the Conventional Commit - types, AI agent prefixes (``ai``, ``claude``, ``codex``, ``copilot``, - ``cursor``), and bot prefixes (``dependabot``, ``renovate``). Set it - explicitly for strict spec-only validation. -* ``branch.allow_branch_names`` — additional standalone names to accept, such as - ``["develop", "staging"]``. -* ``branch.ignore_authors`` — bypass the check for specific authors. - -.. _cc202: - -merge-base (CC202) -~~~~~~~~~~~~~~~~~~ - -**What it does** - -Checks that the current branch is rebased onto a target branch. - -**Why is this bad?** - -A branch that has fallen behind is tested against code that no longer exists on -the target. CI passing on it says little about whether it will pass after -merging, and the failures it hides — a renamed function, a changed migration — -surface on the target branch instead of the pull request. - -**Example** - -.. code-block:: bash - - # branch was cut from main three weeks ago - git push - -Use instead: - -.. code-block:: bash - - git fetch origin - git rebase origin/main - git push --force-with-lease - -**Options** - -* ``branch.require_rebase_target`` — the target branch, for example ``"main"``. - Unset by default, meaning no rebase requirement. - -Push rules ----------- - -.. _cc301: - -no-force-push (CC301) -~~~~~~~~~~~~~~~~~~~~~ - -**What it does** - -Blocks force pushes. Run it as a ``pre-push`` hook, where it reads the push -details from stdin, or with ``--no-force-push``, where it compares the current -branch against its upstream. - -**Why is this bad?** - -A force push to a shared branch rewrites history that other people have already -based work on. Their next pull produces conflicts against commits that no longer -exist, and any commit pushed between their fetch and the force push is silently -dropped. On a personal branch this is a routine part of rebasing; on a shared -one it is a data-loss event. - -**Example** - -.. code-block:: bash - - git push --force - -Use instead: - -.. code-block:: bash - - # on a shared branch, add a commit rather than rewriting - git revert - - # on your own branch, at least refuse to clobber someone else's work - git push --force-with-lease - -**Options** - -* ``push.allow_force_push`` — set to ``false`` to enable this rule. diff --git a/docs/troubleshoot.md b/docs/troubleshoot.md new file mode 100644 index 00000000..49c1653c --- /dev/null +++ b/docs/troubleshoot.md @@ -0,0 +1,41 @@ +# Troubleshooting + +## How to Skip Author Name Check + +In some cases, Commit Check may fail due to an invalid `author_name`, as shown below: + +```shell +check committer name.....................................................Failed +- hook id: check-author-name +- exit code: 1 + +Commit rejected by Commit-Check. + +Type author_name check failed => 12 +It doesn't match regex: ^[A-Za-zÀ-ÖØ-öø-ÿ\u0100-\u017F\u0180-\u024F ,.\'-]+$|.*(\[bot]) +The committer name seems invalid +Suggest: run command `git config user.name "Your Name"` +``` + +To fix it, you can either update your Git config or temporarily skip the check using one of the following methods. + +### Bypass Specific Hook + +Use the `--no-verify` flag to skip the pre-commit hook: + +```shell +# Amend the commit without running hooks +git commit --amend --author="Xianpeng Shen " --no-edit --no-verify +``` + +### Bypass All Hooks + +Alternatively, use the `SKIP=your-hook-name` environment variable, like below: + +```shell +# Set the correct Git author name +git config user.name "Xianpeng Shen" + +# Force amend while skipping the specified hook +SKIP=check-author-name git commit --amend --author="Xianpeng Shen " --no-edit +``` diff --git a/docs/troubleshoot.rst b/docs/troubleshoot.rst deleted file mode 100644 index 66df7c48..00000000 --- a/docs/troubleshoot.rst +++ /dev/null @@ -1,45 +0,0 @@ -Troubleshooting -=============== - -How to Skip Author Name Check ------------------------------ - -In some cases, Commit Check may fail due to an invalid ``author_name``, as shown below: - -.. code-block:: shell - - check committer name.....................................................Failed - - hook id: check-author-name - - exit code: 1 - - Commit rejected by Commit-Check. - - Type author_name check failed => 12 - It doesn't match regex: ^[A-Za-zÀ-ÖØ-öø-ÿ\u0100-\u017F\u0180-\u024F ,.\'-]+$|.*(\[bot]) - The committer name seems invalid - Suggest: run command `git config user.name "Your Name"` - -To fix it, you can either update your Git config or temporarily skip the check using one of the following methods. - -Bypass Specific Hook -~~~~~~~~~~~~~~~~~~~~ - -Use the ``--no-verify`` flag to skip the pre-commit hook: - -.. code-block:: shell - - # Amend the commit without running hooks - git commit --amend --author="Xianpeng Shen " --no-edit --no-verify - -Bypass All Hooks -~~~~~~~~~~~~~~~~ - -Alternatively, use the ``SKIP=your-hook-name`` environment variable, like below: - -.. code-block:: shell - - # Set the correct Git author name - git config user.name "Xianpeng Shen" - - # Force amend while skipping the specified hook - SKIP=check-author-name git commit --amend --author="Xianpeng Shen " --no-edit diff --git a/docs/what-is-new.md b/docs/what-is-new.md new file mode 100644 index 00000000..1a568087 --- /dev/null +++ b/docs/what-is-new.md @@ -0,0 +1,366 @@ +# What's New + +This document highlights the major changes and improvements in each version of commit-check. + +## Version 2.11.0 — AI Attribution Governance + +### Enforce Your Project's AI Contribution Policy + +commit-check now supports **AI attribution governance** — a neutral enforcement +layer for the industry-wide discussion on AI disclosure in open source. + +Configured under `[commit]`: + +```toml +[commit] +# "ignore" (default) | "forbid" +ai_attribution = "forbid" +``` + +When set to `"forbid"`, any commit containing known AI tool signatures is +rejected. The built-in signature database detects trailers and markers from: + +* **Claude Code** — `Co-authored-by: Claude`, `Assisted-by: Claude:...`, + `🤖 Generated with Claude`, `Claude-Session:`, `Claude-Workflow:` +* **GitHub Copilot** — `Co-authored-by: Copilot` +* **OpenAI Codex** — `Co-authored-by: Codex` +* **Gemini** — `Co-authored-by: Gemini` +* **Cursor** — `Co-authored-by: Cursor` +* **Devin** — `Co-authored-by: Devin` +* **Aider** — `Co-authored-by: Aider`, `Co-authored-by: ... (aider)` +* **Windsurf** — `Co-authored-by: Windsurf` +* **Tabby** — `Co-authored-by: Tabby` +* **Generic AI** — `Assisted-by:` (Linux kernel style, with tool list), + model names like `claude-sonnet-4`, `gpt-4-turbo` + +The signature database is designed to be extensible — adding a new tool is as +simple as adding a `KnownAiTool` entry with the tool's patterns. + +This feature is motivated by ongoing discussions in the CPython core +development community, the Linux kernel's `Assisted-by:` trailer standard, +VS Code, Apache, Fedora, and other foundations. + +See [Configuration Documentation](configuration.md) for details. + +## Version 2.10.0 — Bot Branch Types as Default + +### `dependabot/` and `renovate/` branches now pass by default + +`dependabot` and `renovate` are now included in `DEFAULT_BRANCH_TYPES`, +so branches like `dependabot/go_modules/go-deps-c57c3fe1e0` and +`renovate/lodash-5.x` are automatically accepted without manual +`allow_branch_types` configuration. + +## Version 2.9.0 — AI Agent Branch Prefixes + +### Conventional Branch v1.1.0 AI agent prefixes supported by default + +`ai/`, `claude/`, `codex/`, `copilot/`, and `cursor/` have been +added to `DEFAULT_BRANCH_TYPES` as defined in `Conventional Branch +v1.1.0 `_. Branches created by AI +coding agents are now valid out of the box without extra configuration. + +## Version 2.7.0 — Force Push Blocking + +### Force Push Detection and Prevention + +commit-check now includes a **force push detection** feature that blocks +accidental `git push --force` / `git push -f` by inspecting pushed ref +ancestry via `git merge-base --is-ancestor`. + +**How it works:** + +* Runs inside a Git `pre-push` hook — receives pushed ref metadata on stdin + and inspects the ancestry relationship. +* New branch pushes (remote SHA is all zeros) always pass. +* Fast-forward pushes (remote is ancestor of local) pass. +* When the remote commit is **not** an ancestor of the local commit, a force + push is detected and **blocked**. +* Git errors (e.g., unknown SHA) result in a safe pass. + +**Usage:** + +```bash +# Standalone: check whether pushing HEAD to its upstream requires force +commit-check --no-force-push +``` + +```yaml +# As a pre-commit pre-push hook +repos: + - repo: https://github.com/commit-check/commit-check + rev: v2.7.0 + hooks: + - id: check-no-force-push + stages: [pre-push] +``` + +```toml +# Configurable in cchk.toml +[push] +allow_force_push = false # default: true (force pushes allowed) +``` + +**New Python API:** + +```python +from commit_check.api import validate_push + +zero = "0000000000000000000000000000000000000000" +result = validate_push(f"refs/heads/main abc123 refs/heads/main {zero}") +print(result["status"]) # "pass" +``` + +See the [Push Safety section in README](https://github.com/commit-check/commit-check#check-push-safety) +and [Push Validation Examples](https://docs.commit-check.com/example.html#push-validation-examples) +for more details. + +## Version 2.6.0 — Output Controls for CLI Workflows + +### Quieter Human-Readable Failure Output + +commit-check now includes two CLI flags for workflows that want less verbose +terminal output without switching to JSON mode: + +* `--no-banner` suppresses the ASCII art failure banner while keeping the + detailed error message and suggestion output. +* `--compact` prints a single `[FAIL]` line per failing check and implies + `--no-banner`. + +These flags are useful in CI logs, pre-commit output, and agent-driven terminal +sessions where the full banner is noisy but plain-text diagnostics are still +helpful. + +## Version 2.5.0 — New Features + +### Co-author Bypass in `ignore_authors` + +commit-check can now skip validation when a **co-author** of the commit matches an entry in `ignore_authors`, not just the primary commit author. + +This is especially useful for AI-assisted workflows where a bot (e.g., `coderabbitai[bot]`, `copilot[bot]`) co-authors a commit that does not follow Conventional Commits format: + +```toml +[commit] +ignore_authors = ["dependabot[bot]", "renovate[bot]", "coderabbitai[bot]", "copilot[bot]"] +``` + +When a `Co-authored-by:` trailer in the commit message body matches any entry in the list, all commit checks are skipped for that commit. + +### Organization-Level Config Inheritance (`inherit_from`) + +Teams can now share a **centralized base configuration** across all repositories in an organization using the new `inherit_from` top-level key. + +```toml +# .github/cchk.toml — in every repo +inherit_from = "github:my-org/.github:cchk.toml" + +[commit] +subject_max_length = 72 # Local override +``` + +**Supported source formats:** + +* `github:owner/repo:path/to/cchk.toml` — fetches from the default branch via `raw.githubusercontent.com` +* `github:owner/repo@main:path/to/cchk.toml` — pins to a specific branch, tag, or SHA +* A local file path (relative or absolute) +* An HTTPS URL + +Local settings always **override** the inherited configuration. HTTP (non-TLS) URLs are rejected for security. If the source is unreachable, the local config is used as-is. + +### Git Config Author Validation + +Author name and email validation now checks **`git config user.name` / `user.email`** first — the identity that will be used for the *next* commit — and falls back to the last commit's author only if git config is unset. + +Previously, a developer with a misconfigured `user.name` (e.g., starting with a digit) would pass validation as long as their most recent commit had a valid author name. This fix closes that gap. + +## Version 2.0.0 - Major Release + +Version 2.0.0 represents a complete architectural overhaul of commit-check, introducing significant improvements in configuration, usability, and maintainability. + +### **Overview** + +The most significant change in v2.0.0 is the transition from YAML to TOML configuration format, along with a complete redesign of the validation engine using SOLID principles. + +**Key Benefits:** + +* **Simplified Configuration**: More intuitive TOML syntax +* **Better Defaults**: Sensible out-of-the-box behavior +* **Enhanced Validation**: Built-in support for Conventional Commits and Conventional Branches +* **Improved Architecture**: Modular, maintainable codebase +* **Better Documentation**: Comprehensive guides and examples + +### **Documentation & Migration** + +* **Configuration Guide**: Updated [Configuration Documentation](configuration.md) with comprehensive examples +* **Migration Support**: Complete [Migration Guide](migration.md) for upgrading from v1.x to v2.0+ + +### **Configuration Format Migration** + +The configuration format has changed from YAML to TOML, providing better readability and easier maintenance. + +**Format Comparison:** + +| Feature | YAML (v1.x) | TOML (v2.0+) | +|---|---|---| +| **Syntax** | Complex nested structure | Simple key-value pairs | +| **Validation** | Custom regex patterns | Built-in conventional standards | +| **Configuration** | `.commit-check.yml` | `cchk.toml` or `commit-check.toml` | +| **Maintainability** | Manual regex maintenance | Standardized patterns | + +### **Configuration Examples** + +Below are side-by-side comparisons showing how common configurations translate from v1.x to v2.0+. + +#### Commit Message Validation + +Transform complex regex patterns into simple, standardized configuration. + +**Before (YAML v1.x):** + +```yaml +checks: + - check: message + regex: '^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)|(Merge).*|(fixup!.*)' + error: "The commit message should be structured as follows:\n\n + [optional scope]: \n + [optional body]\n + [optional footer(s)]\n\n + More details please refer to https://www.conventionalcommits.org" + suggest: please check your commit message whether matches above regex +``` + +**After (TOML v2.0+):** + +```toml +[commit] +conventional_commits = true +allow_commit_types = ["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "style", "test"] +``` + +**Benefits**: No more complex regex patterns, built-in [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) support, clearer configuration. + +#### Branch Naming Validation + +Standardize branch naming with conventional patterns. + +**Before (YAML v1.x):** + +```yaml +checks: + - check: branch + regex: ^(bugfix|feature|release|hotfix|task|chore)\/.+|(master)|(main)|(HEAD)|(PR-.+) + error: "Branches must begin with these types: bugfix/ feature/ release/ hotfix/ task/ chore/" + suggest: run command `git checkout -b type/branch_name` +``` + +**After (TOML v2.0+):** + +```toml +[branch] +conventional_branch = true +allow_branch_types = ["bugfix", "feature", "release", "hotfix", "task", "chore"] +``` + +**Benefits**: Built-in [Conventional Branch](https://conventionalbranch.org) support, automatic handling of special branches (main, master, HEAD, PR-\*). + +#### Author Validation + +Flexible author validation with allow/ignore lists. + +**Before (YAML v1.x):** + +```yaml +checks: + - check: author_name + regex: ^[A-Za-zÀ-ÖØ-öø-ÿ\u0100-\u017F\u0180-\u024F ,.\'-]+$|.*(\[bot]) + error: The committer name seems invalid + suggest: run command `git config user.name "Your Name"` +``` + +**After (TOML v2.0+):** + +```toml +[commit] +# Built-in validation with sensible defaults for author name/email +# Optional: ignore specific authors (e.g., bots) +ignore_authors = ["dependabot[bot]", "renovate[bot]"] +``` + +**Benefits**: Built-in validation patterns, flexible ignore lists, automatic bot detection. + +#### Signed-off-by Requirements + +Simple boolean flag for DCO compliance. + +**Before (YAML v1.x):** + +```yaml +checks: + - check: commit_signoff + regex: Signed-off-by:.*[A-Za-z0-9]\s+<.+@.+> + error: Signed-off-by not found in latest commit + suggest: run command `git commit -m "conventional commit message" --signoff` +``` + +**After (TOML v2.0+):** + +```toml +[commit] +require_signed_off_by = true +``` + +**Benefits**: Simple boolean configuration, built-in DCO validation, clear error messages. + +### **Architecture Improvements** + +#### **New Validation Engine** + +* **SOLID Principles**: Maintainable, extensible design +* **Specialized Validators**: Dedicated classes for each validation type +* **Centralized Rules**: Rule catalog with consistent error messages +* **Flexible Configuration**: Dynamic rule building from configuration + +#### **Module Organization** + +| Module | Purpose | +|---|---| +| `config.py` | TOML configuration loading and validation | +| `engine.py` | Core validation engine and specialized validators | +| `rule_builder.py` | Builds validation rules from configuration | +| `rules_catalog.py` | Centralized catalog of validation rules and messages | +| `main.py` | CLI interface and orchestration | + +### **Getting Started with v2.0** + +#### **For New Users:** + +1. **Install commit-check v2.0+**: + +```bash +pip install commit-check>=2.0.0 +``` + +2. **Start with defaults** (no configuration needed): + +```bash +commit-check --message --branch +``` + +3. **Customize as needed** with `cchk.toml`: + +```toml +[commit] +conventional_commits = true +subject_max_length = 72 +``` + +#### For Existing Users + +1. **Follow the Migration Guide**: See [Migration Guide](migration.md) +2. **Test thoroughly**: Validate your new configuration before deploying + +### **Additional Resources** + +* [Configuration Reference](configuration.md) - Complete configuration options +* [Migration Guide](migration.md) - Step-by-step upgrade instructions +* [CLI Reference](cli.md) - Command-line interface documentation \ No newline at end of file diff --git a/docs/what-is-new.rst b/docs/what-is-new.rst deleted file mode 100644 index 81d5ebab..00000000 --- a/docs/what-is-new.rst +++ /dev/null @@ -1,420 +0,0 @@ -What's New -========== - -This document highlights the major changes and improvements in each version of commit-check. - -Version 2.11.0 — AI Attribution Governance --------------------------------------------- - -Enforce Your Project's AI Contribution Policy -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -commit-check now supports **AI attribution governance** — a neutral enforcement -layer for the industry-wide discussion on AI disclosure in open source. - -Configured under ``[commit]``: - -.. code-block:: toml - - [commit] - # "ignore" (default) | "forbid" - ai_attribution = "forbid" - -When set to ``"forbid"``, any commit containing known AI tool signatures is -rejected. The built-in signature database detects trailers and markers from: - -* **Claude Code** — ``Co-authored-by: Claude``, ``Assisted-by: Claude:...``, - ``🤖 Generated with Claude``, ``Claude-Session:``, ``Claude-Workflow:`` -* **GitHub Copilot** — ``Co-authored-by: Copilot`` -* **OpenAI Codex** — ``Co-authored-by: Codex`` -* **Gemini** — ``Co-authored-by: Gemini`` -* **Cursor** — ``Co-authored-by: Cursor`` -* **Devin** — ``Co-authored-by: Devin`` -* **Aider** — ``Co-authored-by: Aider``, ``Co-authored-by: ... (aider)`` -* **Windsurf** — ``Co-authored-by: Windsurf`` -* **Tabby** — ``Co-authored-by: Tabby`` -* **Generic AI** — ``Assisted-by:`` (Linux kernel style, with tool list), - model names like ``claude-sonnet-4``, ``gpt-4-turbo`` - -The signature database is designed to be extensible — adding a new tool is as -simple as adding a ``KnownAiTool`` entry with the tool's patterns. - -This feature is motivated by ongoing discussions in the CPython core -development community, the Linux kernel's ``Assisted-by:`` trailer standard, -VS Code, Apache, Fedora, and other foundations. - -See `Configuration Documentation `_ for details. - -Version 2.10.0 — Bot Branch Types as Default ---------------------------------------------- - -``dependabot/`` and ``renovate/`` branches now pass by default -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -``dependabot`` and ``renovate`` are now included in ``DEFAULT_BRANCH_TYPES``, -so branches like ``dependabot/go_modules/go-deps-c57c3fe1e0`` and -``renovate/lodash-5.x`` are automatically accepted without manual -``allow_branch_types`` configuration. - -Version 2.9.0 — AI Agent Branch Prefixes ----------------------------------------- - -Conventional Branch v1.1.0 AI agent prefixes supported by default -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -``ai/``, ``claude/``, ``codex/``, ``copilot/``, and ``cursor/`` have been -added to ``DEFAULT_BRANCH_TYPES`` as defined in `Conventional Branch -v1.1.0 `_. Branches created by AI -coding agents are now valid out of the box without extra configuration. - -Version 2.7.0 — Force Push Blocking ------------------------------------ - -Force Push Detection and Prevention -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -commit-check now includes a **force push detection** feature that blocks -accidental ``git push --force`` / ``git push -f`` by inspecting pushed ref -ancestry via ``git merge-base --is-ancestor``. - -**How it works:** - -* Runs inside a Git ``pre-push`` hook — receives pushed ref metadata on stdin - and inspects the ancestry relationship. -* New branch pushes (remote SHA is all zeros) always pass. -* Fast-forward pushes (remote is ancestor of local) pass. -* When the remote commit is **not** an ancestor of the local commit, a force - push is detected and **blocked**. -* Git errors (e.g., unknown SHA) result in a safe pass. - -**Usage:** - -.. code-block:: bash - - # Standalone: check whether pushing HEAD to its upstream requires force - commit-check --no-force-push - -.. code-block:: yaml - - # As a pre-commit pre-push hook - repos: - - repo: https://github.com/commit-check/commit-check - rev: v2.7.0 - hooks: - - id: check-no-force-push - stages: [pre-push] - -.. code-block:: toml - - # Configurable in cchk.toml - [push] - allow_force_push = false # default: true (force pushes allowed) - -**New Python API:** - -.. code-block:: python - - from commit_check.api import validate_push - - zero = "0000000000000000000000000000000000000000" - result = validate_push(f"refs/heads/main abc123 refs/heads/main {zero}") - print(result["status"]) # "pass" - -See the `Push Safety section in README `_ -and `Push Validation Examples `_ -for more details. - - -Version 2.6.0 — Output Controls for CLI Workflows -------------------------------------------------- - -Quieter Human-Readable Failure Output -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -commit-check now includes two CLI flags for workflows that want less verbose -terminal output without switching to JSON mode: - -* ``--no-banner`` suppresses the ASCII art failure banner while keeping the - detailed error message and suggestion output. -* ``--compact`` prints a single ``[FAIL]`` line per failing check and implies - ``--no-banner``. - -These flags are useful in CI logs, pre-commit output, and agent-driven terminal -sessions where the full banner is noisy but plain-text diagnostics are still -helpful. - -Version 2.5.0 — New Features ------------------------------- - -Co-author Bypass in ``ignore_authors`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -commit-check can now skip validation when a **co-author** of the commit matches an entry in ``ignore_authors``, not just the primary commit author. - -This is especially useful for AI-assisted workflows where a bot (e.g., ``coderabbitai[bot]``, ``copilot[bot]``) co-authors a commit that does not follow Conventional Commits format: - -.. code-block:: toml - - [commit] - ignore_authors = ["dependabot[bot]", "renovate[bot]", "coderabbitai[bot]", "copilot[bot]"] - -When a ``Co-authored-by:`` trailer in the commit message body matches any entry in the list, all commit checks are skipped for that commit. - -Organization-Level Config Inheritance (``inherit_from``) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Teams can now share a **centralized base configuration** across all repositories in an organization using the new ``inherit_from`` top-level key. - -.. code-block:: toml - - # .github/cchk.toml — in every repo - inherit_from = "github:my-org/.github:cchk.toml" - - [commit] - subject_max_length = 72 # Local override - -**Supported source formats:** - -* ``github:owner/repo:path/to/cchk.toml`` — fetches from the default branch via ``raw.githubusercontent.com`` -* ``github:owner/repo@main:path/to/cchk.toml`` — pins to a specific branch, tag, or SHA -* A local file path (relative or absolute) -* An HTTPS URL - -Local settings always **override** the inherited configuration. HTTP (non-TLS) URLs are rejected for security. If the source is unreachable, the local config is used as-is. - -Git Config Author Validation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Author name and email validation now checks **``git config user.name`` / ``user.email``** first — the identity that will be used for the *next* commit — and falls back to the last commit's author only if git config is unset. - -Previously, a developer with a misconfigured ``user.name`` (e.g., starting with a digit) would pass validation as long as their most recent commit had a valid author name. This fix closes that gap. - -Version 2.0.0 - Major Release ------------------------------ - -Version 2.0.0 represents a complete architectural overhaul of commit-check, introducing significant improvements in configuration, usability, and maintainability. - -**Overview** -~~~~~~~~~~~~~~~ - -The most significant change in v2.0.0 is the transition from YAML to TOML configuration format, along with a complete redesign of the validation engine using SOLID principles. - -**Key Benefits:** - -* **Simplified Configuration**: More intuitive TOML syntax -* **Better Defaults**: Sensible out-of-the-box behavior -* **Enhanced Validation**: Built-in support for Conventional Commits and Conventional Branches -* **Improved Architecture**: Modular, maintainable codebase -* **Better Documentation**: Comprehensive guides and examples - -**Documentation & Migration** -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* **Configuration Guide**: Updated `Configuration Documentation `_ with comprehensive examples -* **Migration Support**: Complete `Migration Guide `_ for upgrading from v1.x to v2.0+ - -**Configuration Format Migration** -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The configuration format has changed from YAML to TOML, providing better readability and easier maintenance. - -**Format Comparison:** - -.. list-table:: - :header-rows: 1 - :widths: 20 40 40 - - * - Feature - - YAML (v1.x) - - TOML (v2.0+) - * - **Syntax** - - Complex nested structure - - Simple key-value pairs - * - **Validation** - - Custom regex patterns - - Built-in conventional standards - * - **Configuration** - - ``.commit-check.yml`` - - ``cchk.toml`` or ``commit-check.toml`` - * - **Maintainability** - - Manual regex maintenance - - Standardized patterns - - -**Configuration Examples** -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Below are side-by-side comparisons showing how common configurations translate from v1.x to v2.0+. - -Commit Message Validation -^^^^^^^^^^^^^^^^^^^^^^^^^ - -Transform complex regex patterns into simple, standardized configuration. - -**Before (YAML v1.x):** - -.. code-block:: yaml - - checks: - - check: message - regex: '^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)|(Merge).*|(fixup!.*)' - error: "The commit message should be structured as follows:\n\n - [optional scope]: \n - [optional body]\n - [optional footer(s)]\n\n - More details please refer to https://www.conventionalcommits.org" - suggest: please check your commit message whether matches above regex - -**After (TOML v2.0+):** - -.. code-block:: toml - - [commit] - conventional_commits = true - allow_commit_types = ["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "style", "test"] - -**Benefits**: No more complex regex patterns, built-in `Conventional Commits `_ support, clearer configuration. - -Branch Naming Validation -^^^^^^^^^^^^^^^^^^^^^^^^ - -Standardize branch naming with conventional patterns. - -**Before (YAML v1.x):** - -.. code-block:: yaml - - checks: - - check: branch - regex: ^(bugfix|feature|release|hotfix|task|chore)\/.+|(master)|(main)|(HEAD)|(PR-.+) - error: "Branches must begin with these types: bugfix/ feature/ release/ hotfix/ task/ chore/" - suggest: run command `git checkout -b type/branch_name` - -**After (TOML v2.0+):** - -.. code-block:: toml - - [branch] - conventional_branch = true - allow_branch_types = ["bugfix", "feature", "release", "hotfix", "task", "chore"] - -**Benefits**: Built-in `Conventional Branch `_ support, automatic handling of special branches (main, master, HEAD, PR-\*). - -Author Validation -^^^^^^^^^^^^^^^^^ - -Flexible author validation with allow/ignore lists. - -**Before (YAML v1.x):** - -.. code-block:: yaml - - checks: - - check: author_name - regex: ^[A-Za-zÀ-ÖØ-öø-ÿ\u0100-\u017F\u0180-\u024F ,.\'-]+$|.*(\[bot]) - error: The committer name seems invalid - suggest: run command `git config user.name "Your Name"` - -**After (TOML v2.0+):** - -.. code-block:: toml - - [commit] - # Built-in validation with sensible defaults for author name/email - # Optional: ignore specific authors (e.g., bots) - ignore_authors = ["dependabot[bot]", "renovate[bot]"] - -**Benefits**: Built-in validation patterns, flexible ignore lists, automatic bot detection. - -Signed-off-by Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Simple boolean flag for DCO compliance. - -**Before (YAML v1.x):** - -.. code-block:: yaml - - checks: - - check: commit_signoff - regex: Signed-off-by:.*[A-Za-z0-9]\s+<.+@.+> - error: Signed-off-by not found in latest commit - suggest: run command `git commit -m "conventional commit message" --signoff` - -**After (TOML v2.0+):** - -.. code-block:: toml - - [commit] - require_signed_off_by = true - -**Benefits**: Simple boolean configuration, built-in DCO validation, clear error messages. - -**Architecture Improvements** -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**New Validation Engine** -^^^^^^^^^^^^^^^^^^^^^^^^^ - -* **SOLID Principles**: Maintainable, extensible design -* **Specialized Validators**: Dedicated classes for each validation type -* **Centralized Rules**: Rule catalog with consistent error messages -* **Flexible Configuration**: Dynamic rule building from configuration - -**Module Organization** -^^^^^^^^^^^^^^^^^^^^^^^ - -.. list-table:: - :header-rows: 1 - :widths: 30 70 - - * - Module - - Purpose - * - ``config.py`` - - TOML configuration loading and validation - * - ``engine.py`` - - Core validation engine and specialized validators - * - ``rule_builder.py`` - - Builds validation rules from configuration - * - ``rules_catalog.py`` - - Centralized catalog of validation rules and messages - * - ``main.py`` - - CLI interface and orchestration - -**Getting Started with v2.0** -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**For New Users:** -^^^^^^^^^^^^^^^^^^ - -1. **Install commit-check v2.0+**: - - .. code-block:: bash - - pip install commit-check>=2.0.0 - -2. **Start with defaults** (no configuration needed): - - .. code-block:: bash - - commit-check --message --branch - -3. **Customize as needed** with ``cchk.toml``: - - .. code-block:: toml - - [commit] - conventional_commits = true - subject_max_length = 72 - -For Existing Users -^^^^^^^^^^^^^^^^^^ -1. **Follow the Migration Guide**: See `Migration Guide `_ -2. **Test thoroughly**: Validate your new configuration before deploying - -**Additional Resources** -~~~~~~~~~~~~~~~~~~~~~~~~ - -* `Configuration Reference `_ - Complete configuration options -* `Migration Guide `_ - Step-by-step upgrade instructions -* `CLI Reference `_ - Command-line interface documentation diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..bd6308db --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,128 @@ +site_name: Commit Check +site_url: https://docs.commit-check.com/ +site_description: >- + Enforce commit message, branch naming, author and signoff standards — + one policy, across your CLI, pre-commit hooks, CI, and AI agents. +site_author: shenxianpeng +copyright: Copyright © 2022 Commit Check + +repo_url: https://github.com/commit-check/commit-check +repo_name: commit-check/commit-check +edit_uri: edit/main/docs/ + +docs_dir: docs + +theme: + name: material + language: en + logo: assets/logo.svg + favicon: assets/favicon.svg + icon: + repo: fontawesome/brands/github + font: + text: Inter + code: JetBrains Mono + palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Follow system theme + - media: "(prefers-color-scheme: light)" + scheme: default + primary: custom + accent: custom + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: custom + accent: custom + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + # Navigation lives in the left sidebar, grouped into sections. + - navigation.sections + - navigation.top + - navigation.tracking + - navigation.instant + - navigation.instant.progress + - navigation.footer + - toc.follow + - search.suggest + - search.highlight + - search.share + - content.code.copy + - content.code.annotate + - content.tabs.link + - content.action.edit + +extra_css: + - assets/extra.css + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/commit-check + name: Commit Check on GitHub + - icon: fontawesome/brands/python + link: https://pypi.org/project/commit-check/ + name: commit-check on PyPI + +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - md_in_html + - tables + - toc: + permalink: true + permalink_title: Link to this section + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + +plugins: + - search + +hooks: + - scripts/mkdocs_hooks.py + +nav: + - Home: index.md + - Getting started: + - Installation: getting-started/installation.md + - Quick start: getting-started/quickstart.md + - Why Commit Check: getting-started/why.md + - Guides: + - Pre-commit hook: guides/pre-commit.md + - GitHub Actions: guides/github-actions.md + - Organization-wide policy: guides/organization.md + - Signoff and DCO: guides/signoff.md + - AI attribution policy: guides/ai-attribution.md + - Command-line recipes: example.md + - Reference: + - Rules: rules.md + - Configuration: configuration.md + - CLI: cli.md + - About: + - Migrating from v1: migration.md + - Troubleshooting: troubleshoot.md + - Release highlights: what-is-new.md + - Changelog: changelog.md diff --git a/noxfile.py b/noxfile.py index 44072ced..70409490 100644 --- a/noxfile.py +++ b/noxfile.py @@ -53,12 +53,12 @@ def coverage(session): @nox.session() def docs(session): session.install(".[docs]") - session.run("sphinx-build", "-E", "-b", "html", "docs", "_build/html") + session.run("mkdocs", "build", "--strict") @nox.session(name="docs-live") def docs_live(session): session.install(".[docs]") session.run( - "sphinx-autobuild", "-b", "html", "docs", "_build/html", "--watch", "docs/" + "mkdocs", "serve" ) diff --git a/pyproject.toml b/pyproject.toml index cc58e8b0..8a0a62eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ tracker = "https://github.com/commit-check/commit-check/issues" [project.optional-dependencies] dev = ['nox==2026.7.11'] test = ['coverage', 'pytest', 'pytest-mock', 'pytest-codspeed'] -docs = ['sphinx<9', 'sphinx-immaterial', 'sphinx-autobuild', 'sphinx_issues', 'myst-parser'] +docs = ['mkdocs-material>=9.7'] ci = ['twine==7.0.0'] [tool.setuptools] diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py new file mode 100644 index 00000000..38127834 --- /dev/null +++ b/scripts/mkdocs_hooks.py @@ -0,0 +1,89 @@ +"""MkDocs build hooks. + +Two jobs: + +* generate ``docs/cli.md`` from the CLI's own ``--help`` output, so the + documented interface cannot drift from the shipped one; +* emit redirects for the ``.html`` URLs the previous Sphinx site served, so + links already published elsewhere keep working. +""" + +from __future__ import annotations + +import subprocess +from pathlib import Path + +#: Sphinx page name -> path under the MkDocs site. +LEGACY_URLS = { + "configuration": "configuration/", + "rules": "rules/", + "example": "example/", + "migration": "migration/", + "troubleshoot": "troubleshoot/", + "changelog": "changelog/", + "what-is-new": "what-is-new/", + "cli_args": "cli/", + "README": "getting-started/installation/", + "genindex": "", +} + +REDIRECT = """ + + + +Redirecting… + + + +Redirecting to {url}… + +""" + +PAGE = """# CLI reference + +Generated from `commit-check --help`. The CLI is also available as `cchk`. + +```console +$ commit-check --help +``` + +```text +{help} +``` + +## See also + +- [Configuration](configuration.md) — every option, with the environment + variable and TOML key that set it. +- [Rules](rules.md) — which flag activates which rule. +""" + + +def on_pre_build(config, **kwargs) -> None: + """Write ``cli.md`` before the build reads the docs directory.""" + try: + result = subprocess.run( + ["commit-check", "--help"], + capture_output=True, + encoding="utf-8", + check=True, + ) + except (OSError, subprocess.CalledProcessError) as exc: # pragma: no cover + raise RuntimeError( + "could not run 'commit-check --help' to generate the CLI reference; " + "install the package first (pip install -e .)" + ) from exc + + text = result.stdout.rstrip() + target = Path(config["docs_dir"], "cli.md") + target.write_text(PAGE.format(help=text), encoding="utf-8") + + +def on_post_build(config, **kwargs) -> None: + """Write a redirect stub for each URL the Sphinx site used to serve.""" + site = Path(config["site_dir"]) + base = config["site_url"] or "/" + for legacy, target in LEGACY_URLS.items(): + (site / f"{legacy}.html").write_text( + REDIRECT.format(url=base + target), encoding="utf-8" + ) diff --git a/tests/rules_catalog_test.py b/tests/rules_catalog_test.py index 15bd7fc6..e8c330be 100644 --- a/tests/rules_catalog_test.py +++ b/tests/rules_catalog_test.py @@ -122,11 +122,11 @@ def test_every_rule_is_documented(self): This prevents shipping a new rule without documenting it. """ - content = _read_doc("rules.rst") + content = _read_doc("rules.md") for entry in ALL_RULES: - anchor = f".. _{entry.rule_id.lower()}:" + anchor = f"{{ #{entry.rule_id.lower()} }}" assert anchor in content, ( - f"{entry.rule_id} ({entry.check}) is missing from docs/rules.rst" + f"{entry.rule_id} ({entry.check}) is missing from docs/rules.md" ) def test_every_rule_has_a_section_heading(self): @@ -135,16 +135,18 @@ def test_every_rule_has_a_section_heading(self): Checked inside the rule's own section: an anchor alone, or a heading that survives elsewhere on the page, would otherwise pass. """ - content = _read_doc("rules.rst") + content = _read_doc("rules.md") for entry in ALL_RULES: - heading = f"{entry.name} ({entry.rule_id})" - assert heading in _rule_section(content, entry.rule_id), ( - f"docs/rules.rst has no section titled '{heading}'" + heading = ( + f"### {entry.name} ({entry.rule_id}) {{ #{entry.rule_id.lower()} }}" + ) + assert heading in content, ( + f"docs/rules.md has no section titled '{heading}'" ) def test_every_rule_explains_itself(self): """Each rule section must answer what it does and why it matters.""" - content = _read_doc("rules.rst") + content = _read_doc("rules.md") for entry in ALL_RULES: section = _rule_section(content, entry.rule_id) for required in ("**What it does**", "**Why is this bad?**", "**Options**"): @@ -161,20 +163,20 @@ class TestDocumentedDefaults: def test_every_runtime_option_is_documented(self): """Every option the runtime defines has a row in the options table.""" - documented = _parse_options_table(_read_doc("configuration.rst")) + documented = _parse_options_table(_read_doc("configuration.md")) for section, options in get_default_config().items(): for option in options: assert (section, option) in documented, ( f"[{section}] {option} exists in get_default_config() but " - f"has no row in the options table of docs/configuration.rst" + f"has no row in the options table of docs/configuration.md" ) def test_no_invented_options_are_documented(self): """The options table does not document options that do not exist.""" runtime = get_default_config() - for section, option in _parse_options_table(_read_doc("configuration.rst")): + for section, option in _parse_options_table(_read_doc("configuration.md")): assert option in runtime.get(section, {}), ( - f"docs/configuration.rst documents [{section}] {option}, which " + f"docs/configuration.md documents [{section}] {option}, which " f"does not exist in get_default_config()" ) @@ -186,7 +188,7 @@ def test_documented_defaults_match_the_runtime(self): no config file". The options table is maintained by hand and silently drifts away from it without this guard. """ - documented = _parse_options_table(_read_doc("configuration.rst")) + documented = _parse_options_table(_read_doc("configuration.md")) runtime = get_default_config() for (section, option), (type_, cell) in sorted(documented.items()): @@ -197,14 +199,14 @@ def test_documented_defaults_match_the_runtime(self): if isinstance(expected, list): # Allow-lists: order carries no meaning, membership does. assert set(actual or []) == set(expected), ( - f"docs/configuration.rst documents [{section}] {option} " + f"docs/configuration.md documents [{section}] {option} " f"with {sorted(set(actual or []) - set(expected))} that are " f"not defaults, and is missing " f"{sorted(set(expected) - set(actual or []))}" ) else: assert actual == expected, ( - f"docs/configuration.rst documents [{section}] {option} as " + f"docs/configuration.md documents [{section}] {option} as " f"{cell.strip()!r}, but the runtime default is {expected!r}" ) @@ -216,15 +218,16 @@ def _read_doc(name: str) -> str: def _rule_section(content: str, rule_id: str) -> str: """Return just the part of the rules page belonging to one rule.""" - _, _, after = content.partition(f".. _{rule_id.lower()}:") - return re.split(r"\n\.\. _cc\d{3}:", after)[0] + _, _, after = content.partition(f"{{ #{rule_id.lower()} }}") + return re.split(r"\{ #cc\d{3} \}", after)[0] _OPTIONS_ROW = re.compile( - r"\*\s+-\s+(commit|branch|push)\s*\n" # section - r"\s+-\s+(\w+)\s*\n" # option name - r"\s+-\s+(bool|int|str|list\[str\])\s*\n" # type - r"\s+-\s+(.+)\n" # documented default + r"^\|\s*(commit|branch|push)\s*" # section + r"\|\s*(\w+)\s*" # option name + r"\|\s*(bool|int|str|list\[str\])\s*" # type + r"\|\s*(.+?)\s*\|", # documented default + re.M, ) @@ -245,7 +248,8 @@ def _parse_options_table(content: str) -> dict[tuple[str, str], tuple[str, str]] } -_QUOTED = re.compile(r'^(?:``(.*?)``|"(.*?)")') +# Markdown code spans use single backticks; RST used double. +_QUOTED = re.compile(r'^(?:`+(.*?)`+|"(.*?)")') def _documented_default(type_: str, cell: str) -> Any: From 7d35da51f4e1ce9cf7a7b0615b107aaeda3b4d18 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:37:19 +0000 Subject: [PATCH 2/4] ci: auto fixes from pre-commit.com hooks --- docs/changelog.md | 2 +- docs/example.md | 2 +- docs/migration.md | 2 +- docs/rules.md | 2 +- docs/what-is-new.md | 2 +- noxfile.py | 4 +--- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index d5178863..f1fccb02 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -184,4 +184,4 @@ Last release before the big v2.0 changes. ## v0.1.0 (2022-11-02) -Initial release of commit-check. \ No newline at end of file +Initial release of commit-check. diff --git a/docs/example.md b/docs/example.md index 371e079f..5d90ade3 100644 --- a/docs/example.md +++ b/docs/example.md @@ -371,4 +371,4 @@ done echo "All commits are valid!" ``` -For more configuration options, see the [Configuration Documentation](configuration.md). \ No newline at end of file +For more configuration options, see the [Configuration Documentation](configuration.md). diff --git a/docs/migration.md b/docs/migration.md index 2c572b2b..45d57ffa 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -186,4 +186,4 @@ commit-check --message --branch --author-name --author-email --dry-run ## Getting Help * **Documentation**: Check the [Configuration Guide](configuration.md) -* **Issues**: Report problems on [GitHub Issues](https://github.com/commit-check/commit-check/issues) \ No newline at end of file +* **Issues**: Report problems on [GitHub Issues](https://github.com/commit-check/commit-check/issues) diff --git a/docs/rules.md b/docs/rules.md index 51fbc5fe..e8b3f587 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -692,4 +692,4 @@ git push --force-with-lease **Options** -* `push.allow_force_push` — set to `false` to enable this rule. \ No newline at end of file +* `push.allow_force_push` — set to `false` to enable this rule. diff --git a/docs/what-is-new.md b/docs/what-is-new.md index 1a568087..1b480885 100644 --- a/docs/what-is-new.md +++ b/docs/what-is-new.md @@ -363,4 +363,4 @@ subject_max_length = 72 * [Configuration Reference](configuration.md) - Complete configuration options * [Migration Guide](migration.md) - Step-by-step upgrade instructions -* [CLI Reference](cli.md) - Command-line interface documentation \ No newline at end of file +* [CLI Reference](cli.md) - Command-line interface documentation diff --git a/noxfile.py b/noxfile.py index 70409490..3ed32045 100644 --- a/noxfile.py +++ b/noxfile.py @@ -59,6 +59,4 @@ def docs(session): @nox.session(name="docs-live") def docs_live(session): session.install(".[docs]") - session.run( - "mkdocs", "serve" - ) + session.run("mkdocs", "serve") From 1e5ee722aa299b0e5650070fe876d676df2daa86 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 3 Aug 2026 16:40:55 +0000 Subject: [PATCH 3/4] ci: exempt mkdocs.yml from check-yaml Material's emoji extension is configured with `!!python/name:` tags, which yaml.safe_load cannot construct, so the hook fails on a file that is perfectly valid for the tool that consumes it. MkDocs parses and validates the same file on every build, and CI runs that build with --strict, so excluding it here loses no coverage. --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index efb135f6..50a3a9c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,11 @@ repos: rev: v6.0.0 hooks: - id: check-yaml + # mkdocs.yml carries the `!!python/name:` tags that Material's emoji + # extension requires, which yaml.safe_load cannot construct. MkDocs + # validates the file itself on every build (`mkdocs build --strict`), + # so it is checked more thoroughly than this hook would manage. + exclude: ^mkdocs\.yml$ - id: check-toml - id: end-of-file-fixer - id: trailing-whitespace From 3b4faa2e7208603b1f08087d97fbddf6bba95e87 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 3 Aug 2026 16:47:05 +0000 Subject: [PATCH 4/4] ci: define the Netlify docs build in the repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deploy previews stopped appearing on documentation pull requests. The build settings lived in Netlify's web UI and still pointed at Sphinx's _build/html, so the move to MkDocs left them publishing a directory that no longer exists — a break that nothing in the repository could show in review. Move the build into netlify.toml, so the preview build changes in the same commit as the toolchain it builds. Deploy previews also now set SITE_URL to their own address, and mkdocs.yml reads it. Otherwise the canonical links and the generated redirect stubs on a preview point back at the production site, which sends a reviewer away from the very change they are reviewing. --- mkdocs.yml | 4 +++- netlify.toml | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 netlify.toml diff --git a/mkdocs.yml b/mkdocs.yml index bd6308db..04669a01 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,7 @@ site_name: Commit Check -site_url: https://docs.commit-check.com/ +# Deploy previews override this so the preview is self-consistent; see +# netlify.toml. +site_url: !ENV [SITE_URL, 'https://docs.commit-check.com/'] site_description: >- Enforce commit message, branch naming, author and signoff standards — one policy, across your CLI, pre-commit hooks, CI, and AI agents. diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 00000000..a4d3c81a --- /dev/null +++ b/netlify.toml @@ -0,0 +1,30 @@ +# Netlify builds the documentation for deploy previews on pull requests. +# +# This file exists so the build is defined in version control rather than in +# Netlify's web UI: when the docs toolchain changes, the preview build changes +# with it in the same commit, and a mismatch shows up in review instead of +# silently producing an empty preview. + +[build] + command = "pip install '.[docs]' && mkdocs build --strict" + publish = "site" + +[build.environment] + # MkDocs Material needs a modern Python; the package itself supports 3.10+. + PYTHON_VERSION = "3.12" + +# On a deploy preview, point the site at the preview itself. Without this the +# canonical links and the generated redirect stubs would send reviewers from +# the preview back to the production site, which defeats the purpose. +[context.deploy-preview.environment] + SITE_URL = "${DEPLOY_PRIME_URL}" + +[context.branch-deploy.environment] + SITE_URL = "${DEPLOY_PRIME_URL}" + +# Serve the legacy Sphinx URLs from the redirect stubs the build emits, and +# give anything genuinely missing the themed 404 page. +[[redirects]] + from = "/*" + to = "/404.html" + status = 404