Skip to content

Setup Gitlint. #645

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .gitlint
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This file is copied from the original .gitlint at zulip/zulip.
# Please don't edit here; instead update the zulip/zulip copy and then resync this file.

[general]
ignore=title-trailing-punctuation, body-min-length, body-is-missing
extra-path=tools/gitlint-rules.py

[title-match-regex]
regex=^(.+:\ )?[A-Z].+\.$

[title-max-length]
line-length=76

[body-max-line-length]
line-length=76
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ This is part of the Zulip open source project; see the
[contributing guide](https://zulip.readthedocs.io/en/latest/overview/contributing.html)
and [commit guidelines](https://zulip.readthedocs.io/en/latest/contributing/version-control.html).

1. Fork and clone the Git repo:
`git clone https://github.com/<your_username>/python-zulip-api.git`

2. Make sure you have [pip](https://pip.pypa.io/en/stable/installing/)
and [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html)
installed.
1. Fork and clone the Git repo, and set upstream to zulip/python-zulip-api:
```
git clone https://github.com/<your_username>/python-zulip-api.git
cd python-zulip-api
git remote add upstream https://github.com/zulip/python-zulip-api.git
git fetch upstream
```

3. `cd` into the repository cloned earlier:
`cd python-zulip-api`
2. Make sure you have [pip](https://pip.pypa.io/en/stable/installing/).

4. Run:
3. Run:
```
python3 ./tools/provision
```
Expand All @@ -43,14 +43,14 @@ and [commit guidelines](https://zulip.readthedocs.io/en/latest/contributing/vers
python3 ./tools/provision -p <path_to_your_python_version>
```

5. If that succeeds, it will end with printing the following command:
4. If that succeeds, it will end with printing the following command:
```
source /.../python-zulip-api/.../activate
```
You can run this command to enter the virtual environment.
You'll want to run this in each new shell before running commands from `python-zulip-api`.

6. Once you've entered the virtualenv, you should see something like this on the terminal:
5. Once you've entered the virtualenv, you should see something like this on the terminal:
```
(zulip-api-py3-venv) user@pc ~/python-zulip-api $
```
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ pytest
-e ./zulip_botserver
-e git+https://github.com/zulip/zulint@14e3974001bf8442a6a3486125865660f1f2eb68#egg=zulint==1.0.0
mypy==0.790
gitlint>=0.13.0
145 changes: 145 additions & 0 deletions tools/gitlint-rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# This file is copied from the original at tools/lib/gitlint-rules.py in zulip/zulip.
# Please don't edit here; instead update the zulip/zulip copy and then resync this file.

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
elif key < imperative_form:
upper = mid - 1
elif key > imperative_form:
lower = mid + 1


class ImperativeMood(LineRule):
""" This rule will 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 = "Z1"
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]:
violations = []

# Ignore the section tag (ie `<section tag>: <message body>.`)
words = line.split(': ', 1)[-1].split()
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
5 changes: 5 additions & 0 deletions tools/lint
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ EXCLUDED_FILES = [
def run() -> None:
parser = argparse.ArgumentParser()
add_default_linter_arguments(parser)
parser.add_argument('--no-gitlint', action='store_true', help='Disable gitlint')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zulint already has an option for this: #659.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will create a PR soon, fixing the required changes.

args = parser.parse_args()

linter_config = LinterConfig(args)
Expand All @@ -27,6 +28,10 @@ def run() -> None:
linter_config.external_linter('flake8', ['flake8'], ['py'],
description="Standard Python linter (config: .flake8)")

if not args.no_gitlint:
linter_config.external_linter('gitlint', ['tools/lint-commits'],
description="Git Lint for commit messages")

@linter_config.lint
def custom_py() -> int:
"""Runs custom checks for python files (config: tools/linter_lib/custom_check.py)"""
Expand Down
27 changes: 27 additions & 0 deletions tools/lint-commits
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

# This file is copied from the original tools/commit-message-lint at zulip/zulip,
# Edited at Line 14 Col 97 (zulip -> python-zulip-api)
# Please don't edit here; instead update the zulip/zulip copy and then resync this file.

# Lint all commit messages that are newer than upstream/master if running
# locally or the commits in the push or PR Gh-Actions.

# The rules can be found in /.gitlint

if [[ "
$(git remote -v)
" =~ '
'([^[:space:]]*)[[:space:]]*(https://github\.com/|ssh://git@github\.com/|git@github\.com:)zulip/python-zulip-api(\.git|/)?\ \(fetch\)'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we should do a commit to zulip/zulip that makes the repository name a variable at the top of the file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, we can do that.

' ]]; then
range="${BASH_REMATCH[1]}/master..HEAD"
else
range="upstream/master..HEAD"
fi

commits=$(git log "$range" | wc -l)
if [ "$commits" -gt 0 ]; then
# Only run gitlint with non-empty commit lists, to avoid a printed
# warning.
gitlint --commits "$range"
fi
2 changes: 1 addition & 1 deletion tools/test-static-analysis
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

set -ev

tools/lint
tools/lint --no-gitlint
tools/run-mypy