-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This uses my personal preferences [1] to lint the project's history. [1] https://github.com/Notgnoshi/dotfiles/tree/master/stowdir/.config/gitlint
- Loading branch information
Showing
4 changed files
with
401 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import re | ||
|
||
from gitlint.rules import BodyMaxLineLength | ||
|
||
|
||
class BodyMaxLineLengthWithExceptions(BodyMaxLineLength): | ||
"""Extend the existing body-max-line-length rule. | ||
Allow narrow exceptions, specifically leading block quote-style | ||
indent and footnote-style URLs, to the line length enforcement. | ||
""" | ||
|
||
name = "body-max-line-length-with-exceptions" | ||
id = "UC2" | ||
|
||
def validate(self, line, commit): | ||
# Allow block-quoted lines to exceed the line length limit. | ||
if line.startswith(" " * 4): | ||
return None | ||
|
||
# Allow footnote lines to exceed the line length limit. | ||
ret = re.match(r"^\[\d+\] ", line) | ||
if ret is not None: | ||
return None | ||
|
||
return super().validate(line, commit) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
[general] | ||
ignore=body-is-missing,body-max-line-length | ||
|
||
ignore-merge-commits=false | ||
# If you're reverting a commit, the revert commit should darn well contain enough information about WHY | ||
ignore-revert-commits=false | ||
ignore-fixup-commits=false | ||
ignore-fixup-amend-commits=false | ||
ignore-squash-commits=false | ||
|
||
# This will be the default in the future, so enable now | ||
regex-style-search=true | ||
|
||
[title-must-not-contain-word] | ||
words=wip,fixup,fixups,squash,fixme,dropme,dontmerge,donotmerge | ||
|
||
# 72 should be the target, but don't fail until it's past 80. | ||
[body-max-line-length-with-exceptions] | ||
line-length=80 | ||
|
||
# Not every commit needs a body. Some are simple enough just a title will do | ||
[body-min-length] | ||
min-length=0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
from typing import List | ||
|
||
from gitlint.git import GitCommit | ||
from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation | ||
|
||
# Word list from https://github.com/m1foley/fit-commit | ||
# Copyright (c) 2015 Mike Foley | ||
# License: MIT | ||
# Ref: fit_commit/validators/tense.rb | ||
WORD_SET = { | ||
"adds", | ||
"adding", | ||
"added", | ||
"allows", | ||
"allowing", | ||
"allowed", | ||
"amends", | ||
"amending", | ||
"amended", | ||
"bumps", | ||
"bumping", | ||
"bumped", | ||
"calculates", | ||
"calculating", | ||
"calculated", | ||
"changes", | ||
"changing", | ||
"changed", | ||
"cleans", | ||
"cleaning", | ||
"cleaned", | ||
"commits", | ||
"committing", | ||
"committed", | ||
"corrects", | ||
"correcting", | ||
"corrected", | ||
"creates", | ||
"creating", | ||
"created", | ||
"darkens", | ||
"darkening", | ||
"darkened", | ||
"disables", | ||
"disabling", | ||
"disabled", | ||
"displays", | ||
"displaying", | ||
"displayed", | ||
"documents", | ||
"documenting", | ||
"documented", | ||
"drys", | ||
"drying", | ||
"dryed", | ||
"ends", | ||
"ending", | ||
"ended", | ||
"enforces", | ||
"enforcing", | ||
"enforced", | ||
"enqueues", | ||
"enqueuing", | ||
"enqueued", | ||
"extracts", | ||
"extracting", | ||
"extracted", | ||
"finishes", | ||
"finishing", | ||
"finished", | ||
"fixes", | ||
"fixing", | ||
"fixed", | ||
"formats", | ||
"formatting", | ||
"formatted", | ||
"guards", | ||
"guarding", | ||
"guarded", | ||
"handles", | ||
"handling", | ||
"handled", | ||
"hides", | ||
"hiding", | ||
"hid", | ||
"increases", | ||
"increasing", | ||
"increased", | ||
"ignores", | ||
"ignoring", | ||
"ignored", | ||
"implements", | ||
"implementing", | ||
"implemented", | ||
"improves", | ||
"improving", | ||
"improved", | ||
"keeps", | ||
"keeping", | ||
"kept", | ||
"kills", | ||
"killing", | ||
"killed", | ||
"makes", | ||
"making", | ||
"made", | ||
"merges", | ||
"merging", | ||
"merged", | ||
"moves", | ||
"moving", | ||
"moved", | ||
"permits", | ||
"permitting", | ||
"permitted", | ||
"prevents", | ||
"preventing", | ||
"prevented", | ||
"pushes", | ||
"pushing", | ||
"pushed", | ||
"rebases", | ||
"rebasing", | ||
"rebased", | ||
"refactors", | ||
"refactoring", | ||
"refactored", | ||
"removes", | ||
"removing", | ||
"removed", | ||
"renames", | ||
"renaming", | ||
"renamed", | ||
"reorders", | ||
"reordering", | ||
"reordered", | ||
"replaces", | ||
"replacing", | ||
"replaced", | ||
"requires", | ||
"requiring", | ||
"required", | ||
"restores", | ||
"restoring", | ||
"restored", | ||
"sends", | ||
"sending", | ||
"sent", | ||
"sets", | ||
"setting", | ||
"separates", | ||
"separating", | ||
"separated", | ||
"shows", | ||
"showing", | ||
"showed", | ||
"simplifies", | ||
"simplifying", | ||
"simplified", | ||
"skips", | ||
"skipping", | ||
"skipped", | ||
"sorts", | ||
"sorting", | ||
"speeds", | ||
"speeding", | ||
"sped", | ||
"starts", | ||
"starting", | ||
"started", | ||
"supports", | ||
"supporting", | ||
"supported", | ||
"takes", | ||
"taking", | ||
"took", | ||
"testing", | ||
"tested", # 'tests' excluded to reduce false negative | ||
"truncates", | ||
"truncating", | ||
"truncated", | ||
"updates", | ||
"updating", | ||
"updated", | ||
"uses", | ||
"using", | ||
"used", | ||
} | ||
|
||
imperative_forms = [ | ||
"add", | ||
"allow", | ||
"amend", | ||
"bump", | ||
"calculate", | ||
"change", | ||
"clean", | ||
"commit", | ||
"correct", | ||
"create", | ||
"darken", | ||
"disable", | ||
"display", | ||
"document", | ||
"dry", | ||
"end", | ||
"enforce", | ||
"enqueue", | ||
"extract", | ||
"finish", | ||
"fix", | ||
"format", | ||
"guard", | ||
"handle", | ||
"hide", | ||
"ignore", | ||
"implement", | ||
"improve", | ||
"increase", | ||
"keep", | ||
"kill", | ||
"make", | ||
"merge", | ||
"move", | ||
"permit", | ||
"prevent", | ||
"push", | ||
"rebase", | ||
"refactor", | ||
"remove", | ||
"rename", | ||
"reorder", | ||
"replace", | ||
"require", | ||
"restore", | ||
"send", | ||
"separate", | ||
"set", | ||
"show", | ||
"simplify", | ||
"skip", | ||
"sort", | ||
"speed", | ||
"start", | ||
"support", | ||
"take", | ||
"test", | ||
"truncate", | ||
"update", | ||
"use", | ||
] | ||
imperative_forms.sort() | ||
|
||
|
||
def head_binary_search(key: str, words: List[str]) -> str: | ||
"""Find the imperative mood version of `word` by looking at the first 3 characters.""" | ||
# Edge case: 'disable' and 'display' have the same 3 starting letters. | ||
if key in ["displays", "displaying", "displayed"]: | ||
return "display" | ||
|
||
lower = 0 | ||
upper = len(words) - 1 | ||
|
||
while True: | ||
if lower > upper: | ||
# Should not happen | ||
raise Exception(f"Cannot find imperative mood of {key}") | ||
|
||
mid = (lower + upper) // 2 | ||
imperative_form = words[mid] | ||
|
||
if key[:3] == imperative_form[:3]: | ||
return imperative_form | ||
if key < imperative_form: | ||
upper = mid - 1 | ||
elif key > imperative_form: | ||
lower = mid + 1 | ||
|
||
|
||
class ImperativeMood(LineRule): | ||
"""A rule to enforce that the commit message title uses imperative mood. | ||
This is done by checking if the first word is in `WORD_SET`, if so show the word in the correct | ||
mood. | ||
""" | ||
|
||
name = "title-imperative-mood" | ||
id = "UC1" | ||
target = CommitMessageTitle | ||
|
||
error_msg = ( | ||
"The first word in commit title should be in imperative mood " | ||
'("{word}" -> "{imperative}"): "{title}"' | ||
) | ||
|
||
def validate(self, line: str, commit: GitCommit) -> List[RuleViolation]: | ||
"""Validate the given commit message.""" | ||
violations = [] | ||
|
||
# Ignore the section tag (ie `<section tag>: <message body>.`) | ||
words = line.split(": ", 1)[-1].split() | ||
|
||
if not words: | ||
return None | ||
|
||
first_word = words[0].lower() | ||
|
||
if first_word in WORD_SET: | ||
imperative = head_binary_search(first_word, imperative_forms) | ||
violation = RuleViolation( | ||
self.id, | ||
self.error_msg.format( | ||
word=first_word, | ||
imperative=imperative, | ||
title=commit.message.title, | ||
), | ||
) | ||
|
||
violations.append(violation) | ||
|
||
return violations |
Oops, something went wrong.