Merging upstream version 0.15.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
470a4841cc
commit
3213982697
75 changed files with 1281 additions and 1555 deletions
4
.github/workflows/checks.yml
vendored
4
.github/workflows/checks.yml
vendored
|
@ -7,7 +7,7 @@ jobs:
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, pypy2, pypy3]
|
python-version: [3.6, 3.7, 3.8, 3.9, pypy3]
|
||||||
os: ["macos-latest", "ubuntu-latest"]
|
os: ["macos-latest", "ubuntu-latest"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
@ -76,7 +76,7 @@ jobs:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [2.7, 3.6]
|
python-version: [3.6]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
- id: gitlint
|
- id: gitlint
|
||||||
name: gitlint
|
name: gitlint
|
||||||
language: python
|
language: python
|
||||||
entry: gitlint --staged --msg-filename
|
entry: gitlint
|
||||||
|
args: [--staged, --msg-filename]
|
||||||
stages: [commit-msg]
|
stages: [commit-msg]
|
||||||
|
|
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -1,9 +1,22 @@
|
||||||
# Changelog #
|
# Changelog #
|
||||||
|
|
||||||
|
## v0.15.0 (2020-11-27) ##
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Special thanks to [BrunIF](https://github.com/BrunIF), [lukech](https://github.com/lukech), [Cielquan](https://github.com/Cielquan), [harens](https://github.com/harens) and [sigmavirus24](https://github.com/sigmavirus24).
|
||||||
|
|
||||||
|
**This release drops support for Python 2.7 and Python 3.5 ([both are EOL](https://endoflife.date/python)). Other than a few minor fixes, there are no functional differences from the 0.14.0 release.**
|
||||||
|
|
||||||
|
Other call-outs:
|
||||||
|
- **Mac users**: Gitlint can now be installed using both homebrew (upgraded to latest) and macports. Special thanks to [@harens](https://github.com/harens) for maintaining these packages (best-effort).
|
||||||
|
- Bugfix: Gitlint now properly handles exceptions when using its built-in commit-msg hook ([#166](https://github.com/jorisroovers/gitlint/issues/166)).
|
||||||
|
- All dependencies have been upgraded to the latest available versions (`Click==7.1.2`, `arrow==0.17.0`, `sh==1.14.1`).
|
||||||
|
- Much under-the-hood refactoring as a result of dropping Python 2.7
|
||||||
|
|
||||||
## v0.14.0 (2020-10-24) ##
|
## v0.14.0 (2020-10-24) ##
|
||||||
|
|
||||||
Contributors:
|
Contributors:
|
||||||
Special thanks to all contributors for this release, in particular [@mrshu](https://github.com/mrshu), [@glasserc](https://github.com/glasserc), [@strk](https://github.com/strk), [@chgl](https://github.com/chgl), [@melg8](https://github.com/melg8) and [@sigmavirus24](https://github.com/sigmavirus24).
|
Special thanks to all contributors for this release, in particular [mrshu](https://github.com/mrshu), [glasserc](https://github.com/glasserc), [strk](https://github.com/strk), [chgl](https://github.com/chgl), [melg8](https://github.com/melg8) and [sigmavirus24](https://github.com/sigmavirus24).
|
||||||
|
|
||||||
|
|
||||||
- **IMPORTANT: Gitlint 0.14.x will be the last gitlint release to support Python 2.7 and Python 3.5, as [both are EOL](https://endoflife.date/python) which makes it difficult to keep supporting them.**
|
- **IMPORTANT: Gitlint 0.14.x will be the last gitlint release to support Python 2.7 and Python 3.5, as [both are EOL](https://endoflife.date/python) which makes it difficult to keep supporting them.**
|
||||||
|
@ -13,7 +26,7 @@ Special thanks to all contributors for this release, in particular [@mrshu](http
|
||||||
- **New Rule**: [ignore-body-lines](http://jorisroovers.github.io/gitlint/rules/#i3-ignore-body-lines) allows users to
|
- **New Rule**: [ignore-body-lines](http://jorisroovers.github.io/gitlint/rules/#i3-ignore-body-lines) allows users to
|
||||||
[ignore parts of a commit](http://jorisroovers.github.io/gitlint/gitlint/#ignoring-commits) by matching a regex against
|
[ignore parts of a commit](http://jorisroovers.github.io/gitlint/gitlint/#ignoring-commits) by matching a regex against
|
||||||
the lines in a commit message body ([#126](https://github.com/jorisroovers/gitlint/issues/126))
|
the lines in a commit message body ([#126](https://github.com/jorisroovers/gitlint/issues/126))
|
||||||
- [Named Rules](http://jorisroovers.github.io/gitlint/#named-rules) allow users to have multiple instances of the same rule active at the same time. This is useful when you want to enforce the same rule multiple times but with different options ([#113](https://github.com/jorisroovers/gitlint/issues/130), [#66](https://github.com/jorisroovers/gitlint/issues/130))
|
- [Named Rules](http://jorisroovers.github.io/gitlint/#named-rules) allow users to have multiple instances of the same rule active at the same time. This is useful when you want to enforce the same rule multiple times but with different options ([#113](https://github.com/jorisroovers/gitlint/issues/113), [#66](https://github.com/jorisroovers/gitlint/issues/66))
|
||||||
- [User-defined Configuration Rules](http://jorisroovers.github.io/gitlint/user_defined_rules/#configuration-rules) allow users to dynamically change gitlint's configuration and/or the commit *before* any other rules are applied.
|
- [User-defined Configuration Rules](http://jorisroovers.github.io/gitlint/user_defined_rules/#configuration-rules) allow users to dynamically change gitlint's configuration and/or the commit *before* any other rules are applied.
|
||||||
- The `commit-msg` hook has been re-written in Python (it contained a lot of Bash before), fixing a number of platform specific issues. Existing users will need to reinstall their hooks (`gitlint uninstall-hook; gitlint install-hook`) to make use of this.
|
- The `commit-msg` hook has been re-written in Python (it contained a lot of Bash before), fixing a number of platform specific issues. Existing users will need to reinstall their hooks (`gitlint uninstall-hook; gitlint install-hook`) to make use of this.
|
||||||
- Most general options can now be set through environment variables (e.g. set the `general.ignore` option via `GITLINT_IGNORE=T1,T2`). The list of available environment variables can be found in the [configuration documentation](http://jorisroovers.github.io/gitlint/configuration).
|
- Most general options can now be set through environment variables (e.g. set the `general.ignore` option via `GITLINT_IGNORE=T1,T2`). The list of available environment variables can be found in the [configuration documentation](http://jorisroovers.github.io/gitlint/configuration).
|
||||||
|
|
13
Vagrantfile
vendored
13
Vagrantfile
vendored
|
@ -7,18 +7,19 @@ INSTALL_DEPS=<<EOF
|
||||||
cd /vagrant
|
cd /vagrant
|
||||||
sudo add-apt-repository -y ppa:deadsnakes/ppa
|
sudo add-apt-repository -y ppa:deadsnakes/ppa
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y --allow-unauthenticated python2.7-dev python3.5-dev python3.6-dev python3.7-dev python3.8-dev python3.9-dev
|
sudo apt-get install -y --allow-unauthenticated python3.6-dev python3.7-dev python3.8-dev python3.9-dev
|
||||||
sudo apt-get install -y --allow-unauthenticated python3.8-distutils python3.9-distutils # Needed to work around python3.8/9+virtualenv issue
|
sudo apt-get install -y --allow-unauthenticated python3.8-distutils python3.9-distutils # Needed to work around python3.8/9+virtualenv issue
|
||||||
sudo apt-get install -y python-virtualenv git ipython python-pip python3-pip silversearcher-ag jq
|
sudo apt-get install -y git python3-pip ripgrep jq
|
||||||
sudo apt-get install -y build-essential libssl-dev libffi-dev # for rebuilding cryptography (required for pypy2)
|
sudo apt-get install -y build-essential libssl-dev libffi-dev # for rebuilding cryptography (required for pypy2)
|
||||||
sudo apt-get purge -y python3-virtualenv
|
sudo apt-get install -y python3-pip
|
||||||
sudo pip3 install virtualenv
|
pip3 install -U pip
|
||||||
|
pip3 install 'virtualenv!=20.1.0'
|
||||||
|
|
||||||
./run_tests.sh --uninstall --envs all
|
./run_tests.sh --uninstall --envs all
|
||||||
./run_tests.sh --install --envs all
|
./run_tests.sh --install --envs all
|
||||||
|
|
||||||
grep 'cd /vagrant' /home/vagrant/.bashrc || echo 'cd /vagrant' >> /home/vagrant/.bashrc
|
grep 'cd /vagrant' /home/vagrant/.bashrc || echo 'cd /vagrant' >> /home/vagrant/.bashrc
|
||||||
grep 'source .venv27/bin/activate' /home/vagrant/.bashrc || echo 'source .venv27/bin/activate' >> /home/vagrant/.bashrc
|
grep 'source .venv36/bin/activate' /home/vagrant/.bashrc || echo 'source .venv36/bin/activate' >> /home/vagrant/.bashrc
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
INSTALL_JENKINS=<<EOF
|
INSTALL_JENKINS=<<EOF
|
||||||
|
@ -31,7 +32,7 @@ EOF
|
||||||
|
|
||||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
|
|
||||||
config.vm.box = "ubuntu/xenial64"
|
config.vm.box = "ubuntu/focal64"
|
||||||
|
|
||||||
config.vm.define "dev" do |dev|
|
config.vm.define "dev" do |dev|
|
||||||
dev.vm.provision "gitlint", type: "shell", inline: "#{INSTALL_DEPS}"
|
dev.vm.provision "gitlint", type: "shell", inline: "#{INSTALL_DEPS}"
|
||||||
|
|
|
@ -80,7 +80,7 @@ min-length=5
|
||||||
words=wip
|
words=wip
|
||||||
|
|
||||||
[title-match-regex]
|
[title-match-regex]
|
||||||
# python like regex (https://docs.python.org/2/library/re.html) that the
|
# python like regex (https://docs.python.org/3/library/re.html) that the
|
||||||
# commit-msg title must be matched to.
|
# commit-msg title must be matched to.
|
||||||
# Note that the regex can contradict with other rules if not used correctly
|
# Note that the regex can contradict with other rules if not used correctly
|
||||||
# (e.g. title-must-not-contain-word).
|
# (e.g. title-must-not-contain-word).
|
||||||
|
@ -100,8 +100,8 @@ ignore-merge-commits=false
|
||||||
[body-changed-file-mention]
|
[body-changed-file-mention]
|
||||||
# List of files that need to be explicitly mentioned in the body when they are changed
|
# List of files that need to be explicitly mentioned in the body when they are changed
|
||||||
# This is useful for when developers often erroneously edit certain files or git submodules.
|
# This is useful for when developers often erroneously edit certain files or git submodules.
|
||||||
# By specifying this rule, developers can only change the file when they explicitly reference
|
# By specifying this rule, developers can only change the file when they explicitly
|
||||||
# it in the commit message.
|
# reference it in the commit message.
|
||||||
files=gitlint/rules.py,README.md
|
files=gitlint/rules.py,README.md
|
||||||
|
|
||||||
[body-match-regex]
|
[body-match-regex]
|
||||||
|
@ -110,9 +110,10 @@ files=gitlint/rules.py,README.md
|
||||||
regex=My-Commit-Tag: foo$
|
regex=My-Commit-Tag: foo$
|
||||||
|
|
||||||
[author-valid-email]
|
[author-valid-email]
|
||||||
# python like regex (https://docs.python.org/2/library/re.html) that the
|
# python like regex (https://docs.python.org/3/library/re.html) that the
|
||||||
# commit author email address should be matched to
|
# commit author email address should be matched to
|
||||||
# E.g.: For example, use the following regex if you only want to allow email addresses from foo.com
|
# E.g.: For example, use the following regex if you only want to allow email
|
||||||
|
# addresses from foo.com
|
||||||
regex=[^@]+@foo.com
|
regex=[^@]+@foo.com
|
||||||
|
|
||||||
[ignore-by-title]
|
[ignore-by-title]
|
||||||
|
@ -330,8 +331,10 @@ Default value | gitlint version | commandline flag | environment
|
||||||
```sh
|
```sh
|
||||||
# CLI
|
# CLI
|
||||||
gitlint --contrib=contrib-title-conventional-commits,CC1
|
gitlint --contrib=contrib-title-conventional-commits,CC1
|
||||||
gitlint -c general.contrib=contrib-title-conventional-commits,CC1 # different way of doing the same
|
# different way of doing the same
|
||||||
GITLINT_CONTRIB=contrib-title-conventional-commits,CC1 gitlint # using env variable
|
gitlint -c general.contrib=contrib-title-conventional-commits,CC1
|
||||||
|
# using env variable
|
||||||
|
GITLINT_CONTRIB=contrib-title-conventional-commits,CC1 gitlint
|
||||||
```
|
```
|
||||||
```ini
|
```ini
|
||||||
#.gitlint
|
#.gitlint
|
||||||
|
@ -341,7 +344,7 @@ contrib=contrib-title-conventional-commits,CC1
|
||||||
|
|
||||||
### staged
|
### staged
|
||||||
|
|
||||||
Fetch additional meta-data from the local `repository when manually passing a commit message to gitlint via stdin or `--commit-msg`.
|
Fetch additional meta-data from the local repository when manually passing a commit message to gitlint via stdin or `--commit-msg`.
|
||||||
|
|
||||||
Default value | gitlint version | commandline flag | environment variable
|
Default value | gitlint version | commandline flag | environment variable
|
||||||
---------------|------------------|-------------------|-----------------------
|
---------------|------------------|-------------------|-----------------------
|
||||||
|
|
|
@ -34,7 +34,8 @@ and it's likely that your PR will be merged and released a lot sooner. Thanks!
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
There is a Vagrantfile in this repository that can be used for development.
|
There is a Vagrantfile (Ubuntu) in this repository that can be used for development.
|
||||||
|
It comes pre-installed with all Python versions that gitlint supports.
|
||||||
```sh
|
```sh
|
||||||
vagrant up
|
vagrant up
|
||||||
vagrant ssh
|
vagrant ssh
|
||||||
|
@ -51,7 +52,7 @@ python setup.py develop
|
||||||
To run tests:
|
To run tests:
|
||||||
```sh
|
```sh
|
||||||
./run_tests.sh # run unit tests and print test coverage
|
./run_tests.sh # run unit tests and print test coverage
|
||||||
./run_test.sh gitlint/tests/test_body_rules.py::BodyRuleTests::test_body_missing # run a single test
|
./run_tests.sh gitlint/tests/rules/test_body_rules.py::BodyRuleTests::test_body_missing # run a single test
|
||||||
./run_tests.sh --no-coverage # run unit tests without test coverage
|
./run_tests.sh --no-coverage # run unit tests without test coverage
|
||||||
./run_tests.sh --collect-only --no-coverage # Only collect, don't run unit tests
|
./run_tests.sh --collect-only --no-coverage # Only collect, don't run unit tests
|
||||||
./run_tests.sh --integration # Run integration tests (requires that you have gitlint installed)
|
./run_tests.sh --integration # Run integration tests (requires that you have gitlint installed)
|
||||||
|
@ -63,12 +64,12 @@ To run tests:
|
||||||
./run_tests.sh --all # Run unit, integration, pep8 and gitlint checks
|
./run_tests.sh --all # Run unit, integration, pep8 and gitlint checks
|
||||||
```
|
```
|
||||||
|
|
||||||
The `Vagrantfile` comes with `virtualenv`s for python 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 and pypy2.
|
The `Vagrantfile` comes with `virtualenv`s for python 3.6, 3.7, 3.8, 3.9 and pypy3.6.
|
||||||
You can easily run tests against specific python environments by using the following commands *inside* of the Vagrant VM:
|
You can easily run tests against specific python environments by using the following commands *inside* of the Vagrant VM:
|
||||||
```sh
|
```sh
|
||||||
./run_tests.sh --envs 27 # Run the unit tests against Python 2.7
|
./run_tests.sh --envs 36 # Run the unit tests against Python 3.6
|
||||||
./run_tests.sh --envs 27,37,pypy2 # Run the unit tests against Python 2.7, Python 3.7 and Pypy2
|
./run_tests.sh --envs 36,37,pypy36 # Run the unit tests against Python 3.6, Python 3.7 and Pypy3.6
|
||||||
./run_tests.sh --envs 27,37 --pep8 # Run pep8 checks against Python 2.7 and Python 3.7 (also works for --git, --integration, --pep8, --stats and --lint.
|
./run_tests.sh --envs 36,37 --pep8 # Run pep8 checks against Python 3.6 and Python 3.7 (also works for --git, --integration, --pep8, --stats and --lint.
|
||||||
./run_tests.sh --envs all --all # Run all tests against all environments
|
./run_tests.sh --envs all --all # Run all tests against all environments
|
||||||
./run_tests.sh --all-env --all # Idem: Run all tests against all environments
|
./run_tests.sh --all-env --all # Idem: Run all tests against all environments
|
||||||
```
|
```
|
||||||
|
|
|
@ -15,7 +15,7 @@ Great for use as a [commit-msg git hook](#using-gitlint-as-a-commit-msg-hook) or
|
||||||
|
|
||||||
|
|
||||||
!!! important
|
!!! important
|
||||||
**Gitlint will soon be dropping support for Python 2.7 and Python 3.5 as they [have reached End-Of-Life](https://endoflife.date/python)**.
|
**Gitlint no longer supports Python 2.7 and Python 3.5 as they [have reached End-Of-Life](https://endoflife.date/python). The last gitlint version to support Python 2.7 and Python 3.5 is `0.14.0` (released on October 24th, 2020).**
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- **Commit message hook**: [Auto-trigger validations against new commit message right when you're committing](#using-gitlint-as-a-commit-msg-hook). Also [works with pre-commit](#using-gitlint-through-pre-commit).
|
- **Commit message hook**: [Auto-trigger validations against new commit message right when you're committing](#using-gitlint-as-a-commit-msg-hook). Also [works with pre-commit](#using-gitlint-through-pre-commit).
|
||||||
|
@ -39,8 +39,8 @@ useful throughout the years.
|
||||||
pip install gitlint
|
pip install gitlint
|
||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
brew tap rockyluke/devops
|
|
||||||
brew install gitlint
|
brew install gitlint
|
||||||
|
sudo port install gitlint # alternative using macports
|
||||||
|
|
||||||
# Ubuntu
|
# Ubuntu
|
||||||
apt-get install gitlint
|
apt-get install gitlint
|
||||||
|
@ -219,11 +219,14 @@ your `.pre-commit-config.yaml` file like so:
|
||||||
rev: # Fill in a tag / sha here
|
rev: # Fill in a tag / sha here
|
||||||
hooks:
|
hooks:
|
||||||
- id: gitlint
|
- id: gitlint
|
||||||
stages: [commit-msg]
|
|
||||||
entry: gitlint
|
|
||||||
args: [--contrib=CT1, --msg-filename]
|
args: [--contrib=CT1, --msg-filename]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! important
|
||||||
|
|
||||||
|
You need to add `--msg-filename` at the end of your custom `args` list as the gitlint-hook will fail otherwise.
|
||||||
|
|
||||||
|
|
||||||
## Using gitlint in a CI environment
|
## Using gitlint in a CI environment
|
||||||
By default, when just running `gitlint` without additional parameters, gitlint lints the last commit in the current
|
By default, when just running `gitlint` without additional parameters, gitlint lints the last commit in the current
|
||||||
working directory.
|
working directory.
|
||||||
|
@ -426,4 +429,4 @@ Exit Code | Description
|
||||||
-----------|------------------------------------------------------------
|
-----------|------------------------------------------------------------
|
||||||
253 | Wrong invocation of the `gitlint` command.
|
253 | Wrong invocation of the `gitlint` command.
|
||||||
254 | Something went wrong when invoking git.
|
254 | Something went wrong when invoking git.
|
||||||
255 | Invalid gitlint configuration
|
255 | Invalid gitlint configuration
|
||||||
|
|
|
@ -255,8 +255,8 @@ files | >= 0.4 | (empty) | Comma-separated list o
|
||||||
#### .gitlint
|
#### .gitlint
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# Prevent that certain sensitive files are committed by mistake by forcing users to mention them explicitly if they're
|
# Prevent that certain sensitive files are committed by mistake by forcing
|
||||||
# deliberately changing them
|
# users to mention them explicitly if they're deliberately changing them
|
||||||
[body-changed-file-mention]
|
[body-changed-file-mention]
|
||||||
files=generated.xml,secrets.txt,private-key.pem
|
files=generated.xml,secrets.txt,private-key.pem
|
||||||
```
|
```
|
||||||
|
|
|
@ -152,7 +152,7 @@ class SpecialChars(LineRule):
|
||||||
# options can be accessed by looking them up by their name in self.options
|
# options can be accessed by looking them up by their name in self.options
|
||||||
for char in self.options['special-chars'].value:
|
for char in self.options['special-chars'].value:
|
||||||
if char in line:
|
if char in line:
|
||||||
msg = "Title contains the special character '{0}'".format(char)
|
msg = f"Title contains the special character '{char}'"
|
||||||
violation = RuleViolation(self.id, msg, line)
|
violation = RuleViolation(self.id, msg, line)
|
||||||
violations.append(violation)
|
violations.append(violation)
|
||||||
|
|
||||||
|
@ -262,8 +262,7 @@ class BodyMaxLineCount(CommitRule):
|
||||||
line_count = len(commit.message.body)
|
line_count = len(commit.message.body)
|
||||||
max_line_count = self.options['max-line-count'].value
|
max_line_count = self.options['max-line-count'].value
|
||||||
if line_count > max_line_count:
|
if line_count > max_line_count:
|
||||||
message = "Body contains too many lines ({0} > {1})".format(line_count,
|
message = f"Body contains too many lines ({line_count} > {max_line_count})"
|
||||||
max_line_count)
|
|
||||||
return [RuleViolation(self.id, message, line_nr=1)]
|
return [RuleViolation(self.id, message, line_nr=1)]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -371,7 +370,7 @@ class ReleaseConfigurationRule(ConfigurationRule):
|
||||||
|
|
||||||
# You can add any extra properties you want to the commit object,
|
# You can add any extra properties you want to the commit object,
|
||||||
# these will be available later on in all rules.
|
# these will be available later on in all rules.
|
||||||
commit.my_property = u"This is my property"
|
commit.my_property = "This is my property"
|
||||||
```
|
```
|
||||||
|
|
||||||
For all available properties and methods on the `config` object, have a look at the
|
For all available properties and methods on the `config` object, have a look at the
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
from gitlint.rules import CommitRule, RuleViolation
|
from gitlint.rules import CommitRule, RuleViolation
|
||||||
from gitlint.options import IntOption, ListOption
|
from gitlint.options import IntOption, ListOption
|
||||||
from gitlint import utils
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Full details on user-defined rules: https://jorisroovers.com/gitlint/user_defined_rules
|
Full details on user-defined rules: https://jorisroovers.com/gitlint/user_defined_rules
|
||||||
|
@ -37,7 +35,7 @@ class BodyMaxLineCount(CommitRule):
|
||||||
line_count = len(commit.message.body)
|
line_count = len(commit.message.body)
|
||||||
max_line_count = self.options['max-line-count'].value
|
max_line_count = self.options['max-line-count'].value
|
||||||
if line_count > max_line_count:
|
if line_count > max_line_count:
|
||||||
message = "Body contains too many lines ({0} > {1})".format(line_count, max_line_count)
|
message = f"Body contains too many lines ({line_count} > {max_line_count})"
|
||||||
return [RuleViolation(self.id, message, line_nr=1)]
|
return [RuleViolation(self.id, message, line_nr=1)]
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,8 +88,7 @@ class BranchNamingConventions(CommitRule):
|
||||||
break
|
break
|
||||||
|
|
||||||
if not valid_branch_name:
|
if not valid_branch_name:
|
||||||
msg = "Branch name '{0}' does not start with one of {1}".format(branch,
|
msg = f"Branch name '{branch}' does not start with one of {allowed_branch_prefixes}"
|
||||||
utils.sstr(allowed_branch_prefixes))
|
|
||||||
violations.append(RuleViolation(self.id, msg, line_nr=1))
|
violations.append(RuleViolation(self.id, msg, line_nr=1))
|
||||||
|
|
||||||
return violations
|
return violations
|
||||||
|
|
|
@ -69,4 +69,4 @@ class ReleaseConfigurationRule(ConfigurationRule):
|
||||||
|
|
||||||
# You can add any extra properties you want to the commit object, these will be available later on
|
# You can add any extra properties you want to the commit object, these will be available later on
|
||||||
# in all rules.
|
# in all rules.
|
||||||
commit.my_property = u"This is my property"
|
commit.my_property = "This is my property"
|
||||||
|
|
|
@ -45,7 +45,7 @@ class SpecialChars(LineRule):
|
||||||
# options can be accessed by looking them up by their name in self.options
|
# options can be accessed by looking them up by their name in self.options
|
||||||
for char in self.options['special-chars'].value:
|
for char in self.options['special-chars'].value:
|
||||||
if char in line:
|
if char in line:
|
||||||
msg = "Title contains the special character '{0}'".format(char)
|
msg = f"Title contains the special character '{char}'"
|
||||||
violation = RuleViolation(self.id, msg, line)
|
violation = RuleViolation(self.id, msg, line)
|
||||||
violations.append(violation)
|
violations.append(violation)
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
__version__ = "0.14.0"
|
__version__ = "0.15.0"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class PropertyCache(object):
|
class PropertyCache:
|
||||||
""" Mixin class providing a simple cache. """
|
""" Mixin class providing a simple cache. """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -13,7 +13,7 @@ class PropertyCache(object):
|
||||||
return self._cache[cache_key]
|
return self._cache[cache_key]
|
||||||
|
|
||||||
|
|
||||||
def cache(original_func=None, cachekey=None):
|
def cache(original_func=None, cachekey=None): # pylint: disable=unused-argument
|
||||||
""" Cache decorator. Caches function return values.
|
""" Cache decorator. Caches function return values.
|
||||||
Requires the parent class to extend and initialize PropertyCache.
|
Requires the parent class to extend and initialize PropertyCache.
|
||||||
Usage:
|
Usage:
|
||||||
|
@ -28,27 +28,23 @@ def cache(original_func=None, cachekey=None):
|
||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Decorators with optional arguments are a bit convoluted in python, especially if you want to support both
|
# Decorators with optional arguments are a bit convoluted in python, see some of the links below for details.
|
||||||
# Python 2 and 3. See some of the links below for details.
|
|
||||||
|
|
||||||
def cache_decorator(func):
|
def cache_decorator(func):
|
||||||
|
# Use 'nonlocal' keyword to access parent function variable:
|
||||||
# If no specific cache key is given, use the function name as cache key
|
# https://stackoverflow.com/a/14678445/381010
|
||||||
if not cache_decorator.cachekey:
|
nonlocal cachekey
|
||||||
cache_decorator.cachekey = func.__name__
|
if not cachekey:
|
||||||
|
cachekey = func.__name__
|
||||||
|
|
||||||
def wrapped(*args):
|
def wrapped(*args):
|
||||||
def cache_func_result():
|
def cache_func_result():
|
||||||
# Call decorated function and store its result in the cache
|
# Call decorated function and store its result in the cache
|
||||||
args[0]._cache[cache_decorator.cachekey] = func(*args)
|
args[0]._cache[cachekey] = func(*args)
|
||||||
return args[0]._try_cache(cache_decorator.cachekey, cache_func_result)
|
return args[0]._try_cache(cachekey, cache_func_result)
|
||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
# Passing parent function variables to child functions requires special voodoo in python2:
|
|
||||||
# https://stackoverflow.com/a/14678445/381010
|
|
||||||
cache_decorator.cachekey = cachekey # attribute on the function
|
|
||||||
|
|
||||||
# To support optional kwargs for decorators, we need to check if a function is passed as first argument or not.
|
# To support optional kwargs for decorators, we need to check if a function is passed as first argument or not.
|
||||||
# https://stackoverflow.com/a/24617244/381010
|
# https://stackoverflow.com/a/24617244/381010
|
||||||
if original_func:
|
if original_func:
|
||||||
|
|
100
gitlint/cli.py
100
gitlint/cli.py
|
@ -8,19 +8,20 @@ import stat
|
||||||
import sys
|
import sys
|
||||||
import click
|
import click
|
||||||
|
|
||||||
# Error codes
|
|
||||||
MAX_VIOLATION_ERROR_CODE = 252 # noqa
|
|
||||||
USAGE_ERROR_CODE = 253 # noqa
|
|
||||||
GIT_CONTEXT_ERROR_CODE = 254 # noqa
|
|
||||||
CONFIG_ERROR_CODE = 255 # noqa
|
|
||||||
|
|
||||||
import gitlint
|
import gitlint
|
||||||
from gitlint.lint import GitLinter
|
from gitlint.lint import GitLinter
|
||||||
from gitlint.config import LintConfigBuilder, LintConfigError, LintConfigGenerator
|
from gitlint.config import LintConfigBuilder, LintConfigError, LintConfigGenerator
|
||||||
from gitlint.git import GitContext, GitContextError, git_version
|
from gitlint.git import GitContext, GitContextError, git_version
|
||||||
from gitlint import hooks
|
from gitlint import hooks
|
||||||
from gitlint.shell import shell
|
from gitlint.shell import shell
|
||||||
from gitlint.utils import ustr, LOG_FORMAT, IS_PY2
|
from gitlint.utils import LOG_FORMAT
|
||||||
|
from gitlint.exception import GitlintError
|
||||||
|
|
||||||
|
# Error codes
|
||||||
|
MAX_VIOLATION_ERROR_CODE = 252
|
||||||
|
USAGE_ERROR_CODE = 253
|
||||||
|
GIT_CONTEXT_ERROR_CODE = 254
|
||||||
|
CONFIG_ERROR_CODE = 255
|
||||||
|
|
||||||
DEFAULT_CONFIG_FILE = ".gitlint"
|
DEFAULT_CONFIG_FILE = ".gitlint"
|
||||||
# -n: disable swap files. This fixes a vim error on windows (E303: Unable to open swap file for <path>)
|
# -n: disable swap files. This fixes a vim error on windows (E303: Unable to open swap file for <path>)
|
||||||
|
@ -34,7 +35,7 @@ click.UsageError.exit_code = USAGE_ERROR_CODE
|
||||||
LOG = logging.getLogger("gitlint.cli")
|
LOG = logging.getLogger("gitlint.cli")
|
||||||
|
|
||||||
|
|
||||||
class GitLintUsageError(Exception):
|
class GitLintUsageError(GitlintError):
|
||||||
""" Exception indicating there is an issue with how gitlint is used. """
|
""" Exception indicating there is an issue with how gitlint is used. """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -134,7 +135,7 @@ def get_stdin_data():
|
||||||
# Only return the input data if there's actually something passed
|
# Only return the input data if there's actually something passed
|
||||||
# i.e. don't consider empty piped data
|
# i.e. don't consider empty piped data
|
||||||
if input_data:
|
if input_data:
|
||||||
return ustr(input_data)
|
return str(input_data)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@ -151,7 +152,7 @@ def build_git_context(lint_config, msg_filename, refspec):
|
||||||
# 1. Any data specified via --msg-filename
|
# 1. Any data specified via --msg-filename
|
||||||
if msg_filename:
|
if msg_filename:
|
||||||
LOG.debug("Using --msg-filename.")
|
LOG.debug("Using --msg-filename.")
|
||||||
return from_commit_msg(ustr(msg_filename.read()))
|
return from_commit_msg(str(msg_filename.read()))
|
||||||
|
|
||||||
# 2. Any data sent to stdin (unless stdin is being ignored)
|
# 2. Any data sent to stdin (unless stdin is being ignored)
|
||||||
if not lint_config.ignore_stdin:
|
if not lint_config.ignore_stdin:
|
||||||
|
@ -162,15 +163,28 @@ def build_git_context(lint_config, msg_filename, refspec):
|
||||||
return from_commit_msg(stdin_input)
|
return from_commit_msg(stdin_input)
|
||||||
|
|
||||||
if lint_config.staged:
|
if lint_config.staged:
|
||||||
raise GitLintUsageError(u"The 'staged' option (--staged) can only be used when using '--msg-filename' or "
|
raise GitLintUsageError("The 'staged' option (--staged) can only be used when using '--msg-filename' or "
|
||||||
u"when piping data to gitlint via stdin.")
|
"when piping data to gitlint via stdin.")
|
||||||
|
|
||||||
# 3. Fallback to reading from local repository
|
# 3. Fallback to reading from local repository
|
||||||
LOG.debug("No --msg-filename flag, no or empty data passed to stdin. Using the local repo.")
|
LOG.debug("No --msg-filename flag, no or empty data passed to stdin. Using the local repo.")
|
||||||
return GitContext.from_local_repository(lint_config.target, refspec)
|
return GitContext.from_local_repository(lint_config.target, refspec)
|
||||||
|
|
||||||
|
|
||||||
class ContextObj(object):
|
def handle_gitlint_error(ctx, exc):
|
||||||
|
""" Helper function to handle exceptions """
|
||||||
|
if isinstance(exc, GitContextError):
|
||||||
|
click.echo(exc)
|
||||||
|
ctx.exit(GIT_CONTEXT_ERROR_CODE)
|
||||||
|
elif isinstance(exc, GitLintUsageError):
|
||||||
|
click.echo(f"Error: {exc}")
|
||||||
|
ctx.exit(USAGE_ERROR_CODE)
|
||||||
|
elif isinstance(exc, LintConfigError):
|
||||||
|
click.echo(f"Config Error: {exc}")
|
||||||
|
ctx.exit(CONFIG_ERROR_CODE)
|
||||||
|
|
||||||
|
|
||||||
|
class ContextObj:
|
||||||
""" Simple class to hold data that is passed between Click commands via the Click context. """
|
""" Simple class to hold data that is passed between Click commands via the Click context. """
|
||||||
|
|
||||||
def __init__(self, config, config_builder, refspec, msg_filename, gitcontext=None):
|
def __init__(self, config, config_builder, refspec, msg_filename, gitcontext=None):
|
||||||
|
@ -187,7 +201,7 @@ class ContextObj(object):
|
||||||
type=click.Path(exists=True, resolve_path=True, file_okay=False, readable=True),
|
type=click.Path(exists=True, resolve_path=True, file_okay=False, readable=True),
|
||||||
help="Path of the target git repository. [default: current working directory]")
|
help="Path of the target git repository. [default: current working directory]")
|
||||||
@click.option('-C', '--config', type=click.Path(exists=True, dir_okay=False, readable=True, resolve_path=True),
|
@click.option('-C', '--config', type=click.Path(exists=True, dir_okay=False, readable=True, resolve_path=True),
|
||||||
help="Config file location [default: {0}]".format(DEFAULT_CONFIG_FILE))
|
help=f"Config file location [default: {DEFAULT_CONFIG_FILE}]")
|
||||||
@click.option('-c', multiple=True,
|
@click.option('-c', multiple=True,
|
||||||
help="Config flags in format <rule>.<option>=<value> (e.g.: -c T1.line-length=80). " +
|
help="Config flags in format <rule>.<option>=<value> (e.g.: -c T1.line-length=80). " +
|
||||||
"Flag can be used multiple times to set multiple config values.") # pylint: disable=bad-continuation
|
"Flag can be used multiple times to set multiple config values.") # pylint: disable=bad-continuation
|
||||||
|
@ -230,7 +244,7 @@ def cli( # pylint: disable=too-many-arguments
|
||||||
# store it in the context (click allows storing an arbitrary object in ctx.obj).
|
# store it in the context (click allows storing an arbitrary object in ctx.obj).
|
||||||
config, config_builder = build_config(target, config, c, extra_path, ignore, contrib,
|
config, config_builder = build_config(target, config, c, extra_path, ignore, contrib,
|
||||||
ignore_stdin, staged, verbose, silent, debug)
|
ignore_stdin, staged, verbose, silent, debug)
|
||||||
LOG.debug(u"Configuration\n%s", ustr(config))
|
LOG.debug("Configuration\n%s", config)
|
||||||
|
|
||||||
ctx.obj = ContextObj(config, config_builder, commits, msg_filename)
|
ctx.obj = ContextObj(config, config_builder, commits, msg_filename)
|
||||||
|
|
||||||
|
@ -238,15 +252,8 @@ def cli( # pylint: disable=too-many-arguments
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
ctx.invoke(lint)
|
ctx.invoke(lint)
|
||||||
|
|
||||||
except GitContextError as e:
|
except GitlintError as e:
|
||||||
click.echo(ustr(e))
|
handle_gitlint_error(ctx, e)
|
||||||
ctx.exit(GIT_CONTEXT_ERROR_CODE)
|
|
||||||
except GitLintUsageError as e:
|
|
||||||
click.echo(u"Error: {0}".format(ustr(e)))
|
|
||||||
ctx.exit(USAGE_ERROR_CODE)
|
|
||||||
except LintConfigError as e:
|
|
||||||
click.echo(u"Config Error: {0}".format(ustr(e)))
|
|
||||||
ctx.exit(CONFIG_ERROR_CODE)
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command("lint")
|
@cli.command("lint")
|
||||||
|
@ -294,7 +301,7 @@ def lint(ctx):
|
||||||
if violations:
|
if violations:
|
||||||
# Display the commit hash & new lines intelligently
|
# Display the commit hash & new lines intelligently
|
||||||
if number_of_commits > 1 and commit.sha:
|
if number_of_commits > 1 and commit.sha:
|
||||||
linter.display.e(u"{0}Commit {1}:".format(
|
linter.display.e("{0}Commit {1}:".format(
|
||||||
"\n" if not first_violation or commit is last_commit else "",
|
"\n" if not first_violation or commit is last_commit else "",
|
||||||
commit.sha[:10]
|
commit.sha[:10]
|
||||||
))
|
))
|
||||||
|
@ -315,10 +322,10 @@ def install_hook(ctx):
|
||||||
try:
|
try:
|
||||||
hooks.GitHookInstaller.install_commit_msg_hook(ctx.obj.config)
|
hooks.GitHookInstaller.install_commit_msg_hook(ctx.obj.config)
|
||||||
hook_path = hooks.GitHookInstaller.commit_msg_hook_path(ctx.obj.config)
|
hook_path = hooks.GitHookInstaller.commit_msg_hook_path(ctx.obj.config)
|
||||||
click.echo(u"Successfully installed gitlint commit-msg hook in {0}".format(hook_path))
|
click.echo(f"Successfully installed gitlint commit-msg hook in {hook_path}")
|
||||||
ctx.exit(0)
|
ctx.exit(0)
|
||||||
except hooks.GitHookInstallerError as e:
|
except hooks.GitHookInstallerError as e:
|
||||||
click.echo(ustr(e), err=True)
|
click.echo(e, err=True)
|
||||||
ctx.exit(GIT_CONTEXT_ERROR_CODE)
|
ctx.exit(GIT_CONTEXT_ERROR_CODE)
|
||||||
|
|
||||||
|
|
||||||
|
@ -329,10 +336,10 @@ def uninstall_hook(ctx):
|
||||||
try:
|
try:
|
||||||
hooks.GitHookInstaller.uninstall_commit_msg_hook(ctx.obj.config)
|
hooks.GitHookInstaller.uninstall_commit_msg_hook(ctx.obj.config)
|
||||||
hook_path = hooks.GitHookInstaller.commit_msg_hook_path(ctx.obj.config)
|
hook_path = hooks.GitHookInstaller.commit_msg_hook_path(ctx.obj.config)
|
||||||
click.echo(u"Successfully uninstalled gitlint commit-msg hook from {0}".format(hook_path))
|
click.echo(f"Successfully uninstalled gitlint commit-msg hook from {hook_path}")
|
||||||
ctx.exit(0)
|
ctx.exit(0)
|
||||||
except hooks.GitHookInstallerError as e:
|
except hooks.GitHookInstallerError as e:
|
||||||
click.echo(ustr(e), err=True)
|
click.echo(e, err=True)
|
||||||
ctx.exit(GIT_CONTEXT_ERROR_CODE)
|
ctx.exit(GIT_CONTEXT_ERROR_CODE)
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,8 +351,10 @@ def run_hook(ctx):
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
while exit_code > 0:
|
while exit_code > 0:
|
||||||
try:
|
try:
|
||||||
click.echo(u"gitlint: checking commit message...")
|
click.echo("gitlint: checking commit message...")
|
||||||
ctx.invoke(lint)
|
ctx.invoke(lint)
|
||||||
|
except GitlintError as e:
|
||||||
|
handle_gitlint_error(ctx, e)
|
||||||
except click.exceptions.Exit as e:
|
except click.exceptions.Exit as e:
|
||||||
# Flush stderr andstdout, this resolves an issue with output ordering in Cygwin
|
# Flush stderr andstdout, this resolves an issue with output ordering in Cygwin
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
@ -353,11 +362,11 @@ def run_hook(ctx):
|
||||||
|
|
||||||
exit_code = e.exit_code
|
exit_code = e.exit_code
|
||||||
if exit_code == 0:
|
if exit_code == 0:
|
||||||
click.echo(u"gitlint: " + click.style("OK", fg='green') + u" (no violations in commit message)")
|
click.echo("gitlint: " + click.style("OK", fg='green') + " (no violations in commit message)")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
click.echo(u"-----------------------------------------------")
|
click.echo("-----------------------------------------------")
|
||||||
click.echo(u"gitlint: " + click.style("Your commit message contains the above violations.", fg='red'))
|
click.echo("gitlint: " + click.style("Your commit message contains the above violations.", fg='red'))
|
||||||
|
|
||||||
value = None
|
value = None
|
||||||
while value not in ["y", "n", "e"]:
|
while value not in ["y", "n", "e"]:
|
||||||
|
@ -374,14 +383,7 @@ def run_hook(ctx):
|
||||||
# - https://github.com/pallets/click/pull/1372
|
# - https://github.com/pallets/click/pull/1372
|
||||||
# - From https://click.palletsprojects.com/en/7.x/utils/#getting-characters-from-terminal
|
# - From https://click.palletsprojects.com/en/7.x/utils/#getting-characters-from-terminal
|
||||||
# Note that this function will always read from the terminal, even if stdin is instead a pipe.
|
# Note that this function will always read from the terminal, even if stdin is instead a pipe.
|
||||||
#
|
value = input()
|
||||||
# We also need a to use raw_input() in Python2 as input() is unsafe (and raw_input() doesn't exist in
|
|
||||||
# Python3). See https://stackoverflow.com/a/4960216/381010
|
|
||||||
input_func = input
|
|
||||||
if IS_PY2:
|
|
||||||
input_func = raw_input # noqa pylint: disable=undefined-variable
|
|
||||||
|
|
||||||
value = input_func()
|
|
||||||
|
|
||||||
if value == "y":
|
if value == "y":
|
||||||
LOG.debug("run-hook: commit message accepted")
|
LOG.debug("run-hook: commit message accepted")
|
||||||
|
@ -396,15 +398,15 @@ def run_hook(ctx):
|
||||||
LOG.debug("run-hook: %s %s", editor, msg_filename_path)
|
LOG.debug("run-hook: %s %s", editor, msg_filename_path)
|
||||||
shell(editor + " " + msg_filename_path)
|
shell(editor + " " + msg_filename_path)
|
||||||
else:
|
else:
|
||||||
click.echo(u"Editing only possible when --msg-filename is specified.")
|
click.echo("Editing only possible when --msg-filename is specified.")
|
||||||
ctx.exit(exit_code)
|
ctx.exit(exit_code)
|
||||||
elif value == "n":
|
elif value == "n":
|
||||||
LOG.debug("run-hook: commit message declined")
|
LOG.debug("run-hook: commit message declined")
|
||||||
click.echo(u"Commit aborted.")
|
click.echo("Commit aborted.")
|
||||||
click.echo(u"Your commit message: ")
|
click.echo("Your commit message: ")
|
||||||
click.echo(u"-----------------------------------------------")
|
click.echo("-----------------------------------------------")
|
||||||
click.echo(ctx.obj.gitcontext.commits[0].message.full)
|
click.echo(ctx.obj.gitcontext.commits[0].message.full)
|
||||||
click.echo(u"-----------------------------------------------")
|
click.echo("-----------------------------------------------")
|
||||||
ctx.exit(exit_code)
|
ctx.exit(exit_code)
|
||||||
|
|
||||||
ctx.exit(exit_code)
|
ctx.exit(exit_code)
|
||||||
|
@ -418,14 +420,14 @@ def generate_config(ctx):
|
||||||
path = os.path.realpath(path)
|
path = os.path.realpath(path)
|
||||||
dir_name = os.path.dirname(path)
|
dir_name = os.path.dirname(path)
|
||||||
if not os.path.exists(dir_name):
|
if not os.path.exists(dir_name):
|
||||||
click.echo(u"Error: Directory '{0}' does not exist.".format(dir_name), err=True)
|
click.echo(f"Error: Directory '{dir_name}' does not exist.", err=True)
|
||||||
ctx.exit(USAGE_ERROR_CODE)
|
ctx.exit(USAGE_ERROR_CODE)
|
||||||
elif os.path.exists(path):
|
elif os.path.exists(path):
|
||||||
click.echo(u"Error: File \"{0}\" already exists.".format(path), err=True)
|
click.echo(f"Error: File \"{path}\" already exists.", err=True)
|
||||||
ctx.exit(USAGE_ERROR_CODE)
|
ctx.exit(USAGE_ERROR_CODE)
|
||||||
|
|
||||||
LintConfigGenerator.generate_config(path)
|
LintConfigGenerator.generate_config(path)
|
||||||
click.echo(u"Successfully generated {0}".format(path))
|
click.echo(f"Successfully generated {path}")
|
||||||
ctx.exit(0)
|
ctx.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
try:
|
from configparser import ConfigParser, Error as ConfigParserError
|
||||||
# python 2.x
|
|
||||||
from ConfigParser import ConfigParser, Error as ConfigParserError
|
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
# python 3.x
|
|
||||||
from configparser import ConfigParser, Error as ConfigParserError # pragma: no cover, pylint: disable=import-error
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import io
|
import io
|
||||||
|
@ -12,11 +7,12 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from gitlint.utils import ustr, sstr, DEFAULT_ENCODING
|
from gitlint.utils import DEFAULT_ENCODING
|
||||||
from gitlint import rules # For some weird reason pylint complains about this, pylint: disable=unused-import
|
from gitlint import rules # For some weird reason pylint complains about this, pylint: disable=unused-import
|
||||||
from gitlint import options
|
from gitlint import options
|
||||||
from gitlint import rule_finder
|
from gitlint import rule_finder
|
||||||
from gitlint.contrib import rules as contrib_rules
|
from gitlint.contrib import rules as contrib_rules
|
||||||
|
from gitlint.exception import GitlintError
|
||||||
|
|
||||||
|
|
||||||
def handle_option_error(func):
|
def handle_option_error(func):
|
||||||
|
@ -27,16 +23,16 @@ def handle_option_error(func):
|
||||||
try:
|
try:
|
||||||
return func(*args)
|
return func(*args)
|
||||||
except options.RuleOptionError as e:
|
except options.RuleOptionError as e:
|
||||||
raise LintConfigError(ustr(e))
|
raise LintConfigError(str(e)) from e
|
||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
class LintConfigError(Exception):
|
class LintConfigError(GitlintError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LintConfig(object):
|
class LintConfig:
|
||||||
""" Class representing gitlint configuration.
|
""" Class representing gitlint configuration.
|
||||||
Contains active config as well as number of methods to easily get/set the config.
|
Contains active config as well as number of methods to easily get/set the config.
|
||||||
"""
|
"""
|
||||||
|
@ -198,7 +194,7 @@ class LintConfig(object):
|
||||||
self.rules.add_rules(rule_classes, {'is_user_defined': True})
|
self.rules.add_rules(rule_classes, {'is_user_defined': True})
|
||||||
|
|
||||||
except (options.RuleOptionError, rules.UserRuleError) as e:
|
except (options.RuleOptionError, rules.UserRuleError) as e:
|
||||||
raise LintConfigError(ustr(e))
|
raise LintConfigError(str(e)) from e
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def contrib(self):
|
def contrib(self):
|
||||||
|
@ -219,27 +215,25 @@ class LintConfig(object):
|
||||||
# For each specified contrib rule, check whether it exists among the contrib classes
|
# For each specified contrib rule, check whether it exists among the contrib classes
|
||||||
for rule_id_or_name in self.contrib:
|
for rule_id_or_name in self.contrib:
|
||||||
rule_class = next((rc for rc in rule_classes if
|
rule_class = next((rc for rc in rule_classes if
|
||||||
rc.id == ustr(rule_id_or_name) or rc.name == ustr(rule_id_or_name)), False)
|
rule_id_or_name in (rc.id, rc.name)), False)
|
||||||
|
|
||||||
# If contrib rule exists, instantiate it and add it to the rules list
|
# If contrib rule exists, instantiate it and add it to the rules list
|
||||||
if rule_class:
|
if rule_class:
|
||||||
self.rules.add_rule(rule_class, rule_class.id, {'is_contrib': True})
|
self.rules.add_rule(rule_class, rule_class.id, {'is_contrib': True})
|
||||||
else:
|
else:
|
||||||
raise LintConfigError(u"No contrib rule with id or name '{0}' found.".format(ustr(rule_id_or_name)))
|
raise LintConfigError(f"No contrib rule with id or name '{rule_id_or_name}' found.")
|
||||||
|
|
||||||
except (options.RuleOptionError, rules.UserRuleError) as e:
|
except (options.RuleOptionError, rules.UserRuleError) as e:
|
||||||
raise LintConfigError(ustr(e))
|
raise LintConfigError(str(e)) from e
|
||||||
|
|
||||||
def _get_option(self, rule_name_or_id, option_name):
|
def _get_option(self, rule_name_or_id, option_name):
|
||||||
rule_name_or_id = ustr(rule_name_or_id) # convert to unicode first
|
|
||||||
option_name = ustr(option_name)
|
|
||||||
rule = self.rules.find_rule(rule_name_or_id)
|
rule = self.rules.find_rule(rule_name_or_id)
|
||||||
if not rule:
|
if not rule:
|
||||||
raise LintConfigError(u"No such rule '{0}'".format(rule_name_or_id))
|
raise LintConfigError(f"No such rule '{rule_name_or_id}'")
|
||||||
|
|
||||||
option = rule.options.get(option_name)
|
option = rule.options.get(option_name)
|
||||||
if not option:
|
if not option:
|
||||||
raise LintConfigError(u"Rule '{0}' has no option '{1}'".format(rule_name_or_id, option_name))
|
raise LintConfigError(f"Rule '{rule_name_or_id}' has no option '{option_name}'")
|
||||||
|
|
||||||
return option
|
return option
|
||||||
|
|
||||||
|
@ -256,14 +250,14 @@ class LintConfig(object):
|
||||||
try:
|
try:
|
||||||
option.set(option_value)
|
option.set(option_value)
|
||||||
except options.RuleOptionError as e:
|
except options.RuleOptionError as e:
|
||||||
msg = u"'{0}' is not a valid value for option '{1}.{2}'. {3}."
|
msg = f"'{option_value}' is not a valid value for option '{rule_name_or_id}.{option_name}'. {e}."
|
||||||
raise LintConfigError(msg.format(option_value, rule_name_or_id, option_name, ustr(e)))
|
raise LintConfigError(msg) from e
|
||||||
|
|
||||||
def set_general_option(self, option_name, option_value):
|
def set_general_option(self, option_name, option_value):
|
||||||
attr_name = option_name.replace("-", "_")
|
attr_name = option_name.replace("-", "_")
|
||||||
# only allow setting general options that exist and don't start with an underscore
|
# only allow setting general options that exist and don't start with an underscore
|
||||||
if not hasattr(self, attr_name) or attr_name[0] == "_":
|
if not hasattr(self, attr_name) or attr_name[0] == "_":
|
||||||
raise LintConfigError(u"'{0}' is not a valid gitlint option".format(option_name))
|
raise LintConfigError(f"'{option_name}' is not a valid gitlint option")
|
||||||
|
|
||||||
# else:
|
# else:
|
||||||
setattr(self, attr_name, option_value)
|
setattr(self, attr_name, option_value)
|
||||||
|
@ -285,30 +279,26 @@ class LintConfig(object):
|
||||||
self.ignore == other.ignore and \
|
self.ignore == other.ignore and \
|
||||||
self._config_path == other._config_path # noqa
|
self._config_path == other._config_path # noqa
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other) # required for py2
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# config-path is not a user exposed variable, so don't print it under the general section
|
# config-path is not a user exposed variable, so don't print it under the general section
|
||||||
return_str = u"config-path: {0}\n".format(self._config_path)
|
return (f"config-path: {self._config_path}\n"
|
||||||
return_str += u"[GENERAL]\n"
|
f"[GENERAL]\n"
|
||||||
return_str += u"extra-path: {0}\n".format(self.extra_path)
|
f"extra-path: {self.extra_path}\n"
|
||||||
return_str += u"contrib: {0}\n".format(sstr(self.contrib))
|
f"contrib: {self.contrib}\n"
|
||||||
return_str += u"ignore: {0}\n".format(",".join(self.ignore))
|
f"ignore: {','.join(self.ignore)}\n"
|
||||||
return_str += u"ignore-merge-commits: {0}\n".format(self.ignore_merge_commits)
|
f"ignore-merge-commits: {self.ignore_merge_commits}\n"
|
||||||
return_str += u"ignore-fixup-commits: {0}\n".format(self.ignore_fixup_commits)
|
f"ignore-fixup-commits: {self.ignore_fixup_commits}\n"
|
||||||
return_str += u"ignore-squash-commits: {0}\n".format(self.ignore_squash_commits)
|
f"ignore-squash-commits: {self.ignore_squash_commits}\n"
|
||||||
return_str += u"ignore-revert-commits: {0}\n".format(self.ignore_revert_commits)
|
f"ignore-revert-commits: {self.ignore_revert_commits}\n"
|
||||||
return_str += u"ignore-stdin: {0}\n".format(self.ignore_stdin)
|
f"ignore-stdin: {self.ignore_stdin}\n"
|
||||||
return_str += u"staged: {0}\n".format(self.staged)
|
f"staged: {self.staged}\n"
|
||||||
return_str += u"verbosity: {0}\n".format(self.verbosity)
|
f"verbosity: {self.verbosity}\n"
|
||||||
return_str += u"debug: {0}\n".format(self.debug)
|
f"debug: {self.debug}\n"
|
||||||
return_str += u"target: {0}\n".format(self.target)
|
f"target: {self.target}\n"
|
||||||
return_str += u"[RULES]\n{0}".format(self.rules)
|
f"[RULES]\n{self.rules}")
|
||||||
return return_str
|
|
||||||
|
|
||||||
|
|
||||||
class RuleCollection(object):
|
class RuleCollection:
|
||||||
""" Class representing an ordered list of rules. Methods are provided to easily retrieve, add or delete rules. """
|
""" Class representing an ordered list of rules. Methods are provided to easily retrieve, add or delete rules. """
|
||||||
|
|
||||||
def __init__(self, rule_classes=None, rule_attrs=None):
|
def __init__(self, rule_classes=None, rule_attrs=None):
|
||||||
|
@ -318,8 +308,6 @@ class RuleCollection(object):
|
||||||
self.add_rules(rule_classes, rule_attrs)
|
self.add_rules(rule_classes, rule_attrs)
|
||||||
|
|
||||||
def find_rule(self, rule_id_or_name):
|
def find_rule(self, rule_id_or_name):
|
||||||
# try finding rule by id
|
|
||||||
rule_id_or_name = ustr(rule_id_or_name) # convert to unicode first
|
|
||||||
rule = self._rules.get(rule_id_or_name)
|
rule = self._rules.get(rule_id_or_name)
|
||||||
# if not found, try finding rule by name
|
# if not found, try finding rule by name
|
||||||
if not rule:
|
if not rule:
|
||||||
|
@ -351,7 +339,7 @@ class RuleCollection(object):
|
||||||
""" Deletes all rules from the collection that match a given attribute name and value """
|
""" Deletes all rules from the collection that match a given attribute name and value """
|
||||||
# Create a new list based on _rules.values() because in python 3, values() is a ValuesView as opposed to a list
|
# Create a new list based on _rules.values() because in python 3, values() is a ValuesView as opposed to a list
|
||||||
# This means you can't modify the ValueView while iterating over it.
|
# This means you can't modify the ValueView while iterating over it.
|
||||||
for rule in [r for r in self._rules.values()]:
|
for rule in [r for r in self._rules.values()]: # pylint: disable=unnecessary-comprehension
|
||||||
if hasattr(rule, attr_name) and (getattr(rule, attr_name) == attr_val):
|
if hasattr(rule, attr_name) and (getattr(rule, attr_name) == attr_val):
|
||||||
del self._rules[rule.id]
|
del self._rules[rule.id]
|
||||||
|
|
||||||
|
@ -362,19 +350,13 @@ class RuleCollection(object):
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return isinstance(other, RuleCollection) and self._rules == other._rules
|
return isinstance(other, RuleCollection) and self._rules == other._rules
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other) # required for py2
|
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self._rules)
|
return len(self._rules)
|
||||||
|
|
||||||
def __repr__(self):
|
def __str__(self):
|
||||||
return self.__unicode__() # pragma: no cover
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return_str = ""
|
return_str = ""
|
||||||
for rule in self._rules.values():
|
for rule in self._rules.values():
|
||||||
return_str += u" {0}: {1}\n".format(rule.id, rule.name)
|
return_str += f" {rule.id}: {rule.name}\n"
|
||||||
for option_name, option_value in sorted(rule.options.items()):
|
for option_name, option_value in sorted(rule.options.items()):
|
||||||
if option_value.value is None:
|
if option_value.value is None:
|
||||||
option_val_repr = None
|
option_val_repr = None
|
||||||
|
@ -384,11 +366,11 @@ class RuleCollection(object):
|
||||||
option_val_repr = option_value.value.pattern
|
option_val_repr = option_value.value.pattern
|
||||||
else:
|
else:
|
||||||
option_val_repr = option_value.value
|
option_val_repr = option_value.value
|
||||||
return_str += u" {0}={1}\n".format(option_name, option_val_repr)
|
return_str += f" {option_name}={option_val_repr}\n"
|
||||||
return return_str
|
return return_str
|
||||||
|
|
||||||
|
|
||||||
class LintConfigBuilder(object):
|
class LintConfigBuilder:
|
||||||
""" Factory class that can build gitlint config.
|
""" Factory class that can build gitlint config.
|
||||||
This is primarily useful to deal with complex configuration scenarios where configuration can be set and overridden
|
This is primarily useful to deal with complex configuration scenarios where configuration can be set and overridden
|
||||||
from various sources (typically according to certain precedence rules) before the actual config should be
|
from various sources (typically according to certain precedence rules) before the actual config should be
|
||||||
|
@ -427,28 +409,27 @@ class LintConfigBuilder(object):
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
rule_name, option_name = config_name.split(".", 1)
|
rule_name, option_name = config_name.split(".", 1)
|
||||||
self.set_option(rule_name, option_name, option_value)
|
self.set_option(rule_name, option_name, option_value)
|
||||||
except ValueError: # raised if the config string is invalid
|
except ValueError as e: # raised if the config string is invalid
|
||||||
raise LintConfigError(
|
raise LintConfigError(
|
||||||
u"'{0}' is an invalid configuration option. Use '<rule>.<option>=<value>'".format(config_option))
|
f"'{config_option}' is an invalid configuration option. Use '<rule>.<option>=<value>'") from e
|
||||||
|
|
||||||
def set_from_config_file(self, filename):
|
def set_from_config_file(self, filename):
|
||||||
""" Loads lint config from a ini-style config file """
|
""" Loads lint config from a ini-style config file """
|
||||||
if not os.path.exists(filename):
|
if not os.path.exists(filename):
|
||||||
raise LintConfigError(u"Invalid file path: {0}".format(filename))
|
raise LintConfigError(f"Invalid file path: {filename}")
|
||||||
self._config_path = os.path.realpath(filename)
|
self._config_path = os.path.realpath(filename)
|
||||||
try:
|
try:
|
||||||
parser = ConfigParser()
|
parser = ConfigParser()
|
||||||
|
|
||||||
with io.open(filename, encoding=DEFAULT_ENCODING) as config_file:
|
with io.open(filename, encoding=DEFAULT_ENCODING) as config_file:
|
||||||
# readfp() is deprecated in python 3.2+, but compatible with 2.7
|
parser.read_file(config_file, filename)
|
||||||
parser.readfp(config_file, filename) # pylint: disable=deprecated-method
|
|
||||||
|
|
||||||
for section_name in parser.sections():
|
for section_name in parser.sections():
|
||||||
for option_name, option_value in parser.items(section_name):
|
for option_name, option_value in parser.items(section_name):
|
||||||
self.set_option(section_name, option_name, ustr(option_value))
|
self.set_option(section_name, option_name, str(option_value))
|
||||||
|
|
||||||
except ConfigParserError as e:
|
except ConfigParserError as e:
|
||||||
raise LintConfigError(ustr(e))
|
raise LintConfigError(str(e)) from e
|
||||||
|
|
||||||
def _add_named_rule(self, config, qualified_rule_name):
|
def _add_named_rule(self, config, qualified_rule_name):
|
||||||
""" Adds a Named Rule to a given LintConfig object.
|
""" Adds a Named Rule to a given LintConfig object.
|
||||||
|
@ -465,14 +446,14 @@ class LintConfigBuilder(object):
|
||||||
# - not empty
|
# - not empty
|
||||||
# - no whitespace or colons
|
# - no whitespace or colons
|
||||||
if rule_name == "" or bool(re.search("\\s|:", rule_name, re.UNICODE)):
|
if rule_name == "" or bool(re.search("\\s|:", rule_name, re.UNICODE)):
|
||||||
msg = u"The rule-name part in '{0}' cannot contain whitespace, colons or be empty"
|
msg = f"The rule-name part in '{qualified_rule_name}' cannot contain whitespace, colons or be empty"
|
||||||
raise LintConfigError(msg.format(qualified_rule_name))
|
raise LintConfigError(msg)
|
||||||
|
|
||||||
# find parent rule
|
# find parent rule
|
||||||
parent_rule = config.rules.find_rule(parent_rule_specifier)
|
parent_rule = config.rules.find_rule(parent_rule_specifier)
|
||||||
if not parent_rule:
|
if not parent_rule:
|
||||||
msg = u"No such rule '{0}' (named rule: '{1}')"
|
msg = f"No such rule '{parent_rule_specifier}' (named rule: '{qualified_rule_name}')"
|
||||||
raise LintConfigError(msg.format(parent_rule_specifier, qualified_rule_name))
|
raise LintConfigError(msg)
|
||||||
|
|
||||||
# Determine canonical id and name by recombining the parent id/name and instance name parts.
|
# Determine canonical id and name by recombining the parent id/name and instance name parts.
|
||||||
canonical_id = parent_rule.__class__.id + self.RULE_QUALIFIER_SYMBOL + rule_name
|
canonical_id = parent_rule.__class__.id + self.RULE_QUALIFIER_SYMBOL + rule_name
|
||||||
|
@ -525,7 +506,7 @@ class LintConfigBuilder(object):
|
||||||
GITLINT_CONFIG_TEMPLATE_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files/gitlint")
|
GITLINT_CONFIG_TEMPLATE_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files/gitlint")
|
||||||
|
|
||||||
|
|
||||||
class LintConfigGenerator(object):
|
class LintConfigGenerator:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_config(dest):
|
def generate_config(dest):
|
||||||
""" Generates a gitlint config file at the given destination location.
|
""" Generates a gitlint config file at the given destination location.
|
||||||
|
|
|
@ -2,7 +2,6 @@ import re
|
||||||
|
|
||||||
from gitlint.options import ListOption
|
from gitlint.options import ListOption
|
||||||
from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation
|
from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation
|
||||||
from gitlint.utils import ustr
|
|
||||||
|
|
||||||
RULE_REGEX = re.compile(r"[^(]+?(\([^)]+?\))?: .+")
|
RULE_REGEX = re.compile(r"[^(]+?(\([^)]+?\))?: .+")
|
||||||
|
|
||||||
|
@ -26,14 +25,14 @@ class ConventionalCommit(LineRule):
|
||||||
violations = []
|
violations = []
|
||||||
|
|
||||||
for commit_type in self.options["types"].value:
|
for commit_type in self.options["types"].value:
|
||||||
if line.startswith(ustr(commit_type)):
|
if line.startswith(commit_type):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
msg = u"Title does not start with one of {0}".format(', '.join(self.options['types'].value))
|
msg = "Title does not start with one of {0}".format(', '.join(self.options['types'].value))
|
||||||
violations.append(RuleViolation(self.id, msg, line))
|
violations.append(RuleViolation(self.id, msg, line))
|
||||||
|
|
||||||
if not RULE_REGEX.match(line):
|
if not RULE_REGEX.match(line):
|
||||||
msg = u"Title does not follow ConventionalCommits.org format 'type(optional-scope): description'"
|
msg = "Title does not follow ConventionalCommits.org format 'type(optional-scope): description'"
|
||||||
violations.append(RuleViolation(self.id, msg, line))
|
violations.append(RuleViolation(self.id, msg, line))
|
||||||
|
|
||||||
return violations
|
return violations
|
||||||
|
|
|
@ -1,18 +1,7 @@
|
||||||
import codecs
|
|
||||||
import locale
|
|
||||||
from sys import stdout, stderr
|
from sys import stdout, stderr
|
||||||
from gitlint.utils import IS_PY2
|
|
||||||
|
|
||||||
# For some reason, python 2.x sometimes messes up with printing unicode chars to stdout/stderr
|
|
||||||
# This is mostly when there is a mismatch between the terminal encoding and the python encoding.
|
|
||||||
# This use-case is primarily triggered when piping input between commands, in particular our integration tests
|
|
||||||
# tend to trip over this.
|
|
||||||
if IS_PY2:
|
|
||||||
stdout = codecs.getwriter(locale.getpreferredencoding())(stdout) # pylint: disable=invalid-name
|
|
||||||
stderr = codecs.getwriter(locale.getpreferredencoding())(stderr) # pylint: disable=invalid-name
|
|
||||||
|
|
||||||
|
|
||||||
class Display(object):
|
class Display:
|
||||||
""" Utility class to print stuff to an output stream (stdout by default) based on the config's verbosity """
|
""" Utility class to print stuff to an output stream (stdout by default) based on the config's verbosity """
|
||||||
|
|
||||||
def __init__(self, lint_config):
|
def __init__(self, lint_config):
|
||||||
|
|
4
gitlint/exception.py
Normal file
4
gitlint/exception.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
class GitlintError(Exception):
|
||||||
|
""" Based Exception class for all gitlint exceptions """
|
||||||
|
pass
|
118
gitlint/git.py
118
gitlint/git.py
|
@ -8,7 +8,7 @@ from gitlint import shell as sh
|
||||||
from gitlint.shell import CommandNotFound, ErrorReturnCode
|
from gitlint.shell import CommandNotFound, ErrorReturnCode
|
||||||
|
|
||||||
from gitlint.cache import PropertyCache, cache
|
from gitlint.cache import PropertyCache, cache
|
||||||
from gitlint.utils import ustr, sstr
|
from gitlint.exception import GitlintError
|
||||||
|
|
||||||
# For now, the git date format we use is fixed, but technically this format is determined by `git config log.date`
|
# For now, the git date format we use is fixed, but technically this format is determined by `git config log.date`
|
||||||
# We should fix this at some point :-)
|
# We should fix this at some point :-)
|
||||||
|
@ -17,24 +17,23 @@ GIT_TIMEFORMAT = "YYYY-MM-DD HH:mm:ss Z"
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GitContextError(Exception):
|
class GitContextError(GitlintError):
|
||||||
""" Exception indicating there is an issue with the git context """
|
""" Exception indicating there is an issue with the git context """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GitNotInstalledError(GitContextError):
|
class GitNotInstalledError(GitContextError):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(GitNotInstalledError, self).__init__(
|
super().__init__(
|
||||||
u"'git' command not found. You need to install git to use gitlint on a local repository. " +
|
"'git' command not found. You need to install git to use gitlint on a local repository. " +
|
||||||
u"See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git.")
|
"See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git.")
|
||||||
|
|
||||||
|
|
||||||
class GitExitCodeError(GitContextError):
|
class GitExitCodeError(GitContextError):
|
||||||
def __init__(self, command, stderr):
|
def __init__(self, command, stderr):
|
||||||
self.command = command
|
self.command = command
|
||||||
self.stderr = stderr
|
self.stderr = stderr
|
||||||
super(GitExitCodeError, self).__init__(
|
super().__init__(f"An error occurred while executing '{command}': {stderr}")
|
||||||
u"An error occurred while executing '{0}': {1}".format(command, stderr))
|
|
||||||
|
|
||||||
|
|
||||||
def _git(*command_parts, **kwargs):
|
def _git(*command_parts, **kwargs):
|
||||||
|
@ -42,33 +41,33 @@ def _git(*command_parts, **kwargs):
|
||||||
git_kwargs = {'_tty_out': False}
|
git_kwargs = {'_tty_out': False}
|
||||||
git_kwargs.update(kwargs)
|
git_kwargs.update(kwargs)
|
||||||
try:
|
try:
|
||||||
LOG.debug(sstr(command_parts))
|
LOG.debug(command_parts)
|
||||||
result = sh.git(*command_parts, **git_kwargs) # pylint: disable=unexpected-keyword-arg
|
result = sh.git(*command_parts, **git_kwargs) # pylint: disable=unexpected-keyword-arg
|
||||||
# If we reach this point and the result has an exit_code that is larger than 0, this means that we didn't
|
# If we reach this point and the result has an exit_code that is larger than 0, this means that we didn't
|
||||||
# get an exception (which is the default sh behavior for non-zero exit codes) and so the user is expecting
|
# get an exception (which is the default sh behavior for non-zero exit codes) and so the user is expecting
|
||||||
# a non-zero exit code -> just return the entire result
|
# a non-zero exit code -> just return the entire result
|
||||||
if hasattr(result, 'exit_code') and result.exit_code > 0:
|
if hasattr(result, 'exit_code') and result.exit_code > 0:
|
||||||
return result
|
return result
|
||||||
return ustr(result)
|
return str(result)
|
||||||
except CommandNotFound:
|
except CommandNotFound as e:
|
||||||
raise GitNotInstalledError()
|
raise GitNotInstalledError from e
|
||||||
except ErrorReturnCode as e: # Something went wrong while executing the git command
|
except ErrorReturnCode as e: # Something went wrong while executing the git command
|
||||||
error_msg = e.stderr.strip()
|
error_msg = e.stderr.strip()
|
||||||
error_msg_lower = error_msg.lower()
|
error_msg_lower = error_msg.lower()
|
||||||
if '_cwd' in git_kwargs and b"not a git repository" in error_msg_lower:
|
if '_cwd' in git_kwargs and b"not a git repository" in error_msg_lower:
|
||||||
error_msg = u"{0} is not a git repository.".format(git_kwargs['_cwd'])
|
raise GitContextError(f"{git_kwargs['_cwd']} is not a git repository.") from e
|
||||||
raise GitContextError(error_msg)
|
|
||||||
|
|
||||||
if (b"does not have any commits yet" in error_msg_lower or
|
if (b"does not have any commits yet" in error_msg_lower or
|
||||||
b"ambiguous argument 'head': unknown revision" in error_msg_lower):
|
b"ambiguous argument 'head': unknown revision" in error_msg_lower):
|
||||||
raise GitContextError(u"Current branch has no commits. Gitlint requires at least one commit to function.")
|
msg = "Current branch has no commits. Gitlint requires at least one commit to function."
|
||||||
|
raise GitContextError(msg) from e
|
||||||
|
|
||||||
raise GitExitCodeError(e.full_cmd, error_msg)
|
raise GitExitCodeError(e.full_cmd, error_msg) from e
|
||||||
|
|
||||||
|
|
||||||
def git_version():
|
def git_version():
|
||||||
""" Determine the git version installed on this host by calling git --version"""
|
""" Determine the git version installed on this host by calling git --version"""
|
||||||
return _git("--version").replace(u"\n", u"")
|
return _git("--version").replace("\n", "")
|
||||||
|
|
||||||
|
|
||||||
def git_commentchar(repository_path=None):
|
def git_commentchar(repository_path=None):
|
||||||
|
@ -77,17 +76,17 @@ def git_commentchar(repository_path=None):
|
||||||
# git will return an exit code of 1 if it can't find a config value, in this case we fall-back to # as commentchar
|
# git will return an exit code of 1 if it can't find a config value, in this case we fall-back to # as commentchar
|
||||||
if hasattr(commentchar, 'exit_code') and commentchar.exit_code == 1: # pylint: disable=no-member
|
if hasattr(commentchar, 'exit_code') and commentchar.exit_code == 1: # pylint: disable=no-member
|
||||||
commentchar = "#"
|
commentchar = "#"
|
||||||
return ustr(commentchar).replace(u"\n", u"")
|
return commentchar.replace("\n", "")
|
||||||
|
|
||||||
|
|
||||||
def git_hooks_dir(repository_path):
|
def git_hooks_dir(repository_path):
|
||||||
""" Determine hooks directory for a given target dir """
|
""" Determine hooks directory for a given target dir """
|
||||||
hooks_dir = _git("rev-parse", "--git-path", "hooks", _cwd=repository_path)
|
hooks_dir = _git("rev-parse", "--git-path", "hooks", _cwd=repository_path)
|
||||||
hooks_dir = ustr(hooks_dir).replace(u"\n", u"")
|
hooks_dir = hooks_dir.replace("\n", "")
|
||||||
return os.path.realpath(os.path.join(repository_path, hooks_dir))
|
return os.path.realpath(os.path.join(repository_path, hooks_dir))
|
||||||
|
|
||||||
|
|
||||||
class GitCommitMessage(object):
|
class GitCommitMessage:
|
||||||
""" Class representing a git commit message. A commit message consists of the following:
|
""" Class representing a git commit message. A commit message consists of the following:
|
||||||
- context: The `GitContext` this commit message is part of
|
- context: The `GitContext` this commit message is part of
|
||||||
- original: The actual commit message as returned by `git log`
|
- original: The actual commit message as returned by `git log`
|
||||||
|
@ -106,35 +105,26 @@ class GitCommitMessage(object):
|
||||||
def from_full_message(context, commit_msg_str):
|
def from_full_message(context, commit_msg_str):
|
||||||
""" Parses a full git commit message by parsing a given string into the different parts of a commit message """
|
""" Parses a full git commit message by parsing a given string into the different parts of a commit message """
|
||||||
all_lines = commit_msg_str.splitlines()
|
all_lines = commit_msg_str.splitlines()
|
||||||
cutline = u"{0} ------------------------ >8 ------------------------".format(context.commentchar)
|
cutline = f"{context.commentchar} ------------------------ >8 ------------------------"
|
||||||
try:
|
try:
|
||||||
cutline_index = all_lines.index(cutline)
|
cutline_index = all_lines.index(cutline)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
cutline_index = None
|
cutline_index = None
|
||||||
lines = [ustr(line) for line in all_lines[:cutline_index] if not line.startswith(context.commentchar)]
|
lines = [line for line in all_lines[:cutline_index] if not line.startswith(context.commentchar)]
|
||||||
full = "\n".join(lines)
|
full = "\n".join(lines)
|
||||||
title = lines[0] if lines else ""
|
title = lines[0] if lines else ""
|
||||||
body = lines[1:] if len(lines) > 1 else []
|
body = lines[1:] if len(lines) > 1 else []
|
||||||
return GitCommitMessage(context=context, original=commit_msg_str, full=full, title=title, body=body)
|
return GitCommitMessage(context=context, original=commit_msg_str, full=full, title=title, body=body)
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.full # pragma: no cover
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return sstr(self.__unicode__()) # pragma: no cover
|
return self.full
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.__str__() # pragma: no cover
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return (isinstance(other, GitCommitMessage) and self.original == other.original
|
return (isinstance(other, GitCommitMessage) and self.original == other.original
|
||||||
and self.full == other.full and self.title == other.title and self.body == other.body) # noqa
|
and self.full == other.full and self.title == other.title and self.body == other.body) # noqa
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other) # required for py2
|
|
||||||
|
|
||||||
|
class GitCommit:
|
||||||
class GitCommit(object):
|
|
||||||
""" Class representing a git commit.
|
""" Class representing a git commit.
|
||||||
A commit consists of: context, message, author name, author email, date, list of parent commit shas,
|
A commit consists of: context, message, author name, author email, date, list of parent commit shas,
|
||||||
list of changed files, list of branch names.
|
list of changed files, list of branch names.
|
||||||
|
@ -155,39 +145,33 @@ class GitCommit(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_merge_commit(self):
|
def is_merge_commit(self):
|
||||||
return self.message.title.startswith(u"Merge")
|
return self.message.title.startswith("Merge")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_fixup_commit(self):
|
def is_fixup_commit(self):
|
||||||
return self.message.title.startswith(u"fixup!")
|
return self.message.title.startswith("fixup!")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_squash_commit(self):
|
def is_squash_commit(self):
|
||||||
return self.message.title.startswith(u"squash!")
|
return self.message.title.startswith("squash!")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_revert_commit(self):
|
def is_revert_commit(self):
|
||||||
return self.message.title.startswith(u"Revert")
|
return self.message.title.startswith("Revert")
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
format_str = (u"--- Commit Message ----\n%s\n"
|
|
||||||
u"--- Meta info ---------\n"
|
|
||||||
u"Author: %s <%s>\nDate: %s\n"
|
|
||||||
u"is-merge-commit: %s\nis-fixup-commit: %s\n"
|
|
||||||
u"is-squash-commit: %s\nis-revert-commit: %s\n"
|
|
||||||
u"Branches: %s\n"
|
|
||||||
u"Changed Files: %s\n"
|
|
||||||
u"-----------------------") # pragma: no cover
|
|
||||||
date_str = arrow.get(self.date).format(GIT_TIMEFORMAT) if self.date else None
|
|
||||||
return format_str % (ustr(self.message), self.author_name, self.author_email, date_str,
|
|
||||||
self.is_merge_commit, self.is_fixup_commit, self.is_squash_commit,
|
|
||||||
self.is_revert_commit, sstr(self.branches), sstr(self.changed_files)) # pragma: no cover
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return sstr(self.__unicode__()) # pragma: no cover
|
date_str = arrow.get(self.date).format(GIT_TIMEFORMAT) if self.date else None
|
||||||
|
return (f"--- Commit Message ----\n{self.message}\n"
|
||||||
def __repr__(self):
|
"--- Meta info ---------\n"
|
||||||
return self.__str__() # pragma: no cover
|
f"Author: {self.author_name} <{self.author_email}>\n"
|
||||||
|
f"Date: {date_str}\n"
|
||||||
|
f"is-merge-commit: {self.is_merge_commit}\n"
|
||||||
|
f"is-fixup-commit: {self.is_fixup_commit}\n"
|
||||||
|
f"is-squash-commit: {self.is_squash_commit}\n"
|
||||||
|
f"is-revert-commit: {self.is_revert_commit}\n"
|
||||||
|
f"Branches: {self.branches}\n"
|
||||||
|
f"Changed Files: {self.changed_files}\n"
|
||||||
|
"-----------------------")
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
# skip checking the context as context refers back to this obj, this will trigger a cyclic dependency
|
# skip checking the context as context refers back to this obj, this will trigger a cyclic dependency
|
||||||
|
@ -199,9 +183,6 @@ class GitCommit(object):
|
||||||
and self.is_squash_commit == other.is_squash_commit and self.is_revert_commit == other.is_revert_commit
|
and self.is_squash_commit == other.is_squash_commit and self.is_revert_commit == other.is_revert_commit
|
||||||
and self.changed_files == other.changed_files and self.branches == other.branches) # noqa
|
and self.changed_files == other.changed_files and self.branches == other.branches) # noqa
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other) # required for py2
|
|
||||||
|
|
||||||
|
|
||||||
class LocalGitCommit(GitCommit, PropertyCache):
|
class LocalGitCommit(GitCommit, PropertyCache):
|
||||||
""" Class representing a git commit that exists in the local git repository.
|
""" Class representing a git commit that exists in the local git repository.
|
||||||
|
@ -230,7 +211,7 @@ class LocalGitCommit(GitCommit, PropertyCache):
|
||||||
# "YYYY-MM-DD HH:mm:ss Z" -> ISO 8601-like format
|
# "YYYY-MM-DD HH:mm:ss Z" -> ISO 8601-like format
|
||||||
# Use arrow for datetime parsing, because apparently python is quirky around ISO-8601 dates:
|
# Use arrow for datetime parsing, because apparently python is quirky around ISO-8601 dates:
|
||||||
# http://stackoverflow.com/a/30696682/381010
|
# http://stackoverflow.com/a/30696682/381010
|
||||||
commit_date = arrow.get(ustr(date), GIT_TIMEFORMAT).datetime
|
commit_date = arrow.get(date, GIT_TIMEFORMAT).datetime
|
||||||
|
|
||||||
# Create Git commit object with the retrieved info
|
# Create Git commit object with the retrieved info
|
||||||
commit_msg_obj = GitCommitMessage.from_full_message(self.context, commit_msg)
|
commit_msg_obj = GitCommitMessage.from_full_message(self.context, commit_msg)
|
||||||
|
@ -270,7 +251,7 @@ class LocalGitCommit(GitCommit, PropertyCache):
|
||||||
# safely do this since git branches cannot contain '*' anywhere, so if we find an '*' we know it's output
|
# safely do this since git branches cannot contain '*' anywhere, so if we find an '*' we know it's output
|
||||||
# from the git CLI and not part of the branch name. See https://git-scm.com/docs/git-check-ref-format
|
# from the git CLI and not part of the branch name. See https://git-scm.com/docs/git-check-ref-format
|
||||||
# We also drop the last empty line from the output.
|
# We also drop the last empty line from the output.
|
||||||
self._cache['branches'] = [ustr(branch.replace("*", "").strip()) for branch in branches[:-1]]
|
self._cache['branches'] = [branch.replace("*", "").strip() for branch in branches[:-1]]
|
||||||
|
|
||||||
return self._try_cache("branches", cache_branches)
|
return self._try_cache("branches", cache_branches)
|
||||||
|
|
||||||
|
@ -306,17 +287,17 @@ class StagedLocalGitCommit(GitCommit, PropertyCache):
|
||||||
@cache
|
@cache
|
||||||
def author_name(self):
|
def author_name(self):
|
||||||
try:
|
try:
|
||||||
return ustr(_git("config", "--get", "user.name", _cwd=self.context.repository_path)).strip()
|
return _git("config", "--get", "user.name", _cwd=self.context.repository_path).strip()
|
||||||
except GitExitCodeError:
|
except GitExitCodeError as e:
|
||||||
raise GitContextError("Missing git configuration: please set user.name")
|
raise GitContextError("Missing git configuration: please set user.name") from e
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@cache
|
@cache
|
||||||
def author_email(self):
|
def author_email(self):
|
||||||
try:
|
try:
|
||||||
return ustr(_git("config", "--get", "user.email", _cwd=self.context.repository_path)).strip()
|
return _git("config", "--get", "user.email", _cwd=self.context.repository_path).strip()
|
||||||
except GitExitCodeError:
|
except GitExitCodeError as e:
|
||||||
raise GitContextError("Missing git configuration: please set user.email")
|
raise GitContextError("Missing git configuration: please set user.email") from e
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@cache
|
@cache
|
||||||
|
@ -356,7 +337,7 @@ class GitContext(PropertyCache):
|
||||||
@property
|
@property
|
||||||
@cache
|
@cache
|
||||||
def current_branch(self):
|
def current_branch(self):
|
||||||
current_branch = ustr(_git("rev-parse", "--abbrev-ref", "HEAD", _cwd=self.repository_path)).strip()
|
current_branch = _git("rev-parse", "--abbrev-ref", "HEAD", _cwd=self.repository_path).strip()
|
||||||
return current_branch
|
return current_branch
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -396,7 +377,7 @@ class GitContext(PropertyCache):
|
||||||
# We tried many things here e.g.: defaulting to e.g. HEAD or HEAD^... (incl. dealing with
|
# We tried many things here e.g.: defaulting to e.g. HEAD or HEAD^... (incl. dealing with
|
||||||
# repos that only have a single commit - HEAD^... doesn't work there), but then we still get into
|
# repos that only have a single commit - HEAD^... doesn't work there), but then we still get into
|
||||||
# problems with e.g. merge commits. Easiest solution is just taking the SHA from `git log -1`.
|
# problems with e.g. merge commits. Easiest solution is just taking the SHA from `git log -1`.
|
||||||
sha_list = [_git("log", "-1", "--pretty=%H", _cwd=repository_path).replace(u"\n", u"")]
|
sha_list = [_git("log", "-1", "--pretty=%H", _cwd=repository_path).replace("\n", "")]
|
||||||
else:
|
else:
|
||||||
sha_list = _git("rev-list", refspec, _cwd=repository_path).split()
|
sha_list = _git("rev-list", refspec, _cwd=repository_path).split()
|
||||||
|
|
||||||
|
@ -410,6 +391,3 @@ class GitContext(PropertyCache):
|
||||||
return (isinstance(other, GitContext) and self.commits == other.commits
|
return (isinstance(other, GitContext) and self.commits == other.commits
|
||||||
and self.repository_path == other.repository_path
|
and self.repository_path == other.repository_path
|
||||||
and self.commentchar == other.commentchar and self.current_branch == other.current_branch) # noqa
|
and self.commentchar == other.commentchar and self.current_branch == other.current_branch) # noqa
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other) # required for py2
|
|
||||||
|
|
|
@ -5,17 +5,18 @@ import stat
|
||||||
|
|
||||||
from gitlint.utils import DEFAULT_ENCODING
|
from gitlint.utils import DEFAULT_ENCODING
|
||||||
from gitlint.git import git_hooks_dir
|
from gitlint.git import git_hooks_dir
|
||||||
|
from gitlint.exception import GitlintError
|
||||||
|
|
||||||
COMMIT_MSG_HOOK_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files", "commit-msg")
|
COMMIT_MSG_HOOK_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files", "commit-msg")
|
||||||
COMMIT_MSG_HOOK_DST_PATH = "commit-msg"
|
COMMIT_MSG_HOOK_DST_PATH = "commit-msg"
|
||||||
GITLINT_HOOK_IDENTIFIER = "### gitlint commit-msg hook start ###\n"
|
GITLINT_HOOK_IDENTIFIER = "### gitlint commit-msg hook start ###\n"
|
||||||
|
|
||||||
|
|
||||||
class GitHookInstallerError(Exception):
|
class GitHookInstallerError(GitlintError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GitHookInstaller(object):
|
class GitHookInstaller:
|
||||||
""" Utility class that provides methods for installing and uninstalling the gitlint commitmsg hook. """
|
""" Utility class that provides methods for installing and uninstalling the gitlint commitmsg hook. """
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -27,7 +28,7 @@ class GitHookInstaller(object):
|
||||||
""" Asserts that a given target directory is a git repository """
|
""" Asserts that a given target directory is a git repository """
|
||||||
hooks_dir = git_hooks_dir(target)
|
hooks_dir = git_hooks_dir(target)
|
||||||
if not os.path.isdir(hooks_dir):
|
if not os.path.isdir(hooks_dir):
|
||||||
raise GitHookInstallerError(u"{0} is not a git repository.".format(target))
|
raise GitHookInstallerError(f"{target} is not a git repository.")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def install_commit_msg_hook(lint_config):
|
def install_commit_msg_hook(lint_config):
|
||||||
|
@ -35,8 +36,8 @@ class GitHookInstaller(object):
|
||||||
dest_path = GitHookInstaller.commit_msg_hook_path(lint_config)
|
dest_path = GitHookInstaller.commit_msg_hook_path(lint_config)
|
||||||
if os.path.exists(dest_path):
|
if os.path.exists(dest_path):
|
||||||
raise GitHookInstallerError(
|
raise GitHookInstallerError(
|
||||||
u"There is already a commit-msg hook file present in {0}.\n".format(dest_path) +
|
f"There is already a commit-msg hook file present in {dest_path}.\n" +
|
||||||
u"gitlint currently does not support appending to an existing commit-msg file.")
|
"gitlint currently does not support appending to an existing commit-msg file.")
|
||||||
|
|
||||||
# copy hook file
|
# copy hook file
|
||||||
shutil.copy(COMMIT_MSG_HOOK_SRC_PATH, dest_path)
|
shutil.copy(COMMIT_MSG_HOOK_SRC_PATH, dest_path)
|
||||||
|
@ -49,14 +50,14 @@ class GitHookInstaller(object):
|
||||||
GitHookInstaller._assert_git_repo(lint_config.target)
|
GitHookInstaller._assert_git_repo(lint_config.target)
|
||||||
dest_path = GitHookInstaller.commit_msg_hook_path(lint_config)
|
dest_path = GitHookInstaller.commit_msg_hook_path(lint_config)
|
||||||
if not os.path.exists(dest_path):
|
if not os.path.exists(dest_path):
|
||||||
raise GitHookInstallerError(u"There is no commit-msg hook present in {0}.".format(dest_path))
|
raise GitHookInstallerError(f"There is no commit-msg hook present in {dest_path}.")
|
||||||
|
|
||||||
with io.open(dest_path, encoding=DEFAULT_ENCODING) as fp:
|
with io.open(dest_path, encoding=DEFAULT_ENCODING) as fp:
|
||||||
lines = fp.readlines()
|
lines = fp.readlines()
|
||||||
if len(lines) < 2 or lines[1] != GITLINT_HOOK_IDENTIFIER:
|
if len(lines) < 2 or lines[1] != GITLINT_HOOK_IDENTIFIER:
|
||||||
msg = u"The commit-msg hook in {0} was not installed by gitlint (or it was modified).\n" + \
|
msg = f"The commit-msg hook in {dest_path} was not installed by gitlint (or it was modified).\n" + \
|
||||||
u"Uninstallation of 3th party or modified gitlint hooks is not supported."
|
"Uninstallation of 3th party or modified gitlint hooks is not supported."
|
||||||
raise GitHookInstallerError(msg.format(dest_path))
|
raise GitHookInstallerError(msg)
|
||||||
|
|
||||||
# If we are sure it's a gitlint hook, go ahead and remove it
|
# If we are sure it's a gitlint hook, go ahead and remove it
|
||||||
os.remove(dest_path)
|
os.remove(dest_path)
|
||||||
|
|
|
@ -2,13 +2,12 @@
|
||||||
import logging
|
import logging
|
||||||
from gitlint import rules as gitlint_rules
|
from gitlint import rules as gitlint_rules
|
||||||
from gitlint import display
|
from gitlint import display
|
||||||
from gitlint.utils import ustr
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
|
|
||||||
|
|
||||||
class GitLinter(object):
|
class GitLinter:
|
||||||
""" Main linter class. This is where rules actually get applied. See the lint() method. """
|
""" Main linter class. This is where rules actually get applied. See the lint() method. """
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
@ -70,7 +69,7 @@ class GitLinter(object):
|
||||||
def lint(self, commit):
|
def lint(self, commit):
|
||||||
""" Lint the last commit in a given git context by applying all ignore, title, body and commit rules. """
|
""" Lint the last commit in a given git context by applying all ignore, title, body and commit rules. """
|
||||||
LOG.debug("Linting commit %s", commit.sha or "[SHA UNKNOWN]")
|
LOG.debug("Linting commit %s", commit.sha or "[SHA UNKNOWN]")
|
||||||
LOG.debug("Commit Object\n" + ustr(commit))
|
LOG.debug("Commit Object\n" + str(commit))
|
||||||
|
|
||||||
# Apply config rules
|
# Apply config rules
|
||||||
for rule in self.configuration_rules:
|
for rule in self.configuration_rules:
|
||||||
|
@ -79,8 +78,8 @@ class GitLinter(object):
|
||||||
# Skip linting if this is a special commit type that is configured to be ignored
|
# Skip linting if this is a special commit type that is configured to be ignored
|
||||||
ignore_commit_types = ["merge", "squash", "fixup", "revert"]
|
ignore_commit_types = ["merge", "squash", "fixup", "revert"]
|
||||||
for commit_type in ignore_commit_types:
|
for commit_type in ignore_commit_types:
|
||||||
if getattr(commit, "is_{0}_commit".format(commit_type)) and \
|
if getattr(commit, f"is_{commit_type}_commit") and \
|
||||||
getattr(self.config, "ignore_{0}_commits".format(commit_type)):
|
getattr(self.config, f"ignore_{commit_type}_commits"):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
violations = []
|
violations = []
|
||||||
|
@ -99,10 +98,9 @@ class GitLinter(object):
|
||||||
""" Print a given set of violations to the standard error output """
|
""" Print a given set of violations to the standard error output """
|
||||||
for v in violations:
|
for v in violations:
|
||||||
line_nr = v.line_nr if v.line_nr else "-"
|
line_nr = v.line_nr if v.line_nr else "-"
|
||||||
self.display.e(u"{0}: {1}".format(line_nr, v.rule_id), exact=True)
|
self.display.e(f"{line_nr}: {v.rule_id}", exact=True)
|
||||||
self.display.ee(u"{0}: {1} {2}".format(line_nr, v.rule_id, v.message), exact=True)
|
self.display.ee(f"{line_nr}: {v.rule_id} {v.message}", exact=True)
|
||||||
if v.content:
|
if v.content:
|
||||||
self.display.eee(u"{0}: {1} {2}: \"{3}\"".format(line_nr, v.rule_id, v.message, v.content),
|
self.display.eee(f"{line_nr}: {v.rule_id} {v.message}: \"{v.content}\"", exact=True)
|
||||||
exact=True)
|
|
||||||
else:
|
else:
|
||||||
self.display.eee(u"{0}: {1} {2}".format(line_nr, v.rule_id, v.message), exact=True)
|
self.display.eee(f"{line_nr}: {v.rule_id} {v.message}", exact=True)
|
||||||
|
|
|
@ -2,7 +2,7 @@ from abc import abstractmethod
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from gitlint.utils import ustr, sstr
|
from gitlint.exception import GitlintError
|
||||||
|
|
||||||
|
|
||||||
def allow_none(func):
|
def allow_none(func):
|
||||||
|
@ -17,11 +17,11 @@ def allow_none(func):
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
class RuleOptionError(Exception):
|
class RuleOptionError(GitlintError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RuleOption(object):
|
class RuleOption:
|
||||||
""" Base class representing a configurable part (i.e. option) of a rule (e.g. the max-length of the title-max-line
|
""" Base class representing a configurable part (i.e. option) of a rule (e.g. the max-length of the title-max-line
|
||||||
rule).
|
rule).
|
||||||
This class should not be used directly. Instead, use on the derived classes like StrOption, IntOption to set
|
This class should not be used directly. Instead, use on the derived classes like StrOption, IntOption to set
|
||||||
|
@ -29,8 +29,8 @@ class RuleOption(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, value, description):
|
def __init__(self, name, value, description):
|
||||||
self.name = ustr(name)
|
self.name = name
|
||||||
self.description = ustr(description)
|
self.description = description
|
||||||
self.value = None
|
self.value = None
|
||||||
self.set(value)
|
self.set(value)
|
||||||
|
|
||||||
|
@ -40,37 +40,28 @@ class RuleOption(object):
|
||||||
pass # pragma: no cover
|
pass # pragma: no cover
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return sstr(self) # pragma: no cover
|
return f"({self.name}: {self.value} ({self.description}))"
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return u"({0}: {1} ({2}))".format(self.name, self.value, self.description) # pragma: no cover
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.__str__() # pragma: no cover
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.name == other.name and self.description == other.description and self.value == other.value
|
return self.name == other.name and self.description == other.description and self.value == other.value
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other) # required for py2
|
|
||||||
|
|
||||||
|
|
||||||
class StrOption(RuleOption):
|
class StrOption(RuleOption):
|
||||||
@allow_none
|
@allow_none
|
||||||
def set(self, value):
|
def set(self, value):
|
||||||
self.value = ustr(value)
|
self.value = str(value)
|
||||||
|
|
||||||
|
|
||||||
class IntOption(RuleOption):
|
class IntOption(RuleOption):
|
||||||
def __init__(self, name, value, description, allow_negative=False):
|
def __init__(self, name, value, description, allow_negative=False):
|
||||||
self.allow_negative = allow_negative
|
self.allow_negative = allow_negative
|
||||||
super(IntOption, self).__init__(name, value, description)
|
super().__init__(name, value, description)
|
||||||
|
|
||||||
def _raise_exception(self, value):
|
def _raise_exception(self, value):
|
||||||
if self.allow_negative:
|
if self.allow_negative:
|
||||||
error_msg = u"Option '{0}' must be an integer (current value: '{1}')".format(self.name, value)
|
error_msg = f"Option '{self.name}' must be an integer (current value: '{value}')"
|
||||||
else:
|
else:
|
||||||
error_msg = u"Option '{0}' must be a positive integer (current value: '{1}')".format(self.name, value)
|
error_msg = f"Option '{self.name}' must be a positive integer (current value: '{value}')"
|
||||||
raise RuleOptionError(error_msg)
|
raise RuleOptionError(error_msg)
|
||||||
|
|
||||||
@allow_none
|
@allow_none
|
||||||
|
@ -88,9 +79,9 @@ class BoolOption(RuleOption):
|
||||||
|
|
||||||
# explicit choice to not annotate with @allow_none: Booleans must be False or True, they cannot be unset.
|
# explicit choice to not annotate with @allow_none: Booleans must be False or True, they cannot be unset.
|
||||||
def set(self, value):
|
def set(self, value):
|
||||||
value = ustr(value).strip().lower()
|
value = str(value).strip().lower()
|
||||||
if value not in ['true', 'false']:
|
if value not in ['true', 'false']:
|
||||||
raise RuleOptionError(u"Option '{0}' must be either 'true' or 'false'".format(self.name))
|
raise RuleOptionError(f"Option '{self.name}' must be either 'true' or 'false'")
|
||||||
self.value = value == 'true'
|
self.value = value == 'true'
|
||||||
|
|
||||||
|
|
||||||
|
@ -103,37 +94,36 @@ class ListOption(RuleOption):
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
the_list = value
|
the_list = value
|
||||||
else:
|
else:
|
||||||
the_list = ustr(value).split(",")
|
the_list = str(value).split(",")
|
||||||
|
|
||||||
self.value = [ustr(item.strip()) for item in the_list if item.strip() != ""]
|
self.value = [str(item.strip()) for item in the_list if item.strip() != ""]
|
||||||
|
|
||||||
|
|
||||||
class PathOption(RuleOption):
|
class PathOption(RuleOption):
|
||||||
""" Option that accepts either a directory or both a directory and a file. """
|
""" Option that accepts either a directory or both a directory and a file. """
|
||||||
|
|
||||||
def __init__(self, name, value, description, type=u"dir"):
|
def __init__(self, name, value, description, type="dir"):
|
||||||
self.type = type
|
self.type = type
|
||||||
super(PathOption, self).__init__(name, value, description)
|
super().__init__(name, value, description)
|
||||||
|
|
||||||
@allow_none
|
@allow_none
|
||||||
def set(self, value):
|
def set(self, value):
|
||||||
value = ustr(value)
|
value = str(value)
|
||||||
|
|
||||||
error_msg = u""
|
error_msg = ""
|
||||||
|
|
||||||
if self.type == 'dir':
|
if self.type == 'dir':
|
||||||
if not os.path.isdir(value):
|
if not os.path.isdir(value):
|
||||||
error_msg = u"Option {0} must be an existing directory (current value: '{1}')".format(self.name, value)
|
error_msg = f"Option {self.name} must be an existing directory (current value: '{value}')"
|
||||||
elif self.type == 'file':
|
elif self.type == 'file':
|
||||||
if not os.path.isfile(value):
|
if not os.path.isfile(value):
|
||||||
error_msg = u"Option {0} must be an existing file (current value: '{1}')".format(self.name, value)
|
error_msg = f"Option {self.name} must be an existing file (current value: '{value}')"
|
||||||
elif self.type == 'both':
|
elif self.type == 'both':
|
||||||
if not os.path.isdir(value) and not os.path.isfile(value):
|
if not os.path.isdir(value) and not os.path.isfile(value):
|
||||||
error_msg = (u"Option {0} must be either an existing directory or file "
|
error_msg = (f"Option {self.name} must be either an existing directory or file "
|
||||||
u"(current value: '{1}')").format(self.name, value)
|
f"(current value: '{value}')")
|
||||||
else:
|
else:
|
||||||
error_msg = u"Option {0} type must be one of: 'file', 'dir', 'both' (current: '{1}')".format(self.name,
|
error_msg = f"Option {self.name} type must be one of: 'file', 'dir', 'both' (current: '{self.type}')"
|
||||||
self.type)
|
|
||||||
|
|
||||||
if error_msg:
|
if error_msg:
|
||||||
raise RuleOptionError(error_msg)
|
raise RuleOptionError(error_msg)
|
||||||
|
@ -148,7 +138,7 @@ class RegexOption(RuleOption):
|
||||||
try:
|
try:
|
||||||
self.value = re.compile(value, re.UNICODE)
|
self.value = re.compile(value, re.UNICODE)
|
||||||
except (re.error, TypeError) as exc:
|
except (re.error, TypeError) as exc:
|
||||||
raise RuleOptionError("Invalid regular expression: '{0}'".format(exc))
|
raise RuleOptionError(f"Invalid regular expression: '{exc}'") from exc
|
||||||
|
|
||||||
def __deepcopy__(self, _):
|
def __deepcopy__(self, _):
|
||||||
# copy.deepcopy() - used in rules.py - doesn't support copying regex objects prior to Python 3.7
|
# copy.deepcopy() - used in rules.py - doesn't support copying regex objects prior to Python 3.7
|
||||||
|
|
|
@ -5,7 +5,6 @@ import sys
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
from gitlint import rules, options
|
from gitlint import rules, options
|
||||||
from gitlint.utils import ustr
|
|
||||||
|
|
||||||
|
|
||||||
def find_rule_classes(extra_path):
|
def find_rule_classes(extra_path):
|
||||||
|
@ -28,7 +27,7 @@ def find_rule_classes(extra_path):
|
||||||
files = os.listdir(extra_path)
|
files = os.listdir(extra_path)
|
||||||
directory = extra_path
|
directory = extra_path
|
||||||
else:
|
else:
|
||||||
raise rules.UserRuleError(u"Invalid extra-path: {0}".format(extra_path))
|
raise rules.UserRuleError(f"Invalid extra-path: {extra_path}")
|
||||||
|
|
||||||
# Filter out files that are not python modules
|
# Filter out files that are not python modules
|
||||||
for filename in files:
|
for filename in files:
|
||||||
|
@ -56,7 +55,7 @@ def find_rule_classes(extra_path):
|
||||||
importlib.import_module(module)
|
importlib.import_module(module)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise rules.UserRuleError(u"Error while importing extra-path module '{0}': {1}".format(module, ustr(e)))
|
raise rules.UserRuleError(f"Error while importing extra-path module '{module}': {e}")
|
||||||
|
|
||||||
# Find all rule classes in the module. We do this my inspecting all members of the module and checking
|
# Find all rule classes in the module. We do this my inspecting all members of the module and checking
|
||||||
# 1) is it a class, if not, skip
|
# 1) is it a class, if not, skip
|
||||||
|
@ -94,55 +93,53 @@ def assert_valid_rule_class(clazz, rule_type="User-defined"): # pylint: disable
|
||||||
# Rules must extend from LineRule, CommitRule or ConfigurationRule
|
# Rules must extend from LineRule, CommitRule or ConfigurationRule
|
||||||
if not (issubclass(clazz, rules.LineRule) or issubclass(clazz, rules.CommitRule)
|
if not (issubclass(clazz, rules.LineRule) or issubclass(clazz, rules.CommitRule)
|
||||||
or issubclass(clazz, rules.ConfigurationRule)):
|
or issubclass(clazz, rules.ConfigurationRule)):
|
||||||
msg = u"{0} rule class '{1}' must extend from {2}.{3}, {2}.{4} or {2}.{5}"
|
msg = f"{rule_type} rule class '{clazz.__name__}' " + \
|
||||||
raise rules.UserRuleError(msg.format(rule_type, clazz.__name__, rules.CommitRule.__module__,
|
f"must extend from {rules.CommitRule.__module__}.{rules.LineRule.__name__}, " + \
|
||||||
rules.LineRule.__name__, rules.CommitRule.__name__,
|
f"{rules.CommitRule.__module__}.{rules.CommitRule.__name__} or " + \
|
||||||
rules.ConfigurationRule.__name__))
|
f"{rules.CommitRule.__module__}.{rules.ConfigurationRule.__name__}"
|
||||||
|
raise rules.UserRuleError(msg)
|
||||||
|
|
||||||
# Rules must have an id attribute
|
# Rules must have an id attribute
|
||||||
if not hasattr(clazz, 'id') or clazz.id is None or not clazz.id:
|
if not hasattr(clazz, 'id') or clazz.id is None or not clazz.id:
|
||||||
msg = u"{0} rule class '{1}' must have an 'id' attribute"
|
raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have an 'id' attribute")
|
||||||
raise rules.UserRuleError(msg.format(rule_type, clazz.__name__))
|
|
||||||
|
|
||||||
# Rule id's cannot start with gitlint reserved letters
|
# Rule id's cannot start with gitlint reserved letters
|
||||||
if clazz.id[0].upper() in ['R', 'T', 'B', 'M', 'I']:
|
if clazz.id[0].upper() in ['R', 'T', 'B', 'M', 'I']:
|
||||||
msg = u"The id '{1}' of '{0}' is invalid. Gitlint reserves ids starting with R,T,B,M,I"
|
msg = f"The id '{clazz.id[0]}' of '{clazz.__name__}' is invalid. Gitlint reserves ids starting with R,T,B,M,I"
|
||||||
raise rules.UserRuleError(msg.format(clazz.__name__, clazz.id[0]))
|
raise rules.UserRuleError(msg)
|
||||||
|
|
||||||
# Rules must have a name attribute
|
# Rules must have a name attribute
|
||||||
if not hasattr(clazz, 'name') or clazz.name is None or not clazz.name:
|
if not hasattr(clazz, 'name') or clazz.name is None or not clazz.name:
|
||||||
msg = u"{0} rule class '{1}' must have a 'name' attribute"
|
raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have a 'name' attribute")
|
||||||
raise rules.UserRuleError(msg.format(rule_type, clazz.__name__))
|
|
||||||
|
|
||||||
# if set, options_spec must be a list of RuleOption
|
# if set, options_spec must be a list of RuleOption
|
||||||
if not isinstance(clazz.options_spec, list):
|
if not isinstance(clazz.options_spec, list):
|
||||||
msg = u"The options_spec attribute of {0} rule class '{1}' must be a list of {2}.{3}"
|
msg = f"The options_spec attribute of {rule_type.lower()} rule class '{clazz.__name__}' " + \
|
||||||
raise rules.UserRuleError(msg.format(rule_type.lower(), clazz.__name__,
|
f"must be a list of {options.RuleOption.__module__}.{options.RuleOption.__name__}"
|
||||||
options.RuleOption.__module__, options.RuleOption.__name__))
|
raise rules.UserRuleError(msg)
|
||||||
|
|
||||||
# check that all items in options_spec are actual gitlint options
|
# check that all items in options_spec are actual gitlint options
|
||||||
for option in clazz.options_spec:
|
for option in clazz.options_spec:
|
||||||
if not isinstance(option, options.RuleOption):
|
if not isinstance(option, options.RuleOption):
|
||||||
msg = u"The options_spec attribute of {0} rule class '{1}' must be a list of {2}.{3}"
|
msg = f"The options_spec attribute of {rule_type.lower()} rule class '{clazz.__name__}' " + \
|
||||||
raise rules.UserRuleError(msg.format(rule_type.lower(), clazz.__name__,
|
f"must be a list of {options.RuleOption.__module__}.{options.RuleOption.__name__}"
|
||||||
options.RuleOption.__module__, options.RuleOption.__name__))
|
raise rules.UserRuleError(msg)
|
||||||
|
|
||||||
# Line/Commit rules must have a `validate` method
|
# Line/Commit rules must have a `validate` method
|
||||||
# We use isroutine() as it's both python 2 and 3 compatible. Details: http://stackoverflow.com/a/17019998/381010
|
# We use isroutine() as it's both python 2 and 3 compatible. Details: http://stackoverflow.com/a/17019998/381010
|
||||||
if (issubclass(clazz, rules.LineRule) or issubclass(clazz, rules.CommitRule)):
|
if (issubclass(clazz, rules.LineRule) or issubclass(clazz, rules.CommitRule)):
|
||||||
if not hasattr(clazz, 'validate') or not inspect.isroutine(clazz.validate):
|
if not hasattr(clazz, 'validate') or not inspect.isroutine(clazz.validate):
|
||||||
msg = u"{0} rule class '{1}' must have a 'validate' method"
|
raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have a 'validate' method")
|
||||||
raise rules.UserRuleError(msg.format(rule_type, clazz.__name__))
|
|
||||||
# Configuration rules must have an `apply` method
|
# Configuration rules must have an `apply` method
|
||||||
elif issubclass(clazz, rules.ConfigurationRule):
|
elif issubclass(clazz, rules.ConfigurationRule):
|
||||||
if not hasattr(clazz, 'apply') or not inspect.isroutine(clazz.apply):
|
if not hasattr(clazz, 'apply') or not inspect.isroutine(clazz.apply):
|
||||||
msg = u"{0} Configuration rule class '{1}' must have an 'apply' method"
|
msg = f"{rule_type} Configuration rule class '{clazz.__name__}' must have an 'apply' method"
|
||||||
raise rules.UserRuleError(msg.format(rule_type, clazz.__name__))
|
raise rules.UserRuleError(msg)
|
||||||
|
|
||||||
# LineRules must have a valid target: rules.CommitMessageTitle or rules.CommitMessageBody
|
# LineRules must have a valid target: rules.CommitMessageTitle or rules.CommitMessageBody
|
||||||
if issubclass(clazz, rules.LineRule):
|
if issubclass(clazz, rules.LineRule):
|
||||||
if clazz.target not in [rules.CommitMessageTitle, rules.CommitMessageBody]:
|
if clazz.target not in [rules.CommitMessageTitle, rules.CommitMessageBody]:
|
||||||
msg = u"The target attribute of the {0} LineRule class '{1}' must be either {2}.{3} or {2}.{4}"
|
msg = f"The target attribute of the {rule_type.lower()} LineRule class '{clazz.__name__}' " + \
|
||||||
msg = msg.format(rule_type.lower(), clazz.__name__, rules.CommitMessageTitle.__module__,
|
f"must be either {rules.CommitMessageTitle.__module__}.{rules.CommitMessageTitle.__name__} " + \
|
||||||
rules.CommitMessageTitle.__name__, rules.CommitMessageBody.__name__)
|
f"or {rules.CommitMessageTitle.__module__}.{rules.CommitMessageBody.__name__}"
|
||||||
raise rules.UserRuleError(msg)
|
raise rules.UserRuleError(msg)
|
||||||
|
|
|
@ -4,10 +4,10 @@ import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from gitlint.options import IntOption, BoolOption, StrOption, ListOption, RegexOption
|
from gitlint.options import IntOption, BoolOption, StrOption, ListOption, RegexOption
|
||||||
from gitlint.utils import sstr
|
from gitlint.exception import GitlintError
|
||||||
|
|
||||||
|
|
||||||
class Rule(object):
|
class Rule:
|
||||||
""" Class representing gitlint rules. """
|
""" Class representing gitlint rules. """
|
||||||
options_spec = []
|
options_spec = []
|
||||||
id = None
|
id = None
|
||||||
|
@ -36,17 +36,8 @@ class Rule(object):
|
||||||
return self.id == other.id and self.name == other.name and \
|
return self.id == other.id and self.name == other.name and \
|
||||||
self.options == other.options and self.target == other.target # noqa
|
self.options == other.options and self.target == other.target # noqa
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other) # required for py2
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return sstr(self) # pragma: no cover
|
return f"{self.id} {self.name}" # pragma: no cover
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return u"{0} {1}".format(self.id, self.name) # pragma: no cover
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.__str__() # pragma: no cover
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationRule(Rule):
|
class ConfigurationRule(Rule):
|
||||||
|
@ -64,7 +55,7 @@ class LineRule(Rule):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LineRuleTarget(object):
|
class LineRuleTarget:
|
||||||
""" Base class for LineRule targets. A LineRuleTarget specifies where a given rule will be applied
|
""" Base class for LineRule targets. A LineRuleTarget specifies where a given rule will be applied
|
||||||
(e.g. commit message title, commit message body).
|
(e.g. commit message title, commit message body).
|
||||||
Each LineRule MUST have a target specified. """
|
Each LineRule MUST have a target specified. """
|
||||||
|
@ -81,7 +72,7 @@ class CommitMessageBody(LineRuleTarget):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RuleViolation(object):
|
class RuleViolation:
|
||||||
""" Class representing a violation of a rule. I.e.: When a rule is broken, the rule will instantiate this class
|
""" Class representing a violation of a rule. I.e.: When a rule is broken, the rule will instantiate this class
|
||||||
to indicate how and where the rule was broken. """
|
to indicate how and where the rule was broken. """
|
||||||
|
|
||||||
|
@ -96,21 +87,11 @@ class RuleViolation(object):
|
||||||
equal = equal and self.content == other.content and self.line_nr == other.line_nr
|
equal = equal and self.content == other.content and self.line_nr == other.line_nr
|
||||||
return equal
|
return equal
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other) # required for py2
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return sstr(self) # pragma: no cover
|
return f"{self.line_nr}: {self.rule_id} {self.message}: \"{self.content}\""
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return u"{0}: {1} {2}: \"{3}\"".format(self.line_nr, self.rule_id, self.message,
|
|
||||||
self.content) # pragma: no cover
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.__unicode__() # pragma: no cover
|
|
||||||
|
|
||||||
|
|
||||||
class UserRuleError(Exception):
|
class UserRuleError(GitlintError):
|
||||||
""" Error used to indicate that an error occurred while trying to load a user rule """
|
""" Error used to indicate that an error occurred while trying to load a user rule """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -154,7 +135,7 @@ class LineMustNotContainWord(LineRule):
|
||||||
name = "line-must-not-contain"
|
name = "line-must-not-contain"
|
||||||
id = "R5"
|
id = "R5"
|
||||||
options_spec = [ListOption('words', [], "Comma separated list of words that should not be found")]
|
options_spec = [ListOption('words', [], "Comma separated list of words that should not be found")]
|
||||||
violation_message = u"Line contains {0}"
|
violation_message = "Line contains {0}"
|
||||||
|
|
||||||
def validate(self, line, _commit):
|
def validate(self, line, _commit):
|
||||||
strings = self.options['words'].value
|
strings = self.options['words'].value
|
||||||
|
@ -202,7 +183,7 @@ class TitleTrailingPunctuation(LineRule):
|
||||||
punctuation_marks = '?:!.,;'
|
punctuation_marks = '?:!.,;'
|
||||||
for punctuation_mark in punctuation_marks:
|
for punctuation_mark in punctuation_marks:
|
||||||
if title.endswith(punctuation_mark):
|
if title.endswith(punctuation_mark):
|
||||||
return [RuleViolation(self.id, u"Title has trailing punctuation ({0})".format(punctuation_mark), title)]
|
return [RuleViolation(self.id, f"Title has trailing punctuation ({punctuation_mark})", title)]
|
||||||
|
|
||||||
|
|
||||||
class TitleHardTab(HardTab):
|
class TitleHardTab(HardTab):
|
||||||
|
@ -217,7 +198,7 @@ class TitleMustNotContainWord(LineMustNotContainWord):
|
||||||
id = "T5"
|
id = "T5"
|
||||||
target = CommitMessageTitle
|
target = CommitMessageTitle
|
||||||
options_spec = [ListOption('words', ["WIP"], "Must not contain word")]
|
options_spec = [ListOption('words', ["WIP"], "Must not contain word")]
|
||||||
violation_message = u"Title contains the word '{0}' (case-insensitive)"
|
violation_message = "Title contains the word '{0}' (case-insensitive)"
|
||||||
|
|
||||||
|
|
||||||
class TitleLeadingWhitespace(LeadingWhiteSpace):
|
class TitleLeadingWhitespace(LeadingWhiteSpace):
|
||||||
|
@ -239,7 +220,7 @@ class TitleRegexMatches(LineRule):
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.options['regex'].value.search(title):
|
if not self.options['regex'].value.search(title):
|
||||||
violation_msg = u"Title does not match regex ({0})".format(self.options['regex'].value.pattern)
|
violation_msg = f"Title does not match regex ({self.options['regex'].value.pattern})"
|
||||||
return [RuleViolation(self.id, violation_msg, title)]
|
return [RuleViolation(self.id, violation_msg, title)]
|
||||||
|
|
||||||
|
|
||||||
|
@ -253,7 +234,7 @@ class TitleMinLength(LineRule):
|
||||||
min_length = self.options['min-length'].value
|
min_length = self.options['min-length'].value
|
||||||
actual_length = len(title)
|
actual_length = len(title)
|
||||||
if actual_length < min_length:
|
if actual_length < min_length:
|
||||||
violation_message = "Title is too short ({0}<{1})".format(actual_length, min_length)
|
violation_message = f"Title is too short ({actual_length}<{min_length})"
|
||||||
return [RuleViolation(self.id, violation_message, title, 1)]
|
return [RuleViolation(self.id, violation_message, title, 1)]
|
||||||
|
|
||||||
|
|
||||||
|
@ -296,7 +277,7 @@ class BodyMinLength(CommitRule):
|
||||||
body_message_no_newline = "".join([line for line in commit.message.body if line is not None])
|
body_message_no_newline = "".join([line for line in commit.message.body if line is not None])
|
||||||
actual_length = len(body_message_no_newline)
|
actual_length = len(body_message_no_newline)
|
||||||
if 0 < actual_length < min_length:
|
if 0 < actual_length < min_length:
|
||||||
violation_message = "Body message is too short ({0}<{1})".format(actual_length, min_length)
|
violation_message = f"Body message is too short ({actual_length}<{min_length})"
|
||||||
return [RuleViolation(self.id, violation_message, body_message_no_newline, 3)]
|
return [RuleViolation(self.id, violation_message, body_message_no_newline, 3)]
|
||||||
|
|
||||||
|
|
||||||
|
@ -325,7 +306,7 @@ class BodyChangedFileMention(CommitRule):
|
||||||
# in the commit msg body
|
# in the commit msg body
|
||||||
if needs_mentioned_file in commit.changed_files:
|
if needs_mentioned_file in commit.changed_files:
|
||||||
if needs_mentioned_file not in " ".join(commit.message.body):
|
if needs_mentioned_file not in " ".join(commit.message.body):
|
||||||
violation_message = u"Body does not mention changed file '{0}'".format(needs_mentioned_file)
|
violation_message = f"Body does not mention changed file '{needs_mentioned_file}'"
|
||||||
violations.append(RuleViolation(self.id, violation_message, None, len(commit.message.body) + 1))
|
violations.append(RuleViolation(self.id, violation_message, None, len(commit.message.body) + 1))
|
||||||
return violations if violations else None
|
return violations if violations else None
|
||||||
|
|
||||||
|
@ -354,7 +335,7 @@ class BodyRegexMatches(CommitRule):
|
||||||
full_body = "\n".join(body_lines)
|
full_body = "\n".join(body_lines)
|
||||||
|
|
||||||
if not self.options['regex'].value.search(full_body):
|
if not self.options['regex'].value.search(full_body):
|
||||||
violation_msg = u"Body does not match regex ({0})".format(self.options['regex'].value.pattern)
|
violation_msg = f"Body does not match regex ({self.options['regex'].value.pattern})"
|
||||||
return [RuleViolation(self.id, violation_msg, None, len(commit.message.body) + 1)]
|
return [RuleViolation(self.id, violation_msg, None, len(commit.message.body) + 1)]
|
||||||
|
|
||||||
|
|
||||||
|
@ -386,9 +367,8 @@ class IgnoreByTitle(ConfigurationRule):
|
||||||
if self.options['regex'].value.match(commit.message.title):
|
if self.options['regex'].value.match(commit.message.title):
|
||||||
config.ignore = self.options['ignore'].value
|
config.ignore = self.options['ignore'].value
|
||||||
|
|
||||||
message = u"Commit title '{0}' matches the regex '{1}', ignoring rules: {2}"
|
message = f"Commit title '{commit.message.title}' matches the regex " + \
|
||||||
message = message.format(commit.message.title, self.options['regex'].value.pattern,
|
f"'{self.options['regex'].value.pattern}', ignoring rules: {self.options['ignore'].value}"
|
||||||
self.options['ignore'].value)
|
|
||||||
|
|
||||||
self.log.debug("Ignoring commit because of rule '%s': %s", self.id, message)
|
self.log.debug("Ignoring commit because of rule '%s': %s", self.id, message)
|
||||||
|
|
||||||
|
@ -408,8 +388,8 @@ class IgnoreByBody(ConfigurationRule):
|
||||||
if self.options['regex'].value.match(line):
|
if self.options['regex'].value.match(line):
|
||||||
config.ignore = self.options['ignore'].value
|
config.ignore = self.options['ignore'].value
|
||||||
|
|
||||||
message = u"Commit message line '{0}' matches the regex '{1}', ignoring rules: {2}"
|
message = f"Commit message line '{line}' matches the regex '{self.options['regex'].value.pattern}'," + \
|
||||||
message = message.format(line, self.options['regex'].value.pattern, self.options['ignore'].value)
|
f" ignoring rules: {self.options['ignore'].value}"
|
||||||
|
|
||||||
self.log.debug("Ignoring commit because of rule '%s': %s", self.id, message)
|
self.log.debug("Ignoring commit because of rule '%s': %s", self.id, message)
|
||||||
# No need to check other lines if we found a match
|
# No need to check other lines if we found a match
|
||||||
|
@ -429,10 +409,10 @@ class IgnoreBodyLines(ConfigurationRule):
|
||||||
new_body = []
|
new_body = []
|
||||||
for line in commit.message.body:
|
for line in commit.message.body:
|
||||||
if self.options['regex'].value.match(line):
|
if self.options['regex'].value.match(line):
|
||||||
debug_msg = u"Ignoring line '%s' because it matches '%s'"
|
debug_msg = "Ignoring line '%s' because it matches '%s'"
|
||||||
self.log.debug(debug_msg, line, self.options['regex'].value.pattern)
|
self.log.debug(debug_msg, line, self.options['regex'].value.pattern)
|
||||||
else:
|
else:
|
||||||
new_body.append(line)
|
new_body.append(line)
|
||||||
|
|
||||||
commit.message.body = new_body
|
commit.message.body = new_body
|
||||||
commit.message.full = u"\n".join([commit.message.title] + new_body)
|
commit.message.full = "\n".join([commit.message.title] + new_body)
|
||||||
|
|
|
@ -6,7 +6,7 @@ capabilities wrt dealing with more edge-case environments on *nix systems that a
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from gitlint.utils import ustr, IS_PY2, USE_SH_LIB
|
from gitlint.utils import USE_SH_LIB, DEFAULT_ENCODING
|
||||||
|
|
||||||
|
|
||||||
def shell(cmd):
|
def shell(cmd):
|
||||||
|
@ -25,7 +25,7 @@ else:
|
||||||
""" Exception indicating a command was not found during execution """
|
""" Exception indicating a command was not found during execution """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class ShResult(object):
|
class ShResult:
|
||||||
""" Result wrapper class. We use this to more easily migrate from using https://amoffat.github.io/sh/ to using
|
""" Result wrapper class. We use this to more easily migrate from using https://amoffat.github.io/sh/ to using
|
||||||
the builtin subprocess module """
|
the builtin subprocess module """
|
||||||
|
|
||||||
|
@ -51,11 +51,6 @@ else:
|
||||||
return _exec(*args, **kwargs)
|
return _exec(*args, **kwargs)
|
||||||
|
|
||||||
def _exec(*args, **kwargs):
|
def _exec(*args, **kwargs):
|
||||||
if IS_PY2:
|
|
||||||
no_command_error = OSError # noqa pylint: disable=undefined-variable,invalid-name
|
|
||||||
else:
|
|
||||||
no_command_error = FileNotFoundError # noqa pylint: disable=undefined-variable
|
|
||||||
|
|
||||||
pipe = subprocess.PIPE
|
pipe = subprocess.PIPE
|
||||||
popen_kwargs = {'stdout': pipe, 'stderr': pipe, 'shell': kwargs.get('_tty_out', False)}
|
popen_kwargs = {'stdout': pipe, 'stderr': pipe, 'shell': kwargs.get('_tty_out', False)}
|
||||||
if '_cwd' in kwargs:
|
if '_cwd' in kwargs:
|
||||||
|
@ -64,11 +59,11 @@ else:
|
||||||
try:
|
try:
|
||||||
p = subprocess.Popen(args, **popen_kwargs)
|
p = subprocess.Popen(args, **popen_kwargs)
|
||||||
result = p.communicate()
|
result = p.communicate()
|
||||||
except no_command_error:
|
except FileNotFoundError as e:
|
||||||
raise CommandNotFound
|
raise CommandNotFound from e
|
||||||
|
|
||||||
exit_code = p.returncode
|
exit_code = p.returncode
|
||||||
stdout = ustr(result[0])
|
stdout = result[0].decode(DEFAULT_ENCODING)
|
||||||
stderr = result[1] # 'sh' does not decode the stderr bytes to unicode
|
stderr = result[1] # 'sh' does not decode the stderr bytes to unicode
|
||||||
full_cmd = '' if args is None else ' '.join(args)
|
full_cmd = '' if args is None else ' '.join(args)
|
||||||
|
|
||||||
|
|
|
@ -9,33 +9,12 @@ import re
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
try:
|
import unittest
|
||||||
# python 2.x
|
|
||||||
import unittest2 as unittest
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
try:
|
from unittest.mock import patch
|
||||||
# python 2.x
|
|
||||||
from mock import patch
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
|
|
||||||
|
|
||||||
from gitlint.git import GitContext
|
from gitlint.git import GitContext
|
||||||
from gitlint.utils import ustr, IS_PY2, LOG_FORMAT, DEFAULT_ENCODING
|
from gitlint.utils import LOG_FORMAT, DEFAULT_ENCODING
|
||||||
|
|
||||||
|
|
||||||
# unittest2's assertRaisesRegex doesn't do unicode comparison.
|
|
||||||
# Let's monkeypatch the str() function to point to unicode() so that it does :)
|
|
||||||
# For reference, this is where this patch is required:
|
|
||||||
# https://hg.python.org/unittest2/file/tip/unittest2/case.py#l227
|
|
||||||
try:
|
|
||||||
# python 2.x
|
|
||||||
unittest.case.str = unicode
|
|
||||||
except (AttributeError, NameError):
|
|
||||||
pass # python 3.x
|
|
||||||
|
|
||||||
|
|
||||||
class BaseTestCase(unittest.TestCase):
|
class BaseTestCase(unittest.TestCase):
|
||||||
|
@ -72,24 +51,22 @@ class BaseTestCase(unittest.TestCase):
|
||||||
def get_sample_path(filename=""):
|
def get_sample_path(filename=""):
|
||||||
# Don't join up empty files names because this will add a trailing slash
|
# Don't join up empty files names because this will add a trailing slash
|
||||||
if filename == "":
|
if filename == "":
|
||||||
return ustr(BaseTestCase.SAMPLES_DIR)
|
return BaseTestCase.SAMPLES_DIR
|
||||||
|
|
||||||
return ustr(os.path.join(BaseTestCase.SAMPLES_DIR, filename))
|
return os.path.join(BaseTestCase.SAMPLES_DIR, filename)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_sample(filename=""):
|
def get_sample(filename=""):
|
||||||
""" Read and return the contents of a file in gitlint/tests/samples """
|
""" Read and return the contents of a file in gitlint/tests/samples """
|
||||||
sample_path = BaseTestCase.get_sample_path(filename)
|
sample_path = BaseTestCase.get_sample_path(filename)
|
||||||
with io.open(sample_path, encoding=DEFAULT_ENCODING) as content:
|
with io.open(sample_path, encoding=DEFAULT_ENCODING) as content:
|
||||||
sample = ustr(content.read())
|
sample = content.read()
|
||||||
return sample
|
return sample
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def patch_input(side_effect):
|
def patch_input(side_effect):
|
||||||
""" Patches the built-in input() with a provided side-effect """
|
""" Patches the built-in input() with a provided side-effect """
|
||||||
module_path = "builtins.input"
|
module_path = "builtins.input"
|
||||||
if IS_PY2:
|
|
||||||
module_path = "__builtin__.raw_input"
|
|
||||||
patched_module = patch(module_path, side_effect=side_effect)
|
patched_module = patch(module_path, side_effect=side_effect)
|
||||||
return patched_module
|
return patched_module
|
||||||
|
|
||||||
|
@ -99,7 +76,7 @@ class BaseTestCase(unittest.TestCase):
|
||||||
Optionally replace template variables specified by variable_dict. """
|
Optionally replace template variables specified by variable_dict. """
|
||||||
expected_path = os.path.join(BaseTestCase.EXPECTED_DIR, filename)
|
expected_path = os.path.join(BaseTestCase.EXPECTED_DIR, filename)
|
||||||
with io.open(expected_path, encoding=DEFAULT_ENCODING) as content:
|
with io.open(expected_path, encoding=DEFAULT_ENCODING) as content:
|
||||||
expected = ustr(content.read())
|
expected = content.read()
|
||||||
|
|
||||||
if variable_dict:
|
if variable_dict:
|
||||||
expected = expected.format(**variable_dict)
|
expected = expected.format(**variable_dict)
|
||||||
|
@ -114,7 +91,7 @@ class BaseTestCase(unittest.TestCase):
|
||||||
""" Utility method to easily create gitcontext objects based on a given commit msg string and an optional set of
|
""" Utility method to easily create gitcontext objects based on a given commit msg string and an optional set of
|
||||||
changed files"""
|
changed files"""
|
||||||
with patch("gitlint.git.git_commentchar") as comment_char:
|
with patch("gitlint.git.git_commentchar") as comment_char:
|
||||||
comment_char.return_value = u"#"
|
comment_char.return_value = "#"
|
||||||
gitcontext = GitContext.from_commit_msg(commit_msg_str)
|
gitcontext = GitContext.from_commit_msg(commit_msg_str)
|
||||||
commit = gitcontext.commits[-1]
|
commit = gitcontext.commits[-1]
|
||||||
if changed_files:
|
if changed_files:
|
||||||
|
@ -147,8 +124,7 @@ class BaseTestCase(unittest.TestCase):
|
||||||
""" Pass-through method to unittest.TestCase.assertRaisesRegex that applies re.escape() to the passed
|
""" Pass-through method to unittest.TestCase.assertRaisesRegex that applies re.escape() to the passed
|
||||||
`expected_regex`. This is useful to automatically escape all file paths that might be present in the regex.
|
`expected_regex`. This is useful to automatically escape all file paths that might be present in the regex.
|
||||||
"""
|
"""
|
||||||
return super(BaseTestCase, self).assertRaisesRegex(expected_exception, re.escape(expected_regex),
|
return super().assertRaisesRegex(expected_exception, re.escape(expected_regex), *args, **kwargs)
|
||||||
*args, **kwargs)
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def assertRaisesMessage(self, expected_exception, expected_msg): # pylint: disable=invalid-name
|
def assertRaisesMessage(self, expected_exception, expected_msg): # pylint: disable=invalid-name
|
||||||
|
@ -156,17 +132,17 @@ class BaseTestCase(unittest.TestCase):
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
except expected_exception as exc:
|
except expected_exception as exc:
|
||||||
exception_msg = ustr(exc)
|
exception_msg = str(exc)
|
||||||
if exception_msg != expected_msg:
|
if exception_msg != expected_msg:
|
||||||
error = u"Right exception, wrong message:\n got: {0}\n expected: {1}"
|
error = f"Right exception, wrong message:\n got: {exception_msg}\n expected: {expected_msg}"
|
||||||
raise self.fail(error.format(exception_msg, expected_msg))
|
raise self.fail(error)
|
||||||
# else: everything is fine, just return
|
# else: everything is fine, just return
|
||||||
return
|
return
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise self.fail(u"Expected '{0}' got '{1}'".format(expected_exception.__name__, exc.__class__.__name__))
|
raise self.fail(f"Expected '{expected_exception.__name__}' got '{exc.__class__.__name__}'")
|
||||||
|
|
||||||
# No exception raised while we expected one
|
# No exception raised while we expected one
|
||||||
raise self.fail("Expected to raise {0}, didn't get an exception at all".format(expected_exception.__name__))
|
raise self.fail(f"Expected to raise {expected_exception.__name__}, didn't get an exception at all")
|
||||||
|
|
||||||
def object_equality_test(self, obj, attr_list, ctor_kwargs=None):
|
def object_equality_test(self, obj, attr_list, ctor_kwargs=None):
|
||||||
""" Helper function to easily implement object equality tests.
|
""" Helper function to easily implement object equality tests.
|
||||||
|
@ -190,9 +166,9 @@ class BaseTestCase(unittest.TestCase):
|
||||||
self.assertEqual(obj, clone)
|
self.assertEqual(obj, clone)
|
||||||
|
|
||||||
# Change attribute and assert objects are different (via both attribute set and ctor)
|
# Change attribute and assert objects are different (via both attribute set and ctor)
|
||||||
setattr(clone, attr, u"föo")
|
setattr(clone, attr, "föo")
|
||||||
self.assertNotEqual(obj, clone)
|
self.assertNotEqual(obj, clone)
|
||||||
attr_kwargs_copy[attr] = u"föo"
|
attr_kwargs_copy[attr] = "föo"
|
||||||
|
|
||||||
self.assertNotEqual(obj, obj.__class__(**attr_kwargs_copy))
|
self.assertNotEqual(obj, obj.__class__(**attr_kwargs_copy))
|
||||||
|
|
||||||
|
@ -205,4 +181,4 @@ class LogCapture(logging.Handler):
|
||||||
self.messages = []
|
self.messages = []
|
||||||
|
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
self.messages.append(ustr(self.format(record)))
|
self.messages.append(self.format(record))
|
||||||
|
|
|
@ -8,21 +8,11 @@ import platform
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
|
|
||||||
try:
|
from io import StringIO
|
||||||
# python 2.x
|
|
||||||
from StringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from io import StringIO # pylint: disable=ungrouped-imports
|
|
||||||
|
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
try:
|
from unittest.mock import patch
|
||||||
# python 2.x
|
|
||||||
from mock import patch
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
|
|
||||||
|
|
||||||
from gitlint.shell import CommandNotFound
|
from gitlint.shell import CommandNotFound
|
||||||
|
|
||||||
|
@ -59,7 +49,7 @@ class CLITests(BaseTestCase):
|
||||||
def test_version(self):
|
def test_version(self):
|
||||||
""" Test for --version option """
|
""" Test for --version option """
|
||||||
result = self.cli.invoke(cli.cli, ["--version"])
|
result = self.cli.invoke(cli.cli, ["--version"])
|
||||||
self.assertEqual(result.output.split("\n")[0], "cli, version {0}".format(__version__))
|
self.assertEqual(result.output.split("\n")[0], f"cli, version {__version__}")
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=False)
|
@patch('gitlint.cli.get_stdin_data', return_value=False)
|
||||||
@patch('gitlint.git.sh')
|
@patch('gitlint.git.sh')
|
||||||
|
@ -67,11 +57,11 @@ class CLITests(BaseTestCase):
|
||||||
""" Test for basic simple linting functionality """
|
""" Test for basic simple linting functionality """
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
"6f29bf81a8322a04071bb794666e48c443a90360",
|
"6f29bf81a8322a04071bb794666e48c443a90360",
|
||||||
u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
||||||
u"commït-title\n\ncommït-body",
|
"commït-title\n\ncommït-body",
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"commit-1-branch-1\ncommit-1-branch-2\n",
|
"commit-1-branch-1\ncommit-1-branch-2\n",
|
||||||
u"file1.txt\npåth/to/file2.txt\n"
|
"file1.txt\npåth/to/file2.txt\n"
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
|
@ -89,21 +79,21 @@ class CLITests(BaseTestCase):
|
||||||
"25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
|
"25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
|
||||||
"4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
|
"4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
|
||||||
# git log --pretty <FORMAT> <SHA>
|
# git log --pretty <FORMAT> <SHA>
|
||||||
u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
||||||
u"commït-title1\n\ncommït-body1",
|
"commït-title1\n\ncommït-body1",
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
|
"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
|
||||||
u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
|
"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
|
||||||
# git log --pretty <FORMAT> <SHA>
|
# git log --pretty <FORMAT> <SHA>
|
||||||
u"test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n"
|
"test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n"
|
||||||
u"commït-title2\n\ncommït-body2",
|
"commït-title2\n\ncommït-body2",
|
||||||
u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
|
"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
|
||||||
u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
|
"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
|
||||||
# git log --pretty <FORMAT> <SHA>
|
# git log --pretty <FORMAT> <SHA>
|
||||||
u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n"
|
"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n"
|
||||||
u"commït-title3\n\ncommït-body3",
|
"commït-title3\n\ncommït-body3",
|
||||||
u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
|
"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
|
||||||
u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
|
"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
|
@ -122,21 +112,21 @@ class CLITests(BaseTestCase):
|
||||||
"25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
|
"25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
|
||||||
"4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
|
"4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
|
||||||
# git log --pretty <FORMAT> <SHA>
|
# git log --pretty <FORMAT> <SHA>
|
||||||
u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
||||||
u"commït-title1\n\ncommït-body1",
|
"commït-title1\n\ncommït-body1",
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
|
"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
|
||||||
u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
|
"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
|
||||||
# git log --pretty <FORMAT> <SHA>
|
# git log --pretty <FORMAT> <SHA>
|
||||||
u"test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n"
|
"test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n"
|
||||||
u"commït-title2.\n\ncommït-body2\ngitlint-ignore: T3\n",
|
"commït-title2.\n\ncommït-body2\ngitlint-ignore: T3\n",
|
||||||
u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
|
"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
|
||||||
u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
|
"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
|
||||||
# git log --pretty <FORMAT> <SHA>
|
# git log --pretty <FORMAT> <SHA>
|
||||||
u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n"
|
"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n"
|
||||||
u"commït-title3.\n\ncommït-body3",
|
"commït-title3.\n\ncommït-body3",
|
||||||
u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
|
"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
|
||||||
u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
|
"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
|
@ -157,24 +147,24 @@ class CLITests(BaseTestCase):
|
||||||
"25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
|
"25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
|
||||||
"4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
|
"4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
|
||||||
# git log --pretty <FORMAT> <SHA>
|
# git log --pretty <FORMAT> <SHA>
|
||||||
u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
||||||
u"commït-title1\n\ncommït-body1",
|
"commït-title1\n\ncommït-body1",
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
|
"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
|
||||||
u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
|
"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
|
||||||
# git log --pretty <FORMAT> <SHA>
|
# git log --pretty <FORMAT> <SHA>
|
||||||
u"test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n"
|
"test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n"
|
||||||
# Normally T3 violation (trailing punctuation), but this commit is ignored because of
|
# Normally T3 violation (trailing punctuation), but this commit is ignored because of
|
||||||
# config below
|
# config below
|
||||||
u"commït-title2.\n\ncommït-body2\n",
|
"commït-title2.\n\ncommït-body2\n",
|
||||||
u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
|
"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
|
||||||
u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
|
"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
|
||||||
# git log --pretty <FORMAT> <SHA>
|
# git log --pretty <FORMAT> <SHA>
|
||||||
u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n"
|
"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n"
|
||||||
# Normally T1 and B5 violations, now only T1 because we're ignoring B5 in config below
|
# Normally T1 and B5 violations, now only T1 because we're ignoring B5 in config below
|
||||||
u"commït-title3.\n\ncommït-body3 foo",
|
"commït-title3.\n\ncommït-body3 foo",
|
||||||
u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
|
"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
|
||||||
u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
|
"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
|
@ -183,9 +173,9 @@ class CLITests(BaseTestCase):
|
||||||
# We expect that the second commit has no failures because of it matching against I1.regex
|
# We expect that the second commit has no failures because of it matching against I1.regex
|
||||||
# Because we do test for the 3th commit to return violations, this test also ensures that a unique
|
# Because we do test for the 3th commit to return violations, this test also ensures that a unique
|
||||||
# config object is passed to each commit lint call
|
# config object is passed to each commit lint call
|
||||||
expected = (u"Commit 6f29bf81a8:\n"
|
expected = ("Commit 6f29bf81a8:\n"
|
||||||
u'3: B5 Body message is too short (12<20): "commït-body1"\n\n'
|
u'3: B5 Body message is too short (12<20): "commït-body1"\n\n'
|
||||||
u"Commit 4da2656b0d:\n"
|
"Commit 4da2656b0d:\n"
|
||||||
u'1: T3 Title has trailing punctuation (.): "commït-title3."\n')
|
u'1: T3 Title has trailing punctuation (.): "commït-title3."\n')
|
||||||
self.assertEqual(stderr.getvalue(), expected)
|
self.assertEqual(stderr.getvalue(), expected)
|
||||||
self.assertEqual(result.exit_code, 2)
|
self.assertEqual(result.exit_code, 2)
|
||||||
|
@ -218,11 +208,11 @@ class CLITests(BaseTestCase):
|
||||||
""" Test for ignoring stdin when --ignore-stdin flag is enabled"""
|
""" Test for ignoring stdin when --ignore-stdin flag is enabled"""
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
"6f29bf81a8322a04071bb794666e48c443a90360",
|
"6f29bf81a8322a04071bb794666e48c443a90360",
|
||||||
u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
||||||
u"commït-title\n\ncommït-body",
|
"commït-title\n\ncommït-body",
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
|
"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
|
||||||
u"file1.txt\npåth/to/file2.txt\n" # git diff-tree
|
"file1.txt\npåth/to/file2.txt\n" # git diff-tree
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
|
@ -240,11 +230,11 @@ class CLITests(BaseTestCase):
|
||||||
""" Test for ignoring stdin when --ignore-stdin flag is enabled"""
|
""" Test for ignoring stdin when --ignore-stdin flag is enabled"""
|
||||||
|
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"föo user\n", # git config --get user.name
|
"föo user\n", # git config --get user.name
|
||||||
u"föo@bar.com\n", # git config --get user.email
|
"föo@bar.com\n", # git config --get user.email
|
||||||
u"my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch)
|
"my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch)
|
||||||
u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
|
"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
|
@ -263,17 +253,17 @@ class CLITests(BaseTestCase):
|
||||||
""" Test for ignoring stdin when --ignore-stdin flag is enabled"""
|
""" Test for ignoring stdin when --ignore-stdin flag is enabled"""
|
||||||
|
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"föo user\n", # git config --get user.name
|
"föo user\n", # git config --get user.name
|
||||||
u"föo@bar.com\n", # git config --get user.email
|
"föo@bar.com\n", # git config --get user.email
|
||||||
u"my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch)
|
"my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch)
|
||||||
u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
|
"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
|
||||||
]
|
]
|
||||||
|
|
||||||
with self.tempdir() as tmpdir:
|
with self.tempdir() as tmpdir:
|
||||||
msg_filename = os.path.join(tmpdir, "msg")
|
msg_filename = os.path.join(tmpdir, "msg")
|
||||||
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
|
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
|
||||||
f.write(u"WIP: msg-filename tïtle\n")
|
f.write("WIP: msg-filename tïtle\n")
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
result = self.cli.invoke(cli.cli, ["--debug", "--staged", "--msg-filename", msg_filename])
|
result = self.cli.invoke(cli.cli, ["--debug", "--staged", "--msg-filename", msg_filename])
|
||||||
|
@ -289,17 +279,17 @@ class CLITests(BaseTestCase):
|
||||||
def test_lint_staged_negative(self, _):
|
def test_lint_staged_negative(self, _):
|
||||||
result = self.cli.invoke(cli.cli, ["--staged"])
|
result = self.cli.invoke(cli.cli, ["--staged"])
|
||||||
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
||||||
self.assertEqual(result.output, (u"Error: The 'staged' option (--staged) can only be used when using "
|
self.assertEqual(result.output, ("Error: The 'staged' option (--staged) can only be used when using "
|
||||||
u"'--msg-filename' or when piping data to gitlint via stdin.\n"))
|
"'--msg-filename' or when piping data to gitlint via stdin.\n"))
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=False)
|
@patch('gitlint.cli.get_stdin_data', return_value=False)
|
||||||
def test_msg_filename(self, _):
|
def test_msg_filename(self, _):
|
||||||
expected_output = u"3: B6 Body message is missing\n"
|
expected_output = "3: B6 Body message is missing\n"
|
||||||
|
|
||||||
with self.tempdir() as tmpdir:
|
with self.tempdir() as tmpdir:
|
||||||
msg_filename = os.path.join(tmpdir, "msg")
|
msg_filename = os.path.join(tmpdir, "msg")
|
||||||
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
|
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
|
||||||
f.write(u"Commït title\n")
|
f.write("Commït title\n")
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename])
|
result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename])
|
||||||
|
@ -307,7 +297,7 @@ class CLITests(BaseTestCase):
|
||||||
self.assertEqual(result.exit_code, 1)
|
self.assertEqual(result.exit_code, 1)
|
||||||
self.assertEqual(result.output, "")
|
self.assertEqual(result.output, "")
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tïtle \n")
|
@patch('gitlint.cli.get_stdin_data', return_value="WIP: tïtle \n")
|
||||||
def test_silent_mode(self, _):
|
def test_silent_mode(self, _):
|
||||||
""" Test for --silent option """
|
""" Test for --silent option """
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
|
@ -316,7 +306,7 @@ class CLITests(BaseTestCase):
|
||||||
self.assertEqual(result.exit_code, 3)
|
self.assertEqual(result.exit_code, 3)
|
||||||
self.assertEqual(result.output, "")
|
self.assertEqual(result.output, "")
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tïtle \n")
|
@patch('gitlint.cli.get_stdin_data', return_value="WIP: tïtle \n")
|
||||||
def test_verbosity(self, _):
|
def test_verbosity(self, _):
|
||||||
""" Test for --verbosity option """
|
""" Test for --verbosity option """
|
||||||
# We only test -v and -vv, more testing is really not required here
|
# We only test -v and -vv, more testing is really not required here
|
||||||
|
@ -333,7 +323,7 @@ class CLITests(BaseTestCase):
|
||||||
"3: B6 Body message is missing\n"
|
"3: B6 Body message is missing\n"
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
result = self.cli.invoke(cli.cli, ["-vv"], input=u"WIP: tïtle \n")
|
result = self.cli.invoke(cli.cli, ["-vv"], input="WIP: tïtle \n")
|
||||||
self.assertEqual(stderr.getvalue(), expected_output)
|
self.assertEqual(stderr.getvalue(), expected_output)
|
||||||
self.assertEqual(result.exit_code, 3)
|
self.assertEqual(result.exit_code, 3)
|
||||||
self.assertEqual(result.output, "")
|
self.assertEqual(result.output, "")
|
||||||
|
@ -355,19 +345,19 @@ class CLITests(BaseTestCase):
|
||||||
"25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n"
|
"25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n"
|
||||||
"4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
|
"4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
|
||||||
# git log --pretty <FORMAT> <SHA>
|
# git log --pretty <FORMAT> <SHA>
|
||||||
u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00abc\n"
|
"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00abc\n"
|
||||||
u"commït-title1\n\ncommït-body1",
|
"commït-title1\n\ncommït-body1",
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
|
"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
|
||||||
u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
|
"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
|
||||||
u"test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00abc\n"
|
"test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00abc\n"
|
||||||
u"commït-title2.\n\ncommït-body2",
|
"commït-title2.\n\ncommït-body2",
|
||||||
u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
|
"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
|
||||||
u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
|
"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
|
||||||
u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00abc\n"
|
"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00abc\n"
|
||||||
u"föobar\nbar",
|
"föobar\nbar",
|
||||||
u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
|
"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
|
||||||
u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
|
"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
|
@ -387,14 +377,14 @@ class CLITests(BaseTestCase):
|
||||||
expected_logs = self.get_expected('cli/test_cli/test_debug_1', expected_kwargs)
|
expected_logs = self.get_expected('cli/test_cli/test_debug_1', expected_kwargs)
|
||||||
self.assert_logged(expected_logs)
|
self.assert_logged(expected_logs)
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n")
|
@patch('gitlint.cli.get_stdin_data', return_value="Test tïtle\n")
|
||||||
def test_extra_path(self, _):
|
def test_extra_path(self, _):
|
||||||
""" Test for --extra-path flag """
|
""" Test for --extra-path flag """
|
||||||
# Test extra-path pointing to a directory
|
# Test extra-path pointing to a directory
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
extra_path = self.get_sample_path("user_rules")
|
extra_path = self.get_sample_path("user_rules")
|
||||||
result = self.cli.invoke(cli.cli, ["--extra-path", extra_path])
|
result = self.cli.invoke(cli.cli, ["--extra-path", extra_path])
|
||||||
expected_output = u"1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \
|
expected_output = "1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \
|
||||||
"3: B6 Body message is missing\n"
|
"3: B6 Body message is missing\n"
|
||||||
self.assertEqual(stderr.getvalue(), expected_output)
|
self.assertEqual(stderr.getvalue(), expected_output)
|
||||||
self.assertEqual(result.exit_code, 2)
|
self.assertEqual(result.exit_code, 2)
|
||||||
|
@ -403,12 +393,12 @@ class CLITests(BaseTestCase):
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
extra_path = self.get_sample_path(os.path.join("user_rules", "my_commit_rules.py"))
|
extra_path = self.get_sample_path(os.path.join("user_rules", "my_commit_rules.py"))
|
||||||
result = self.cli.invoke(cli.cli, ["--extra-path", extra_path])
|
result = self.cli.invoke(cli.cli, ["--extra-path", extra_path])
|
||||||
expected_output = u"1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \
|
expected_output = "1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \
|
||||||
"3: B6 Body message is missing\n"
|
"3: B6 Body message is missing\n"
|
||||||
self.assertEqual(stderr.getvalue(), expected_output)
|
self.assertEqual(stderr.getvalue(), expected_output)
|
||||||
self.assertEqual(result.exit_code, 2)
|
self.assertEqual(result.exit_code, 2)
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n\nMy body that is long enough")
|
@patch('gitlint.cli.get_stdin_data', return_value="Test tïtle\n\nMy body that is long enough")
|
||||||
def test_contrib(self, _):
|
def test_contrib(self, _):
|
||||||
# Test enabled contrib rules
|
# Test enabled contrib rules
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
|
@ -417,13 +407,13 @@ class CLITests(BaseTestCase):
|
||||||
self.assertEqual(stderr.getvalue(), expected_output)
|
self.assertEqual(stderr.getvalue(), expected_output)
|
||||||
self.assertEqual(result.exit_code, 3)
|
self.assertEqual(result.exit_code, 3)
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n")
|
@patch('gitlint.cli.get_stdin_data', return_value="Test tïtle\n")
|
||||||
def test_contrib_negative(self, _):
|
def test_contrib_negative(self, _):
|
||||||
result = self.cli.invoke(cli.cli, ["--contrib", u"föobar,CC1"])
|
result = self.cli.invoke(cli.cli, ["--contrib", "föobar,CC1"])
|
||||||
self.assertEqual(result.output, u"Config Error: No contrib rule with id or name 'föobar' found.\n")
|
self.assertEqual(result.output, "Config Error: No contrib rule with id or name 'föobar' found.\n")
|
||||||
self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE)
|
self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE)
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tëst")
|
@patch('gitlint.cli.get_stdin_data', return_value="WIP: tëst")
|
||||||
def test_config_file(self, _):
|
def test_config_file(self, _):
|
||||||
""" Test for --config option """
|
""" Test for --config option """
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
|
@ -438,16 +428,14 @@ class CLITests(BaseTestCase):
|
||||||
# Directory as config file
|
# Directory as config file
|
||||||
config_path = self.get_sample_path("config")
|
config_path = self.get_sample_path("config")
|
||||||
result = self.cli.invoke(cli.cli, ["--config", config_path])
|
result = self.cli.invoke(cli.cli, ["--config", config_path])
|
||||||
expected_string = u"Error: Invalid value for \"-C\" / \"--config\": File \"{0}\" is a directory.".format(
|
expected_string = f"Error: Invalid value for '-C' / '--config': File '{config_path}' is a directory."
|
||||||
config_path)
|
|
||||||
self.assertEqual(result.output.split("\n")[3], expected_string)
|
self.assertEqual(result.output.split("\n")[3], expected_string)
|
||||||
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
||||||
|
|
||||||
# Non existing file
|
# Non existing file
|
||||||
config_path = self.get_sample_path(u"föo")
|
config_path = self.get_sample_path("föo")
|
||||||
result = self.cli.invoke(cli.cli, ["--config", config_path])
|
result = self.cli.invoke(cli.cli, ["--config", config_path])
|
||||||
expected_string = u"Error: Invalid value for \"-C\" / \"--config\": File \"{0}\" does not exist.".format(
|
expected_string = f"Error: Invalid value for '-C' / '--config': File '{config_path}' does not exist."
|
||||||
config_path)
|
|
||||||
self.assertEqual(result.output.split("\n")[3], expected_string)
|
self.assertEqual(result.output.split("\n")[3], expected_string)
|
||||||
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
||||||
|
|
||||||
|
@ -471,37 +459,37 @@ class CLITests(BaseTestCase):
|
||||||
def test_target_negative(self):
|
def test_target_negative(self):
|
||||||
""" Negative test for the --target option """
|
""" Negative test for the --target option """
|
||||||
# try setting a non-existing target
|
# try setting a non-existing target
|
||||||
result = self.cli.invoke(cli.cli, ["--target", u"/föo/bar"])
|
result = self.cli.invoke(cli.cli, ["--target", "/föo/bar"])
|
||||||
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
||||||
expected_msg = u"Error: Invalid value for \"--target\": Directory \"/föo/bar\" does not exist."
|
expected_msg = "Error: Invalid value for '--target': Directory '/föo/bar' does not exist."
|
||||||
self.assertEqual(result.output.split("\n")[3], expected_msg)
|
self.assertEqual(result.output.split("\n")[3], expected_msg)
|
||||||
|
|
||||||
# try setting a file as target
|
# try setting a file as target
|
||||||
target_path = self.get_sample_path(os.path.join("config", "gitlintconfig"))
|
target_path = self.get_sample_path(os.path.join("config", "gitlintconfig"))
|
||||||
result = self.cli.invoke(cli.cli, ["--target", target_path])
|
result = self.cli.invoke(cli.cli, ["--target", target_path])
|
||||||
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
||||||
expected_msg = u"Error: Invalid value for \"--target\": Directory \"{0}\" is a file.".format(target_path)
|
expected_msg = f"Error: Invalid value for '--target': Directory '{target_path}' is a file."
|
||||||
self.assertEqual(result.output.split("\n")[3], expected_msg)
|
self.assertEqual(result.output.split("\n")[3], expected_msg)
|
||||||
|
|
||||||
@patch('gitlint.config.LintConfigGenerator.generate_config')
|
@patch('gitlint.config.LintConfigGenerator.generate_config')
|
||||||
def test_generate_config(self, generate_config):
|
def test_generate_config(self, generate_config):
|
||||||
""" Test for the generate-config subcommand """
|
""" Test for the generate-config subcommand """
|
||||||
result = self.cli.invoke(cli.cli, ["generate-config"], input=u"tëstfile\n")
|
result = self.cli.invoke(cli.cli, ["generate-config"], input="tëstfile\n")
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
expected_msg = u"Please specify a location for the sample gitlint config file [.gitlint]: tëstfile\n" + \
|
expected_msg = "Please specify a location for the sample gitlint config file [.gitlint]: tëstfile\n" + \
|
||||||
u"Successfully generated {0}\n".format(os.path.realpath(u"tëstfile"))
|
f"Successfully generated {os.path.realpath('tëstfile')}\n"
|
||||||
self.assertEqual(result.output, expected_msg)
|
self.assertEqual(result.output, expected_msg)
|
||||||
generate_config.assert_called_once_with(os.path.realpath(u"tëstfile"))
|
generate_config.assert_called_once_with(os.path.realpath("tëstfile"))
|
||||||
|
|
||||||
def test_generate_config_negative(self):
|
def test_generate_config_negative(self):
|
||||||
""" Negative test for the generate-config subcommand """
|
""" Negative test for the generate-config subcommand """
|
||||||
# Non-existing directory
|
# Non-existing directory
|
||||||
fake_dir = os.path.abspath(u"/föo")
|
fake_dir = os.path.abspath("/föo")
|
||||||
fake_path = os.path.join(fake_dir, u"bar")
|
fake_path = os.path.join(fake_dir, "bar")
|
||||||
result = self.cli.invoke(cli.cli, ["generate-config"], input=fake_path)
|
result = self.cli.invoke(cli.cli, ["generate-config"], input=fake_path)
|
||||||
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
||||||
expected_msg = (u"Please specify a location for the sample gitlint config file [.gitlint]: {0}\n"
|
expected_msg = f"Please specify a location for the sample gitlint config file [.gitlint]: {fake_path}\n" + \
|
||||||
+ u"Error: Directory '{1}' does not exist.\n").format(fake_path, fake_dir)
|
f"Error: Directory '{fake_dir}' does not exist.\n"
|
||||||
self.assertEqual(result.output, expected_msg)
|
self.assertEqual(result.output, expected_msg)
|
||||||
|
|
||||||
# Existing file
|
# Existing file
|
||||||
|
@ -509,8 +497,8 @@ class CLITests(BaseTestCase):
|
||||||
result = self.cli.invoke(cli.cli, ["generate-config"], input=sample_path)
|
result = self.cli.invoke(cli.cli, ["generate-config"], input=sample_path)
|
||||||
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
||||||
expected_msg = "Please specify a location for the sample gitlint " + \
|
expected_msg = "Please specify a location for the sample gitlint " + \
|
||||||
"config file [.gitlint]: {0}\n".format(sample_path) + \
|
f"config file [.gitlint]: {sample_path}\n" + \
|
||||||
"Error: File \"{0}\" already exists.\n".format(sample_path)
|
f"Error: File \"{sample_path}\" already exists.\n"
|
||||||
self.assertEqual(result.output, expected_msg)
|
self.assertEqual(result.output, expected_msg)
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=False)
|
@patch('gitlint.cli.get_stdin_data', return_value=False)
|
||||||
|
@ -528,10 +516,10 @@ class CLITests(BaseTestCase):
|
||||||
sh.git.side_effect = lambda *_args, **_kwargs: ""
|
sh.git.side_effect = lambda *_args, **_kwargs: ""
|
||||||
result = self.cli.invoke(cli.cli, ["--commits", "master...HEAD"])
|
result = self.cli.invoke(cli.cli, ["--commits", "master...HEAD"])
|
||||||
|
|
||||||
self.assert_log_contains(u"DEBUG: gitlint.cli No commits in range \"master...HEAD\"")
|
self.assert_log_contains("DEBUG: gitlint.cli No commits in range \"master...HEAD\"")
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tëst tïtle")
|
@patch('gitlint.cli.get_stdin_data', return_value="WIP: tëst tïtle")
|
||||||
def test_named_rules(self, _):
|
def test_named_rules(self, _):
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
config_path = self.get_sample_path(os.path.join("config", "named-rules"))
|
config_path = self.get_sample_path(os.path.join("config", "named-rules"))
|
||||||
|
|
|
@ -1,28 +1,18 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
from io import StringIO
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
try:
|
from unittest.mock import patch
|
||||||
# python 2.x
|
|
||||||
from StringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from io import StringIO # pylint: disable=ungrouped-imports
|
|
||||||
|
|
||||||
try:
|
|
||||||
# python 2.x
|
|
||||||
from mock import patch
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
|
|
||||||
|
|
||||||
from gitlint.tests.base import BaseTestCase
|
from gitlint.tests.base import BaseTestCase
|
||||||
from gitlint import cli
|
from gitlint import cli
|
||||||
from gitlint import hooks
|
from gitlint import hooks
|
||||||
from gitlint import config
|
from gitlint import config
|
||||||
|
from gitlint.shell import ErrorReturnCode
|
||||||
|
|
||||||
from gitlint.utils import DEFAULT_ENCODING
|
from gitlint.utils import DEFAULT_ENCODING
|
||||||
|
|
||||||
|
@ -45,12 +35,12 @@ class CLIHookTests(BaseTestCase):
|
||||||
self.git_version_path.stop()
|
self.git_version_path.stop()
|
||||||
|
|
||||||
@patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
|
@patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
|
||||||
@patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join(u"/hür", u"dur"))
|
@patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join("/hür", "dur"))
|
||||||
def test_install_hook(self, _, install_hook):
|
def test_install_hook(self, _, install_hook):
|
||||||
""" Test for install-hook subcommand """
|
""" Test for install-hook subcommand """
|
||||||
result = self.cli.invoke(cli.cli, ["install-hook"])
|
result = self.cli.invoke(cli.cli, ["install-hook"])
|
||||||
expected_path = os.path.join(u"/hür", u"dur", hooks.COMMIT_MSG_HOOK_DST_PATH)
|
expected_path = os.path.join("/hür", "dur", hooks.COMMIT_MSG_HOOK_DST_PATH)
|
||||||
expected = u"Successfully installed gitlint commit-msg hook in {0}\n".format(expected_path)
|
expected = f"Successfully installed gitlint commit-msg hook in {expected_path}\n"
|
||||||
self.assertEqual(result.output, expected)
|
self.assertEqual(result.output, expected)
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
expected_config = config.LintConfig()
|
expected_config = config.LintConfig()
|
||||||
|
@ -58,12 +48,12 @@ class CLIHookTests(BaseTestCase):
|
||||||
install_hook.assert_called_once_with(expected_config)
|
install_hook.assert_called_once_with(expected_config)
|
||||||
|
|
||||||
@patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
|
@patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
|
||||||
@patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join(u"/hür", u"dur"))
|
@patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join("/hür", "dur"))
|
||||||
def test_install_hook_target(self, _, install_hook):
|
def test_install_hook_target(self, _, install_hook):
|
||||||
""" Test for install-hook subcommand with a specific --target option specified """
|
""" Test for install-hook subcommand with a specific --target option specified """
|
||||||
# Specified target
|
# Specified target
|
||||||
result = self.cli.invoke(cli.cli, ["--target", self.SAMPLES_DIR, "install-hook"])
|
result = self.cli.invoke(cli.cli, ["--target", self.SAMPLES_DIR, "install-hook"])
|
||||||
expected_path = os.path.join(u"/hür", u"dur", hooks.COMMIT_MSG_HOOK_DST_PATH)
|
expected_path = os.path.join("/hür", "dur", hooks.COMMIT_MSG_HOOK_DST_PATH)
|
||||||
expected = "Successfully installed gitlint commit-msg hook in %s\n" % expected_path
|
expected = "Successfully installed gitlint commit-msg hook in %s\n" % expected_path
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
self.assertEqual(result.output, expected)
|
self.assertEqual(result.output, expected)
|
||||||
|
@ -72,40 +62,40 @@ class CLIHookTests(BaseTestCase):
|
||||||
expected_config.target = self.SAMPLES_DIR
|
expected_config.target = self.SAMPLES_DIR
|
||||||
install_hook.assert_called_once_with(expected_config)
|
install_hook.assert_called_once_with(expected_config)
|
||||||
|
|
||||||
@patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook', side_effect=hooks.GitHookInstallerError(u"tëst"))
|
@patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook', side_effect=hooks.GitHookInstallerError("tëst"))
|
||||||
def test_install_hook_negative(self, install_hook):
|
def test_install_hook_negative(self, install_hook):
|
||||||
""" Negative test for install-hook subcommand """
|
""" Negative test for install-hook subcommand """
|
||||||
result = self.cli.invoke(cli.cli, ["install-hook"])
|
result = self.cli.invoke(cli.cli, ["install-hook"])
|
||||||
self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
|
self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
|
||||||
self.assertEqual(result.output, u"tëst\n")
|
self.assertEqual(result.output, "tëst\n")
|
||||||
expected_config = config.LintConfig()
|
expected_config = config.LintConfig()
|
||||||
expected_config.target = os.path.realpath(os.getcwd())
|
expected_config.target = os.path.realpath(os.getcwd())
|
||||||
install_hook.assert_called_once_with(expected_config)
|
install_hook.assert_called_once_with(expected_config)
|
||||||
|
|
||||||
@patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook')
|
@patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook')
|
||||||
@patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join(u"/hür", u"dur"))
|
@patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join("/hür", "dur"))
|
||||||
def test_uninstall_hook(self, _, uninstall_hook):
|
def test_uninstall_hook(self, _, uninstall_hook):
|
||||||
""" Test for uninstall-hook subcommand """
|
""" Test for uninstall-hook subcommand """
|
||||||
result = self.cli.invoke(cli.cli, ["uninstall-hook"])
|
result = self.cli.invoke(cli.cli, ["uninstall-hook"])
|
||||||
expected_path = os.path.join(u"/hür", u"dur", hooks.COMMIT_MSG_HOOK_DST_PATH)
|
expected_path = os.path.join("/hür", "dur", hooks.COMMIT_MSG_HOOK_DST_PATH)
|
||||||
expected = u"Successfully uninstalled gitlint commit-msg hook from {0}\n".format(expected_path)
|
expected = f"Successfully uninstalled gitlint commit-msg hook from {expected_path}\n"
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
self.assertEqual(result.output, expected)
|
self.assertEqual(result.output, expected)
|
||||||
expected_config = config.LintConfig()
|
expected_config = config.LintConfig()
|
||||||
expected_config.target = os.path.realpath(os.getcwd())
|
expected_config.target = os.path.realpath(os.getcwd())
|
||||||
uninstall_hook.assert_called_once_with(expected_config)
|
uninstall_hook.assert_called_once_with(expected_config)
|
||||||
|
|
||||||
@patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook', side_effect=hooks.GitHookInstallerError(u"tëst"))
|
@patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook', side_effect=hooks.GitHookInstallerError("tëst"))
|
||||||
def test_uninstall_hook_negative(self, uninstall_hook):
|
def test_uninstall_hook_negative(self, uninstall_hook):
|
||||||
""" Negative test for uninstall-hook subcommand """
|
""" Negative test for uninstall-hook subcommand """
|
||||||
result = self.cli.invoke(cli.cli, ["uninstall-hook"])
|
result = self.cli.invoke(cli.cli, ["uninstall-hook"])
|
||||||
self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
|
self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
|
||||||
self.assertEqual(result.output, u"tëst\n")
|
self.assertEqual(result.output, "tëst\n")
|
||||||
expected_config = config.LintConfig()
|
expected_config = config.LintConfig()
|
||||||
expected_config.target = os.path.realpath(os.getcwd())
|
expected_config.target = os.path.realpath(os.getcwd())
|
||||||
uninstall_hook.assert_called_once_with(expected_config)
|
uninstall_hook.assert_called_once_with(expected_config)
|
||||||
|
|
||||||
def test_hook_no_tty(self):
|
def test_run_hook_no_tty(self):
|
||||||
""" Test for run-hook subcommand.
|
""" Test for run-hook subcommand.
|
||||||
When no TTY is available (like is the case for this test), the hook will abort after the first check.
|
When no TTY is available (like is the case for this test), the hook will abort after the first check.
|
||||||
"""
|
"""
|
||||||
|
@ -119,9 +109,9 @@ class CLIHookTests(BaseTestCase):
|
||||||
# check the output which indirectly proves the same thing.
|
# check the output which indirectly proves the same thing.
|
||||||
|
|
||||||
with self.tempdir() as tmpdir:
|
with self.tempdir() as tmpdir:
|
||||||
msg_filename = os.path.join(tmpdir, u"hür")
|
msg_filename = os.path.join(tmpdir, "hür")
|
||||||
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
|
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
|
||||||
f.write(u"WIP: tïtle\n")
|
f.write("WIP: tïtle\n")
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"])
|
result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"])
|
||||||
|
@ -132,12 +122,12 @@ class CLIHookTests(BaseTestCase):
|
||||||
self.assertEqual(result.exit_code, 1)
|
self.assertEqual(result.exit_code, 1)
|
||||||
|
|
||||||
@patch('gitlint.cli.shell')
|
@patch('gitlint.cli.shell')
|
||||||
def test_hook_edit(self, shell):
|
def test_run_hook_edit(self, shell):
|
||||||
""" Test for run-hook subcommand, answering 'e(dit)' after commit-hook """
|
""" Test for run-hook subcommand, answering 'e(dit)' after commit-hook """
|
||||||
|
|
||||||
set_editors = [None, u"myeditor"]
|
set_editors = [None, "myeditor"]
|
||||||
expected_editors = [u"vim -n", u"myeditor"]
|
expected_editors = ["vim -n", "myeditor"]
|
||||||
commit_messages = [u"WIP: höok edit 1", u"WIP: höok edit 2"]
|
commit_messages = ["WIP: höok edit 1", "WIP: höok edit 2"]
|
||||||
|
|
||||||
for i in range(0, len(set_editors)):
|
for i in range(0, len(set_editors)):
|
||||||
if set_editors[i]:
|
if set_editors[i]:
|
||||||
|
@ -145,7 +135,7 @@ class CLIHookTests(BaseTestCase):
|
||||||
|
|
||||||
with self.patch_input(['e', 'e', 'n']):
|
with self.patch_input(['e', 'e', 'n']):
|
||||||
with self.tempdir() as tmpdir:
|
with self.tempdir() as tmpdir:
|
||||||
msg_filename = os.path.realpath(os.path.join(tmpdir, u"hür"))
|
msg_filename = os.path.realpath(os.path.join(tmpdir, "hür"))
|
||||||
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
|
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
|
||||||
f.write(commit_messages[i] + "\n")
|
f.write(commit_messages[i] + "\n")
|
||||||
|
|
||||||
|
@ -161,18 +151,17 @@ class CLIHookTests(BaseTestCase):
|
||||||
self.assertEqual(result.exit_code, 2)
|
self.assertEqual(result.exit_code, 2)
|
||||||
|
|
||||||
shell.assert_called_with(expected_editors[i] + " " + msg_filename)
|
shell.assert_called_with(expected_editors[i] + " " + msg_filename)
|
||||||
self.assert_log_contains(u"DEBUG: gitlint.cli run-hook: editing commit message")
|
self.assert_log_contains("DEBUG: gitlint.cli run-hook: editing commit message")
|
||||||
self.assert_log_contains(u"DEBUG: gitlint.cli run-hook: {0} {1}".format(expected_editors[i],
|
self.assert_log_contains(f"DEBUG: gitlint.cli run-hook: {expected_editors[i]} {msg_filename}")
|
||||||
msg_filename))
|
|
||||||
|
|
||||||
def test_hook_no(self):
|
def test_run_hook_no(self):
|
||||||
""" Test for run-hook subcommand, answering 'n(o)' after commit-hook """
|
""" Test for run-hook subcommand, answering 'n(o)' after commit-hook """
|
||||||
|
|
||||||
with self.patch_input(['n']):
|
with self.patch_input(['n']):
|
||||||
with self.tempdir() as tmpdir:
|
with self.tempdir() as tmpdir:
|
||||||
msg_filename = os.path.join(tmpdir, u"hür")
|
msg_filename = os.path.join(tmpdir, "hür")
|
||||||
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
|
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
|
||||||
f.write(u"WIP: höok no\n")
|
f.write("WIP: höok no\n")
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"])
|
result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"])
|
||||||
|
@ -184,13 +173,13 @@ class CLIHookTests(BaseTestCase):
|
||||||
self.assertEqual(result.exit_code, 2)
|
self.assertEqual(result.exit_code, 2)
|
||||||
self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message declined")
|
self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message declined")
|
||||||
|
|
||||||
def test_hook_yes(self):
|
def test_run_hook_yes(self):
|
||||||
""" Test for run-hook subcommand, answering 'y(es)' after commit-hook """
|
""" Test for run-hook subcommand, answering 'y(es)' after commit-hook """
|
||||||
with self.patch_input(['y']):
|
with self.patch_input(['y']):
|
||||||
with self.tempdir() as tmpdir:
|
with self.tempdir() as tmpdir:
|
||||||
msg_filename = os.path.join(tmpdir, u"hür")
|
msg_filename = os.path.join(tmpdir, "hür")
|
||||||
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
|
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
|
||||||
f.write(u"WIP: höok yes\n")
|
f.write("WIP: höok yes\n")
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"])
|
result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"])
|
||||||
|
@ -202,8 +191,32 @@ class CLIHookTests(BaseTestCase):
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message accepted")
|
self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message accepted")
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=u"WIP: Test hook stdin tïtle\n")
|
@patch('gitlint.cli.get_stdin_data', return_value=False)
|
||||||
def test_hook_stdin_violations(self, _):
|
@patch('gitlint.git.sh')
|
||||||
|
def test_run_hook_negative(self, sh, _):
|
||||||
|
""" Negative test for the run-hook subcommand: testing whether exceptions are correctly handled when
|
||||||
|
running `gitlint run-hook`.
|
||||||
|
"""
|
||||||
|
# GIT_CONTEXT_ERROR_CODE: git error
|
||||||
|
error_msg = b"fatal: not a git repository (or any of the parent directories): .git"
|
||||||
|
sh.git.side_effect = ErrorReturnCode("full command", b"stdout", error_msg)
|
||||||
|
result = self.cli.invoke(cli.cli, ["run-hook"])
|
||||||
|
expected = self.get_expected('cli/test_cli_hooks/test_run_hook_negative_1', {'git_repo': os.getcwd()})
|
||||||
|
self.assertEqual(result.output, expected)
|
||||||
|
self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
|
||||||
|
|
||||||
|
# USAGE_ERROR_CODE: incorrect use of gitlint
|
||||||
|
result = self.cli.invoke(cli.cli, ["--staged", "run-hook"])
|
||||||
|
self.assertEqual(result.output, self.get_expected('cli/test_cli_hooks/test_run_hook_negative_2'))
|
||||||
|
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
|
||||||
|
|
||||||
|
# CONFIG_ERROR_CODE: incorrect config. Note that this is handled before the hook even runs
|
||||||
|
result = self.cli.invoke(cli.cli, ["-c", "föo.bár=1", "run-hook"])
|
||||||
|
self.assertEqual(result.output, "Config Error: No such rule 'föo'\n")
|
||||||
|
self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE)
|
||||||
|
|
||||||
|
@patch('gitlint.cli.get_stdin_data', return_value="WIP: Test hook stdin tïtle\n")
|
||||||
|
def test_run_hook_stdin_violations(self, _):
|
||||||
""" Test for passing stdin data to run-hook, expecting some violations. Equivalent of:
|
""" Test for passing stdin data to run-hook, expecting some violations. Equivalent of:
|
||||||
$ echo "WIP: Test hook stdin tïtle" | gitlint run-hook
|
$ echo "WIP: Test hook stdin tïtle" | gitlint run-hook
|
||||||
"""
|
"""
|
||||||
|
@ -216,8 +229,8 @@ class CLIHookTests(BaseTestCase):
|
||||||
# Hook will auto-abort because we're using stdin. Abort = exit code 1
|
# Hook will auto-abort because we're using stdin. Abort = exit code 1
|
||||||
self.assertEqual(result.exit_code, 1)
|
self.assertEqual(result.exit_code, 1)
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n\nTest bödy that is long enough")
|
@patch('gitlint.cli.get_stdin_data', return_value="Test tïtle\n\nTest bödy that is long enough")
|
||||||
def test_hook_stdin_no_violations(self, _):
|
def test_run_hook_stdin_no_violations(self, _):
|
||||||
""" Test for passing stdin data to run-hook, expecting *NO* violations, Equivalent of:
|
""" Test for passing stdin data to run-hook, expecting *NO* violations, Equivalent of:
|
||||||
$ echo -e "Test tïtle\n\nTest bödy that is long enough" | gitlint run-hook
|
$ echo -e "Test tïtle\n\nTest bödy that is long enough" | gitlint run-hook
|
||||||
"""
|
"""
|
||||||
|
@ -229,8 +242,8 @@ class CLIHookTests(BaseTestCase):
|
||||||
self.assertEqual(result.output, expected_stdout)
|
self.assertEqual(result.output, expected_stdout)
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=u"WIP: Test hook config tïtle\n")
|
@patch('gitlint.cli.get_stdin_data', return_value="WIP: Test hook config tïtle\n")
|
||||||
def test_hook_config(self, _):
|
def test_run_hook_config(self, _):
|
||||||
""" Test that gitlint still respects config when running run-hook, equivalent of:
|
""" Test that gitlint still respects config when running run-hook, equivalent of:
|
||||||
$ echo "WIP: Test hook config tïtle" | gitlint -c title-max-length.line-length=5 --ignore B6 run-hook
|
$ echo "WIP: Test hook config tïtle" | gitlint -c title-max-length.line-length=5 --ignore B6 run-hook
|
||||||
"""
|
"""
|
||||||
|
@ -244,18 +257,18 @@ class CLIHookTests(BaseTestCase):
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=False)
|
@patch('gitlint.cli.get_stdin_data', return_value=False)
|
||||||
@patch('gitlint.git.sh')
|
@patch('gitlint.git.sh')
|
||||||
def test_hook_local_commit(self, sh, _):
|
def test_run_hook_local_commit(self, sh, _):
|
||||||
""" Test running the hook on the last commit-msg from the local repo, equivalent of:
|
""" Test running the hook on the last commit-msg from the local repo, equivalent of:
|
||||||
$ gitlint run-hook
|
$ gitlint run-hook
|
||||||
and then choosing 'e'
|
and then choosing 'e'
|
||||||
"""
|
"""
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
"6f29bf81a8322a04071bb794666e48c443a90360",
|
"6f29bf81a8322a04071bb794666e48c443a90360",
|
||||||
u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
||||||
u"WIP: commït-title\n\ncommït-body",
|
"WIP: commït-title\n\ncommït-body",
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"commit-1-branch-1\ncommit-1-branch-2\n",
|
"commit-1-branch-1\ncommit-1-branch-2\n",
|
||||||
u"file1.txt\npåth/to/file2.txt\n"
|
"file1.txt\npåth/to/file2.txt\n"
|
||||||
]
|
]
|
||||||
|
|
||||||
with self.patch_input(['e']):
|
with self.patch_input(['e']):
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
try:
|
from unittest.mock import patch
|
||||||
# python 2.x
|
|
||||||
from mock import patch
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
|
|
||||||
|
|
||||||
from gitlint import rules
|
from gitlint import rules
|
||||||
from gitlint.config import LintConfig, LintConfigError, LintConfigGenerator, GITLINT_CONFIG_TEMPLATE_SRC_PATH
|
from gitlint.config import LintConfig, LintConfigError, LintConfigGenerator, GITLINT_CONFIG_TEMPLATE_SRC_PATH
|
||||||
from gitlint import options
|
from gitlint import options
|
||||||
from gitlint.tests.base import BaseTestCase, ustr
|
from gitlint.tests.base import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
class LintConfigTests(BaseTestCase):
|
class LintConfigTests(BaseTestCase):
|
||||||
|
@ -29,20 +24,20 @@ class LintConfigTests(BaseTestCase):
|
||||||
config = LintConfig()
|
config = LintConfig()
|
||||||
|
|
||||||
# non-existing rule
|
# non-existing rule
|
||||||
expected_error_msg = u"No such rule 'föobar'"
|
expected_error_msg = "No such rule 'föobar'"
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
||||||
config.set_rule_option(u'föobar', u'lïne-length', 60)
|
config.set_rule_option(u'föobar', u'lïne-length', 60)
|
||||||
|
|
||||||
# non-existing option
|
# non-existing option
|
||||||
expected_error_msg = u"Rule 'title-max-length' has no option 'föobar'"
|
expected_error_msg = "Rule 'title-max-length' has no option 'föobar'"
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
||||||
config.set_rule_option('title-max-length', u'föobar', 60)
|
config.set_rule_option('title-max-length', u'föobar', 60)
|
||||||
|
|
||||||
# invalid option value
|
# invalid option value
|
||||||
expected_error_msg = u"'föo' is not a valid value for option 'title-max-length.line-length'. " + \
|
expected_error_msg = "'föo' is not a valid value for option 'title-max-length.line-length'. " + \
|
||||||
u"Option 'line-length' must be a positive integer (current value: 'föo')."
|
"Option 'line-length' must be a positive integer (current value: 'föo')."
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
||||||
config.set_rule_option('title-max-length', 'line-length', u"föo")
|
config.set_rule_option('title-max-length', 'line-length', "föo")
|
||||||
|
|
||||||
def test_set_general_option(self):
|
def test_set_general_option(self):
|
||||||
config = LintConfig()
|
config = LintConfig()
|
||||||
|
@ -117,7 +112,7 @@ class LintConfigTests(BaseTestCase):
|
||||||
actual_rule = config.rules.find_rule("contrib-title-conventional-commits")
|
actual_rule = config.rules.find_rule("contrib-title-conventional-commits")
|
||||||
self.assertTrue(actual_rule.is_contrib)
|
self.assertTrue(actual_rule.is_contrib)
|
||||||
|
|
||||||
self.assertEqual(ustr(type(actual_rule)), "<class 'conventional_commit.ConventionalCommit'>")
|
self.assertEqual(str(type(actual_rule)), "<class 'conventional_commit.ConventionalCommit'>")
|
||||||
self.assertEqual(actual_rule.id, 'CT1')
|
self.assertEqual(actual_rule.id, 'CT1')
|
||||||
self.assertEqual(actual_rule.name, u'contrib-title-conventional-commits')
|
self.assertEqual(actual_rule.name, u'contrib-title-conventional-commits')
|
||||||
self.assertEqual(actual_rule.target, rules.CommitMessageTitle)
|
self.assertEqual(actual_rule.target, rules.CommitMessageTitle)
|
||||||
|
@ -135,7 +130,7 @@ class LintConfigTests(BaseTestCase):
|
||||||
actual_rule = config.rules.find_rule("contrib-body-requires-signed-off-by")
|
actual_rule = config.rules.find_rule("contrib-body-requires-signed-off-by")
|
||||||
self.assertTrue(actual_rule.is_contrib)
|
self.assertTrue(actual_rule.is_contrib)
|
||||||
|
|
||||||
self.assertEqual(ustr(type(actual_rule)), "<class 'signedoff_by.SignedOffBy'>")
|
self.assertEqual(str(type(actual_rule)), "<class 'signedoff_by.SignedOffBy'>")
|
||||||
self.assertEqual(actual_rule.id, 'CC1')
|
self.assertEqual(actual_rule.id, 'CC1')
|
||||||
self.assertEqual(actual_rule.name, u'contrib-body-requires-signed-off-by')
|
self.assertEqual(actual_rule.name, u'contrib-body-requires-signed-off-by')
|
||||||
|
|
||||||
|
@ -151,15 +146,15 @@ class LintConfigTests(BaseTestCase):
|
||||||
def test_contrib_negative(self):
|
def test_contrib_negative(self):
|
||||||
config = LintConfig()
|
config = LintConfig()
|
||||||
# non-existent contrib rule
|
# non-existent contrib rule
|
||||||
with self.assertRaisesMessage(LintConfigError, u"No contrib rule with id or name 'föo' found."):
|
with self.assertRaisesMessage(LintConfigError, "No contrib rule with id or name 'föo' found."):
|
||||||
config.contrib = u"contrib-title-conventional-commits,föo"
|
config.contrib = "contrib-title-conventional-commits,föo"
|
||||||
|
|
||||||
# UserRuleError, RuleOptionError should be re-raised as LintConfigErrors
|
# UserRuleError, RuleOptionError should be re-raised as LintConfigErrors
|
||||||
side_effects = [rules.UserRuleError(u"üser-rule"), options.RuleOptionError(u"rüle-option")]
|
side_effects = [rules.UserRuleError("üser-rule"), options.RuleOptionError("rüle-option")]
|
||||||
for side_effect in side_effects:
|
for side_effect in side_effects:
|
||||||
with patch('gitlint.config.rule_finder.find_rule_classes', side_effect=side_effect):
|
with patch('gitlint.config.rule_finder.find_rule_classes', side_effect=side_effect):
|
||||||
with self.assertRaisesMessage(LintConfigError, ustr(side_effect)):
|
with self.assertRaisesMessage(LintConfigError, str(side_effect)):
|
||||||
config.contrib = u"contrib-title-conventional-commits"
|
config.contrib = "contrib-title-conventional-commits"
|
||||||
|
|
||||||
def test_extra_path(self):
|
def test_extra_path(self):
|
||||||
config = LintConfig()
|
config = LintConfig()
|
||||||
|
@ -168,11 +163,11 @@ class LintConfigTests(BaseTestCase):
|
||||||
self.assertEqual(config.extra_path, self.get_user_rules_path())
|
self.assertEqual(config.extra_path, self.get_user_rules_path())
|
||||||
actual_rule = config.rules.find_rule('UC1')
|
actual_rule = config.rules.find_rule('UC1')
|
||||||
self.assertTrue(actual_rule.is_user_defined)
|
self.assertTrue(actual_rule.is_user_defined)
|
||||||
self.assertEqual(ustr(type(actual_rule)), "<class 'my_commit_rules.MyUserCommitRule'>")
|
self.assertEqual(str(type(actual_rule)), "<class 'my_commit_rules.MyUserCommitRule'>")
|
||||||
self.assertEqual(actual_rule.id, 'UC1')
|
self.assertEqual(actual_rule.id, 'UC1')
|
||||||
self.assertEqual(actual_rule.name, u'my-üser-commit-rule')
|
self.assertEqual(actual_rule.name, u'my-üser-commit-rule')
|
||||||
self.assertEqual(actual_rule.target, None)
|
self.assertEqual(actual_rule.target, None)
|
||||||
expected_rule_option = options.IntOption('violation-count', 1, u"Number of violåtions to return")
|
expected_rule_option = options.IntOption('violation-count', 1, "Number of violåtions to return")
|
||||||
self.assertListEqual(actual_rule.options_spec, [expected_rule_option])
|
self.assertListEqual(actual_rule.options_spec, [expected_rule_option])
|
||||||
self.assertDictEqual(actual_rule.options, {'violation-count': expected_rule_option})
|
self.assertDictEqual(actual_rule.options, {'violation-count': expected_rule_option})
|
||||||
|
|
||||||
|
@ -183,10 +178,10 @@ class LintConfigTests(BaseTestCase):
|
||||||
|
|
||||||
def test_extra_path_negative(self):
|
def test_extra_path_negative(self):
|
||||||
config = LintConfig()
|
config = LintConfig()
|
||||||
regex = u"Option extra-path must be either an existing directory or file (current value: 'föo/bar')"
|
regex = "Option extra-path must be either an existing directory or file (current value: 'föo/bar')"
|
||||||
# incorrect extra_path
|
# incorrect extra_path
|
||||||
with self.assertRaisesMessage(LintConfigError, regex):
|
with self.assertRaisesMessage(LintConfigError, regex):
|
||||||
config.extra_path = u"föo/bar"
|
config.extra_path = "föo/bar"
|
||||||
|
|
||||||
# extra path contains classes with errors
|
# extra path contains classes with errors
|
||||||
with self.assertRaisesMessage(LintConfigError,
|
with self.assertRaisesMessage(LintConfigError,
|
||||||
|
@ -198,17 +193,17 @@ class LintConfigTests(BaseTestCase):
|
||||||
|
|
||||||
# Note that we shouldn't test whether we can set unicode because python just doesn't allow unicode attributes
|
# Note that we shouldn't test whether we can set unicode because python just doesn't allow unicode attributes
|
||||||
with self.assertRaisesMessage(LintConfigError, "'foo' is not a valid gitlint option"):
|
with self.assertRaisesMessage(LintConfigError, "'foo' is not a valid gitlint option"):
|
||||||
config.set_general_option("foo", u"bår")
|
config.set_general_option("foo", "bår")
|
||||||
|
|
||||||
# try setting _config_path, this is a real attribute of LintConfig, but the code should prevent it from
|
# try setting _config_path, this is a real attribute of LintConfig, but the code should prevent it from
|
||||||
# being set
|
# being set
|
||||||
with self.assertRaisesMessage(LintConfigError, "'_config_path' is not a valid gitlint option"):
|
with self.assertRaisesMessage(LintConfigError, "'_config_path' is not a valid gitlint option"):
|
||||||
config.set_general_option("_config_path", u"bår")
|
config.set_general_option("_config_path", "bår")
|
||||||
|
|
||||||
# invalid verbosity
|
# invalid verbosity
|
||||||
incorrect_values = [-1, u"föo"]
|
incorrect_values = [-1, "föo"]
|
||||||
for value in incorrect_values:
|
for value in incorrect_values:
|
||||||
expected_msg = u"Option 'verbosity' must be a positive integer (current value: '{0}')".format(value)
|
expected_msg = f"Option 'verbosity' must be a positive integer (current value: '{value}')"
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_msg):
|
||||||
config.verbosity = value
|
config.verbosity = value
|
||||||
|
|
||||||
|
@ -220,12 +215,12 @@ class LintConfigTests(BaseTestCase):
|
||||||
# invalid ignore_xxx_commits
|
# invalid ignore_xxx_commits
|
||||||
ignore_attributes = ["ignore_merge_commits", "ignore_fixup_commits", "ignore_squash_commits",
|
ignore_attributes = ["ignore_merge_commits", "ignore_fixup_commits", "ignore_squash_commits",
|
||||||
"ignore_revert_commits"]
|
"ignore_revert_commits"]
|
||||||
incorrect_values = [-1, 4, u"föo"]
|
incorrect_values = [-1, 4, "föo"]
|
||||||
for attribute in ignore_attributes:
|
for attribute in ignore_attributes:
|
||||||
for value in incorrect_values:
|
for value in incorrect_values:
|
||||||
option_name = attribute.replace("_", "-")
|
option_name = attribute.replace("_", "-")
|
||||||
with self.assertRaisesMessage(LintConfigError,
|
with self.assertRaisesMessage(LintConfigError,
|
||||||
"Option '{0}' must be either 'true' or 'false'".format(option_name)):
|
f"Option '{option_name}' must be either 'true' or 'false'"):
|
||||||
setattr(config, attribute, value)
|
setattr(config, attribute, value)
|
||||||
|
|
||||||
# invalid ignore -> not here because ignore is a ListOption which converts everything to a string before
|
# invalid ignore -> not here because ignore is a ListOption which converts everything to a string before
|
||||||
|
@ -235,15 +230,15 @@ class LintConfigTests(BaseTestCase):
|
||||||
for attribute in ['debug', 'staged', 'ignore_stdin']:
|
for attribute in ['debug', 'staged', 'ignore_stdin']:
|
||||||
option_name = attribute.replace("_", "-")
|
option_name = attribute.replace("_", "-")
|
||||||
with self.assertRaisesMessage(LintConfigError,
|
with self.assertRaisesMessage(LintConfigError,
|
||||||
"Option '{0}' must be either 'true' or 'false'".format(option_name)):
|
f"Option '{option_name}' must be either 'true' or 'false'"):
|
||||||
setattr(config, attribute, u"föobar")
|
setattr(config, attribute, "föobar")
|
||||||
|
|
||||||
# extra-path has its own negative test
|
# extra-path has its own negative test
|
||||||
|
|
||||||
# invalid target
|
# invalid target
|
||||||
with self.assertRaisesMessage(LintConfigError,
|
with self.assertRaisesMessage(LintConfigError,
|
||||||
u"Option target must be an existing directory (current value: 'föo/bar')"):
|
"Option target must be an existing directory (current value: 'föo/bar')"):
|
||||||
config.target = u"föo/bar"
|
config.target = "föo/bar"
|
||||||
|
|
||||||
def test_ignore_independent_from_rules(self):
|
def test_ignore_independent_from_rules(self):
|
||||||
# Test that the lintconfig rules are not modified when setting config.ignore
|
# Test that the lintconfig rules are not modified when setting config.ignore
|
||||||
|
@ -273,9 +268,9 @@ class LintConfigTests(BaseTestCase):
|
||||||
# Other attributes don't matter
|
# Other attributes don't matter
|
||||||
config1 = LintConfig()
|
config1 = LintConfig()
|
||||||
config2 = LintConfig()
|
config2 = LintConfig()
|
||||||
config1.foo = u"bår"
|
config1.foo = "bår"
|
||||||
self.assertEqual(config1, config2)
|
self.assertEqual(config1, config2)
|
||||||
config2.foo = u"dūr"
|
config2.foo = "dūr"
|
||||||
self.assertEqual(config1, config2)
|
self.assertEqual(config1, config2)
|
||||||
|
|
||||||
|
|
||||||
|
@ -283,5 +278,5 @@ class LintConfigGeneratorTests(BaseTestCase):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@patch('gitlint.config.shutil.copyfile')
|
@patch('gitlint.config.shutil.copyfile')
|
||||||
def test_install_commit_msg_hook_negative(copy):
|
def test_install_commit_msg_hook_negative(copy):
|
||||||
LintConfigGenerator.generate_config(u"föo/bar/test")
|
LintConfigGenerator.generate_config("föo/bar/test")
|
||||||
copy.assert_called_with(GITLINT_CONFIG_TEMPLATE_SRC_PATH, u"föo/bar/test")
|
copy.assert_called_with(GITLINT_CONFIG_TEMPLATE_SRC_PATH, "föo/bar/test")
|
||||||
|
|
|
@ -42,30 +42,30 @@ class LintConfigBuilderTests(BaseTestCase):
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
|
|
||||||
# nothing gitlint
|
# nothing gitlint
|
||||||
config_builder.set_config_from_commit(self.gitcommit(u"tëst\ngitlint\nfoo"))
|
config_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint\nfoo"))
|
||||||
config = config_builder.build()
|
config = config_builder.build()
|
||||||
self.assertSequenceEqual(config.rules, original_rules)
|
self.assertSequenceEqual(config.rules, original_rules)
|
||||||
self.assertListEqual(config.ignore, [])
|
self.assertListEqual(config.ignore, [])
|
||||||
|
|
||||||
# ignore all rules
|
# ignore all rules
|
||||||
config_builder.set_config_from_commit(self.gitcommit(u"tëst\ngitlint-ignore: all\nfoo"))
|
config_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint-ignore: all\nfoo"))
|
||||||
config = config_builder.build()
|
config = config_builder.build()
|
||||||
self.assertEqual(config.ignore, original_rule_ids)
|
self.assertEqual(config.ignore, original_rule_ids)
|
||||||
|
|
||||||
# ignore all rules, no space
|
# ignore all rules, no space
|
||||||
config_builder.set_config_from_commit(self.gitcommit(u"tëst\ngitlint-ignore:all\nfoo"))
|
config_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint-ignore:all\nfoo"))
|
||||||
config = config_builder.build()
|
config = config_builder.build()
|
||||||
self.assertEqual(config.ignore, original_rule_ids)
|
self.assertEqual(config.ignore, original_rule_ids)
|
||||||
|
|
||||||
# ignore all rules, more spacing
|
# ignore all rules, more spacing
|
||||||
config_builder.set_config_from_commit(self.gitcommit(u"tëst\ngitlint-ignore: \t all\nfoo"))
|
config_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint-ignore: \t all\nfoo"))
|
||||||
config = config_builder.build()
|
config = config_builder.build()
|
||||||
self.assertEqual(config.ignore, original_rule_ids)
|
self.assertEqual(config.ignore, original_rule_ids)
|
||||||
|
|
||||||
def test_set_from_commit_ignore_specific(self):
|
def test_set_from_commit_ignore_specific(self):
|
||||||
# ignore specific rules
|
# ignore specific rules
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
config_builder.set_config_from_commit(self.gitcommit(u"tëst\ngitlint-ignore: T1, body-hard-tab"))
|
config_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint-ignore: T1, body-hard-tab"))
|
||||||
config = config_builder.build()
|
config = config_builder.build()
|
||||||
self.assertEqual(config.ignore, ["T1", "body-hard-tab"])
|
self.assertEqual(config.ignore, ["T1", "body-hard-tab"])
|
||||||
|
|
||||||
|
@ -89,14 +89,14 @@ class LintConfigBuilderTests(BaseTestCase):
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
|
|
||||||
# bad config file load
|
# bad config file load
|
||||||
foo_path = self.get_sample_path(u"föo")
|
foo_path = self.get_sample_path("föo")
|
||||||
expected_error_msg = u"Invalid file path: {0}".format(foo_path)
|
expected_error_msg = f"Invalid file path: {foo_path}"
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
||||||
config_builder.set_from_config_file(foo_path)
|
config_builder.set_from_config_file(foo_path)
|
||||||
|
|
||||||
# error during file parsing
|
# error during file parsing
|
||||||
path = self.get_sample_path("config/no-sections")
|
path = self.get_sample_path("config/no-sections")
|
||||||
expected_error_msg = u"File contains no section headers."
|
expected_error_msg = "File contains no section headers."
|
||||||
# We only match the start of the message here, since the exact message can vary depending on platform
|
# We only match the start of the message here, since the exact message can vary depending on platform
|
||||||
with self.assertRaisesRegex(LintConfigError, expected_error_msg):
|
with self.assertRaisesRegex(LintConfigError, expected_error_msg):
|
||||||
config_builder.set_from_config_file(path)
|
config_builder.set_from_config_file(path)
|
||||||
|
@ -105,7 +105,7 @@ class LintConfigBuilderTests(BaseTestCase):
|
||||||
path = self.get_sample_path("config/nonexisting-rule")
|
path = self.get_sample_path("config/nonexisting-rule")
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
config_builder.set_from_config_file(path)
|
config_builder.set_from_config_file(path)
|
||||||
expected_error_msg = u"No such rule 'föobar'"
|
expected_error_msg = "No such rule 'föobar'"
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
||||||
config_builder.build()
|
config_builder.build()
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ class LintConfigBuilderTests(BaseTestCase):
|
||||||
path = self.get_sample_path("config/nonexisting-general-option")
|
path = self.get_sample_path("config/nonexisting-general-option")
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
config_builder.set_from_config_file(path)
|
config_builder.set_from_config_file(path)
|
||||||
expected_error_msg = u"'foo' is not a valid gitlint option"
|
expected_error_msg = "'foo' is not a valid gitlint option"
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
||||||
config_builder.build()
|
config_builder.build()
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ class LintConfigBuilderTests(BaseTestCase):
|
||||||
path = self.get_sample_path("config/nonexisting-option")
|
path = self.get_sample_path("config/nonexisting-option")
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
config_builder.set_from_config_file(path)
|
config_builder.set_from_config_file(path)
|
||||||
expected_error_msg = u"Rule 'title-max-length' has no option 'föobar'"
|
expected_error_msg = "Rule 'title-max-length' has no option 'föobar'"
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
||||||
config_builder.build()
|
config_builder.build()
|
||||||
|
|
||||||
|
@ -129,8 +129,8 @@ class LintConfigBuilderTests(BaseTestCase):
|
||||||
path = self.get_sample_path("config/invalid-option-value")
|
path = self.get_sample_path("config/invalid-option-value")
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
config_builder.set_from_config_file(path)
|
config_builder.set_from_config_file(path)
|
||||||
expected_error_msg = u"'föo' is not a valid value for option 'title-max-length.line-length'. " + \
|
expected_error_msg = "'föo' is not a valid value for option 'title-max-length.line-length'. " + \
|
||||||
u"Option 'line-length' must be a positive integer (current value: 'föo')."
|
"Option 'line-length' must be a positive integer (current value: 'föo')."
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
|
||||||
config_builder.build()
|
config_builder.build()
|
||||||
|
|
||||||
|
@ -141,39 +141,39 @@ class LintConfigBuilderTests(BaseTestCase):
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
config_builder.set_config_from_string_list(['general.verbosity=1', 'title-max-length.line-length=60',
|
config_builder.set_config_from_string_list(['general.verbosity=1', 'title-max-length.line-length=60',
|
||||||
'body-max-line-length.line-length=120',
|
'body-max-line-length.line-length=120',
|
||||||
u"title-must-not-contain-word.words=håha"])
|
"title-must-not-contain-word.words=håha"])
|
||||||
|
|
||||||
config = config_builder.build()
|
config = config_builder.build()
|
||||||
self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 60)
|
self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 60)
|
||||||
self.assertEqual(config.get_rule_option('body-max-line-length', 'line-length'), 120)
|
self.assertEqual(config.get_rule_option('body-max-line-length', 'line-length'), 120)
|
||||||
self.assertListEqual(config.get_rule_option('title-must-not-contain-word', 'words'), [u"håha"])
|
self.assertListEqual(config.get_rule_option('title-must-not-contain-word', 'words'), ["håha"])
|
||||||
self.assertEqual(config.verbosity, 1)
|
self.assertEqual(config.verbosity, 1)
|
||||||
|
|
||||||
def test_set_config_from_string_list_negative(self):
|
def test_set_config_from_string_list_negative(self):
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
|
|
||||||
# assert error on incorrect rule - this happens at build time
|
# assert error on incorrect rule - this happens at build time
|
||||||
config_builder.set_config_from_string_list([u"föo.bar=1"])
|
config_builder.set_config_from_string_list(["föo.bar=1"])
|
||||||
with self.assertRaisesMessage(LintConfigError, u"No such rule 'föo'"):
|
with self.assertRaisesMessage(LintConfigError, "No such rule 'föo'"):
|
||||||
config_builder.build()
|
config_builder.build()
|
||||||
|
|
||||||
# no equal sign
|
# no equal sign
|
||||||
expected_msg = u"'föo.bar' is an invalid configuration option. Use '<rule>.<option>=<value>'"
|
expected_msg = "'föo.bar' is an invalid configuration option. Use '<rule>.<option>=<value>'"
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_msg):
|
||||||
config_builder.set_config_from_string_list([u"föo.bar"])
|
config_builder.set_config_from_string_list(["föo.bar"])
|
||||||
|
|
||||||
# missing value
|
# missing value
|
||||||
expected_msg = u"'föo.bar=' is an invalid configuration option. Use '<rule>.<option>=<value>'"
|
expected_msg = "'föo.bar=' is an invalid configuration option. Use '<rule>.<option>=<value>'"
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_msg):
|
||||||
config_builder.set_config_from_string_list([u"föo.bar="])
|
config_builder.set_config_from_string_list(["föo.bar="])
|
||||||
|
|
||||||
# space instead of equal sign
|
# space instead of equal sign
|
||||||
expected_msg = u"'föo.bar 1' is an invalid configuration option. Use '<rule>.<option>=<value>'"
|
expected_msg = "'föo.bar 1' is an invalid configuration option. Use '<rule>.<option>=<value>'"
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_msg):
|
||||||
config_builder.set_config_from_string_list([u"föo.bar 1"])
|
config_builder.set_config_from_string_list(["föo.bar 1"])
|
||||||
|
|
||||||
# no period between rule and option names
|
# no period between rule and option names
|
||||||
expected_msg = u"'föobar=1' is an invalid configuration option. Use '<rule>.<option>=<value>'"
|
expected_msg = "'föobar=1' is an invalid configuration option. Use '<rule>.<option>=<value>'"
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_msg):
|
with self.assertRaisesMessage(LintConfigError, expected_msg):
|
||||||
config_builder.set_config_from_string_list([u'föobar=1'])
|
config_builder.set_config_from_string_list([u'föobar=1'])
|
||||||
|
|
||||||
|
@ -216,15 +216,15 @@ class LintConfigBuilderTests(BaseTestCase):
|
||||||
# Add a named rule by setting an option in the config builder that follows the named rule pattern
|
# Add a named rule by setting an option in the config builder that follows the named rule pattern
|
||||||
# Assert that whitespace in the rule name is stripped
|
# Assert that whitespace in the rule name is stripped
|
||||||
rule_qualifiers = [u'T7:my-extra-rüle', u' T7 : my-extra-rüle ', u'\tT7:\tmy-extra-rüle\t',
|
rule_qualifiers = [u'T7:my-extra-rüle', u' T7 : my-extra-rüle ', u'\tT7:\tmy-extra-rüle\t',
|
||||||
u'T7:\t\n \tmy-extra-rüle\t\n\n', u"title-match-regex:my-extra-rüle"]
|
u'T7:\t\n \tmy-extra-rüle\t\n\n', "title-match-regex:my-extra-rüle"]
|
||||||
for rule_qualifier in rule_qualifiers:
|
for rule_qualifier in rule_qualifiers:
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
config_builder.set_option(rule_qualifier, 'regex', u"föo")
|
config_builder.set_option(rule_qualifier, 'regex', "föo")
|
||||||
|
|
||||||
expected_rules = copy.deepcopy(default_rules)
|
expected_rules = copy.deepcopy(default_rules)
|
||||||
my_rule = rules.TitleRegexMatches({'regex': u"föo"})
|
my_rule = rules.TitleRegexMatches({'regex': "föo"})
|
||||||
my_rule.id = rules.TitleRegexMatches.id + u":my-extra-rüle"
|
my_rule.id = rules.TitleRegexMatches.id + ":my-extra-rüle"
|
||||||
my_rule.name = rules.TitleRegexMatches.name + u":my-extra-rüle"
|
my_rule.name = rules.TitleRegexMatches.name + ":my-extra-rüle"
|
||||||
expected_rules._rules[u'T7:my-extra-rüle'] = my_rule
|
expected_rules._rules[u'T7:my-extra-rüle'] = my_rule
|
||||||
self.assertEqual(config_builder.build().rules, expected_rules)
|
self.assertEqual(config_builder.build().rules, expected_rules)
|
||||||
|
|
||||||
|
@ -233,32 +233,32 @@ class LintConfigBuilderTests(BaseTestCase):
|
||||||
# to the same rule
|
# to the same rule
|
||||||
for other_rule_qualifier in rule_qualifiers:
|
for other_rule_qualifier in rule_qualifiers:
|
||||||
cb = config_builder.clone()
|
cb = config_builder.clone()
|
||||||
cb.set_option(other_rule_qualifier, 'regex', other_rule_qualifier + u"bōr")
|
cb.set_option(other_rule_qualifier, 'regex', other_rule_qualifier + "bōr")
|
||||||
# before setting the expected rule option value correctly, the RuleCollection should be different
|
# before setting the expected rule option value correctly, the RuleCollection should be different
|
||||||
self.assertNotEqual(cb.build().rules, expected_rules)
|
self.assertNotEqual(cb.build().rules, expected_rules)
|
||||||
# after setting the option on the expected rule, it should be equal
|
# after setting the option on the expected rule, it should be equal
|
||||||
my_rule.options['regex'].set(other_rule_qualifier + u"bōr")
|
my_rule.options['regex'].set(other_rule_qualifier + "bōr")
|
||||||
self.assertEqual(cb.build().rules, expected_rules)
|
self.assertEqual(cb.build().rules, expected_rules)
|
||||||
my_rule.options['regex'].set(u"wrong")
|
my_rule.options['regex'].set("wrong")
|
||||||
|
|
||||||
def test_named_rules_negative(self):
|
def test_named_rules_negative(self):
|
||||||
# T7 = title-match-regex
|
# T7 = title-match-regex
|
||||||
# Invalid rule name
|
# Invalid rule name
|
||||||
for invalid_name in ["", " ", " ", "\t", "\n", u"å b", u"å:b", u"åb:", u":åb"]:
|
for invalid_name in ["", " ", " ", "\t", "\n", "å b", "å:b", "åb:", ":åb"]:
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
config_builder.set_option(u"T7:{0}".format(invalid_name), 'regex', u"tëst")
|
config_builder.set_option(f"T7:{invalid_name}", 'regex', "tëst")
|
||||||
expected_msg = u"The rule-name part in 'T7:{0}' cannot contain whitespace, colons or be empty"
|
expected_msg = f"The rule-name part in 'T7:{invalid_name}' cannot contain whitespace, colons or be empty"
|
||||||
with self.assertRaisesMessage(LintConfigError, expected_msg.format(invalid_name)):
|
with self.assertRaisesMessage(LintConfigError, expected_msg):
|
||||||
config_builder.build()
|
config_builder.build()
|
||||||
|
|
||||||
# Invalid parent rule name
|
# Invalid parent rule name
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
config_builder.set_option(u"Ž123:foöbar", u"fåke-option", u"fåke-value")
|
config_builder.set_option("Ž123:foöbar", "fåke-option", "fåke-value")
|
||||||
with self.assertRaisesMessage(LintConfigError, u"No such rule 'Ž123' (named rule: 'Ž123:foöbar')"):
|
with self.assertRaisesMessage(LintConfigError, "No such rule 'Ž123' (named rule: 'Ž123:foöbar')"):
|
||||||
config_builder.build()
|
config_builder.build()
|
||||||
|
|
||||||
# Invalid option name (this is the same as with regular rules)
|
# Invalid option name (this is the same as with regular rules)
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
config_builder.set_option(u"T7:foöbar", u"blå", u"my-rëgex")
|
config_builder.set_option("T7:foöbar", "blå", "my-rëgex")
|
||||||
with self.assertRaisesMessage(LintConfigError, u"Rule 'T7:foöbar' has no option 'blå'"):
|
with self.assertRaisesMessage(LintConfigError, "Rule 'T7:foöbar' has no option 'blå'"):
|
||||||
config_builder.build()
|
config_builder.build()
|
||||||
|
|
|
@ -1,20 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
try:
|
from io import StringIO
|
||||||
# python 2.x
|
|
||||||
from StringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
try:
|
from unittest.mock import patch
|
||||||
# python 2.x
|
|
||||||
from mock import patch
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
|
|
||||||
|
|
||||||
from gitlint.tests.base import BaseTestCase
|
from gitlint.tests.base import BaseTestCase
|
||||||
from gitlint import cli
|
from gitlint import cli
|
||||||
|
@ -25,7 +15,7 @@ class LintConfigPrecedenceTests(BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.cli = CliRunner()
|
self.cli = CliRunner()
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=u"WIP:fö\n\nThis is å test message\n")
|
@patch('gitlint.cli.get_stdin_data', return_value="WIP:fö\n\nThis is å test message\n")
|
||||||
def test_config_precedence(self, _):
|
def test_config_precedence(self, _):
|
||||||
# TODO(jroovers): this test really only test verbosity, we need to do some refactoring to gitlint.cli
|
# TODO(jroovers): this test really only test verbosity, we need to do some refactoring to gitlint.cli
|
||||||
# to more easily test everything
|
# to more easily test everything
|
||||||
|
@ -41,14 +31,14 @@ class LintConfigPrecedenceTests(BaseTestCase):
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
result = self.cli.invoke(cli.cli, ["-vvv", "-c", "general.verbosity=2", "--config", config_path])
|
result = self.cli.invoke(cli.cli, ["-vvv", "-c", "general.verbosity=2", "--config", config_path])
|
||||||
self.assertEqual(result.output, "")
|
self.assertEqual(result.output, "")
|
||||||
self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
|
self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
|
||||||
|
|
||||||
# 2. environment variables
|
# 2. environment variables
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
result = self.cli.invoke(cli.cli, ["-c", "general.verbosity=2", "--config", config_path],
|
result = self.cli.invoke(cli.cli, ["-c", "general.verbosity=2", "--config", config_path],
|
||||||
env={"GITLINT_VERBOSITY": "3"})
|
env={"GITLINT_VERBOSITY": "3"})
|
||||||
self.assertEqual(result.output, "")
|
self.assertEqual(result.output, "")
|
||||||
self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
|
self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
|
||||||
|
|
||||||
# 3. commandline -c flags
|
# 3. commandline -c flags
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
|
@ -66,9 +56,9 @@ class LintConfigPrecedenceTests(BaseTestCase):
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
result = self.cli.invoke(cli.cli)
|
result = self.cli.invoke(cli.cli)
|
||||||
self.assertEqual(result.output, "")
|
self.assertEqual(result.output, "")
|
||||||
self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
|
self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
|
||||||
|
|
||||||
@patch('gitlint.cli.get_stdin_data', return_value=u"WIP: This is å test")
|
@patch('gitlint.cli.get_stdin_data', return_value="WIP: This is å test")
|
||||||
def test_ignore_precedence(self, get_stdin_data):
|
def test_ignore_precedence(self, get_stdin_data):
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
# --ignore takes precedence over -c general.ignore
|
# --ignore takes precedence over -c general.ignore
|
||||||
|
@ -77,11 +67,11 @@ class LintConfigPrecedenceTests(BaseTestCase):
|
||||||
self.assertEqual(result.exit_code, 1)
|
self.assertEqual(result.exit_code, 1)
|
||||||
# We still expect the T5 violation, but no B6 violation as --ignore overwrites -c general.ignore
|
# We still expect the T5 violation, but no B6 violation as --ignore overwrites -c general.ignore
|
||||||
self.assertEqual(stderr.getvalue(),
|
self.assertEqual(stderr.getvalue(),
|
||||||
u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This is å test\"\n")
|
"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This is å test\"\n")
|
||||||
|
|
||||||
# test that we can also still configure a rule that is first ignored but then not
|
# test that we can also still configure a rule that is first ignored but then not
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
get_stdin_data.return_value = u"This is å test"
|
get_stdin_data.return_value = "This is å test"
|
||||||
# --ignore takes precedence over -c general.ignore
|
# --ignore takes precedence over -c general.ignore
|
||||||
result = self.cli.invoke(cli.cli, ["-c", "general.ignore=title-max-length",
|
result = self.cli.invoke(cli.cli, ["-c", "general.ignore=title-max-length",
|
||||||
"-c", "title-max-length.line-length=5",
|
"-c", "title-max-length.line-length=5",
|
||||||
|
@ -91,7 +81,7 @@ class LintConfigPrecedenceTests(BaseTestCase):
|
||||||
|
|
||||||
# We still expect the T1 violation with custom config,
|
# We still expect the T1 violation with custom config,
|
||||||
# but no B6 violation as --ignore overwrites -c general.ignore
|
# but no B6 violation as --ignore overwrites -c general.ignore
|
||||||
self.assertEqual(stderr.getvalue(), u"1: T1 Title exceeds max length (14>5): \"This is å test\"\n")
|
self.assertEqual(stderr.getvalue(), "1: T1 Title exceeds max length (14>5): \"This is å test\"\n")
|
||||||
|
|
||||||
def test_general_option_after_rule_option(self):
|
def test_general_option_after_rule_option(self):
|
||||||
# We used to have a bug where we didn't process general options before setting specific options, this would
|
# We used to have a bug where we didn't process general options before setting specific options, this would
|
||||||
|
|
|
@ -10,34 +10,34 @@ class RuleCollectionTests(BaseTestCase):
|
||||||
|
|
||||||
def test_add_rule(self):
|
def test_add_rule(self):
|
||||||
collection = RuleCollection()
|
collection = RuleCollection()
|
||||||
collection.add_rule(rules.TitleMaxLength, u"my-rüle", {"my_attr": u"föo", "my_attr2": 123})
|
collection.add_rule(rules.TitleMaxLength, "my-rüle", {"my_attr": "föo", "my_attr2": 123})
|
||||||
|
|
||||||
expected = rules.TitleMaxLength()
|
expected = rules.TitleMaxLength()
|
||||||
expected.id = u"my-rüle"
|
expected.id = "my-rüle"
|
||||||
expected.my_attr = u"föo"
|
expected.my_attr = "föo"
|
||||||
expected.my_attr2 = 123
|
expected.my_attr2 = 123
|
||||||
|
|
||||||
self.assertEqual(len(collection), 1)
|
self.assertEqual(len(collection), 1)
|
||||||
self.assertDictEqual(collection._rules, OrderedDict({u"my-rüle": expected}))
|
self.assertDictEqual(collection._rules, OrderedDict({"my-rüle": expected}))
|
||||||
# Need to explicitely compare expected attributes as the rule.__eq__ method does not compare these attributes
|
# Need to explicitely compare expected attributes as the rule.__eq__ method does not compare these attributes
|
||||||
self.assertEqual(collection._rules[expected.id].my_attr, expected.my_attr)
|
self.assertEqual(collection._rules[expected.id].my_attr, expected.my_attr)
|
||||||
self.assertEqual(collection._rules[expected.id].my_attr2, expected.my_attr2)
|
self.assertEqual(collection._rules[expected.id].my_attr2, expected.my_attr2)
|
||||||
|
|
||||||
def test_add_find_rule(self):
|
def test_add_find_rule(self):
|
||||||
collection = RuleCollection()
|
collection = RuleCollection()
|
||||||
collection.add_rules([rules.TitleMaxLength, rules.TitleTrailingWhitespace], {"my_attr": u"föo"})
|
collection.add_rules([rules.TitleMaxLength, rules.TitleTrailingWhitespace], {"my_attr": "föo"})
|
||||||
|
|
||||||
# find by id
|
# find by id
|
||||||
expected = rules.TitleMaxLength()
|
expected = rules.TitleMaxLength()
|
||||||
rule = collection.find_rule('T1')
|
rule = collection.find_rule('T1')
|
||||||
self.assertEqual(rule, expected)
|
self.assertEqual(rule, expected)
|
||||||
self.assertEqual(rule.my_attr, u"föo")
|
self.assertEqual(rule.my_attr, "föo")
|
||||||
|
|
||||||
# find by name
|
# find by name
|
||||||
expected2 = rules.TitleTrailingWhitespace()
|
expected2 = rules.TitleTrailingWhitespace()
|
||||||
rule = collection.find_rule('title-trailing-whitespace')
|
rule = collection.find_rule('title-trailing-whitespace')
|
||||||
self.assertEqual(rule, expected2)
|
self.assertEqual(rule, expected2)
|
||||||
self.assertEqual(rule.my_attr, u"föo")
|
self.assertEqual(rule.my_attr, "föo")
|
||||||
|
|
||||||
# find non-existing
|
# find non-existing
|
||||||
rule = collection.find_rule(u'föo')
|
rule = collection.find_rule(u'föo')
|
||||||
|
@ -45,8 +45,8 @@ class RuleCollectionTests(BaseTestCase):
|
||||||
|
|
||||||
def test_delete_rules_by_attr(self):
|
def test_delete_rules_by_attr(self):
|
||||||
collection = RuleCollection()
|
collection = RuleCollection()
|
||||||
collection.add_rules([rules.TitleMaxLength, rules.TitleTrailingWhitespace], {"foo": u"bår"})
|
collection.add_rules([rules.TitleMaxLength, rules.TitleTrailingWhitespace], {"foo": "bår"})
|
||||||
collection.add_rules([rules.BodyHardTab], {"hur": u"dûr"})
|
collection.add_rules([rules.BodyHardTab], {"hur": "dûr"})
|
||||||
|
|
||||||
# Assert all rules are there as expected
|
# Assert all rules are there as expected
|
||||||
self.assertEqual(len(collection), 3)
|
self.assertEqual(len(collection), 3)
|
||||||
|
@ -54,11 +54,11 @@ class RuleCollectionTests(BaseTestCase):
|
||||||
self.assertEqual(collection.find_rule(expected_rule.id), expected_rule)
|
self.assertEqual(collection.find_rule(expected_rule.id), expected_rule)
|
||||||
|
|
||||||
# Delete rules by attr, assert that we still have the right rules in the collection
|
# Delete rules by attr, assert that we still have the right rules in the collection
|
||||||
collection.delete_rules_by_attr("foo", u"bår")
|
collection.delete_rules_by_attr("foo", "bår")
|
||||||
self.assertEqual(len(collection), 1)
|
self.assertEqual(len(collection), 1)
|
||||||
self.assertIsNone(collection.find_rule(rules.TitleMaxLength.id), None)
|
self.assertIsNone(collection.find_rule(rules.TitleMaxLength.id), None)
|
||||||
self.assertIsNone(collection.find_rule(rules.TitleTrailingWhitespace.id), None)
|
self.assertIsNone(collection.find_rule(rules.TitleTrailingWhitespace.id), None)
|
||||||
|
|
||||||
found = collection.find_rule(rules.BodyHardTab.id)
|
found = collection.find_rule(rules.BodyHardTab.id)
|
||||||
self.assertEqual(found, rules.BodyHardTab())
|
self.assertEqual(found, rules.BodyHardTab())
|
||||||
self.assertEqual(found.hur, u"dûr")
|
self.assertEqual(found.hur, "dûr")
|
||||||
|
|
|
@ -20,28 +20,28 @@ class ContribConventionalCommitTests(BaseTestCase):
|
||||||
|
|
||||||
# No violations when using a correct type and format
|
# No violations when using a correct type and format
|
||||||
for type in ["fix", "feat", "chore", "docs", "style", "refactor", "perf", "test", "revert", "ci", "build"]:
|
for type in ["fix", "feat", "chore", "docs", "style", "refactor", "perf", "test", "revert", "ci", "build"]:
|
||||||
violations = rule.validate(type + u": föo", None)
|
violations = rule.validate(type + ": föo", None)
|
||||||
self.assertListEqual([], violations)
|
self.assertListEqual([], violations)
|
||||||
|
|
||||||
# assert violation on wrong type
|
# assert violation on wrong type
|
||||||
expected_violation = RuleViolation("CT1", "Title does not start with one of fix, feat, chore, docs,"
|
expected_violation = RuleViolation("CT1", "Title does not start with one of fix, feat, chore, docs,"
|
||||||
" style, refactor, perf, test, revert, ci, build", u"bår: foo")
|
" style, refactor, perf, test, revert, ci, build", "bår: foo")
|
||||||
violations = rule.validate(u"bår: foo", None)
|
violations = rule.validate("bår: foo", None)
|
||||||
self.assertListEqual([expected_violation], violations)
|
self.assertListEqual([expected_violation], violations)
|
||||||
|
|
||||||
# assert violation on wrong format
|
# assert violation on wrong format
|
||||||
expected_violation = RuleViolation("CT1", "Title does not follow ConventionalCommits.org format "
|
expected_violation = RuleViolation("CT1", "Title does not follow ConventionalCommits.org format "
|
||||||
"'type(optional-scope): description'", u"fix föo")
|
"'type(optional-scope): description'", "fix föo")
|
||||||
violations = rule.validate(u"fix föo", None)
|
violations = rule.validate("fix föo", None)
|
||||||
self.assertListEqual([expected_violation], violations)
|
self.assertListEqual([expected_violation], violations)
|
||||||
|
|
||||||
# assert no violation when adding new type
|
# assert no violation when adding new type
|
||||||
rule = ConventionalCommit({'types': [u"föo", u"bär"]})
|
rule = ConventionalCommit({'types': ["föo", "bär"]})
|
||||||
for typ in [u"föo", u"bär"]:
|
for typ in ["föo", "bär"]:
|
||||||
violations = rule.validate(typ + u": hür dur", None)
|
violations = rule.validate(typ + ": hür dur", None)
|
||||||
self.assertListEqual([], violations)
|
self.assertListEqual([], violations)
|
||||||
|
|
||||||
# assert violation when using incorrect type when types have been reconfigured
|
# assert violation when using incorrect type when types have been reconfigured
|
||||||
violations = rule.validate(u"fix: hür dur", None)
|
violations = rule.validate("fix: hür dur", None)
|
||||||
expected_violation = RuleViolation("CT1", u"Title does not start with one of föo, bär", u"fix: hür dur")
|
expected_violation = RuleViolation("CT1", "Title does not start with one of föo, bär", "fix: hür dur")
|
||||||
self.assertListEqual([expected_violation], violations)
|
self.assertListEqual([expected_violation], violations)
|
||||||
|
|
|
@ -19,14 +19,14 @@ class ContribSignedOffByTests(BaseTestCase):
|
||||||
def test_signedoff_by(self):
|
def test_signedoff_by(self):
|
||||||
# No violations when 'Signed-Off-By' line is present
|
# No violations when 'Signed-Off-By' line is present
|
||||||
rule = SignedOffBy()
|
rule = SignedOffBy()
|
||||||
violations = rule.validate(self.gitcommit(u"Föobar\n\nMy Body\nSigned-Off-By: John Smith"))
|
violations = rule.validate(self.gitcommit("Föobar\n\nMy Body\nSigned-Off-By: John Smith"))
|
||||||
self.assertListEqual([], violations)
|
self.assertListEqual([], violations)
|
||||||
|
|
||||||
# Assert violation when no 'Signed-Off-By' line is present
|
# Assert violation when no 'Signed-Off-By' line is present
|
||||||
violations = rule.validate(self.gitcommit(u"Föobar\n\nMy Body"))
|
violations = rule.validate(self.gitcommit("Föobar\n\nMy Body"))
|
||||||
expected_violation = RuleViolation("CC1", "Body does not contain a 'Signed-Off-By' line", line_nr=1)
|
expected_violation = RuleViolation("CC1", "Body does not contain a 'Signed-Off-By' line", line_nr=1)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# Assert violation when no 'Signed-Off-By' in title but not in body
|
# Assert violation when no 'Signed-Off-By' in title but not in body
|
||||||
violations = rule.validate(self.gitcommit(u"Signed-Off-By\n\nFöobar"))
|
violations = rule.validate(self.gitcommit("Signed-Off-By\n\nFöobar"))
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
|
@ -6,8 +6,6 @@ from gitlint.contrib import rules as contrib_rules
|
||||||
from gitlint.tests.contrib import rules as contrib_tests
|
from gitlint.tests.contrib import rules as contrib_tests
|
||||||
from gitlint import rule_finder, rules
|
from gitlint import rule_finder, rules
|
||||||
|
|
||||||
from gitlint.utils import ustr
|
|
||||||
|
|
||||||
|
|
||||||
class ContribRuleTests(BaseTestCase):
|
class ContribRuleTests(BaseTestCase):
|
||||||
|
|
||||||
|
@ -24,10 +22,9 @@ class ContribRuleTests(BaseTestCase):
|
||||||
# Find all python files in the contrib dir and assert there's a corresponding test file
|
# Find all python files in the contrib dir and assert there's a corresponding test file
|
||||||
for filename in os.listdir(self.CONTRIB_DIR):
|
for filename in os.listdir(self.CONTRIB_DIR):
|
||||||
if filename.endswith(".py") and filename not in ["__init__.py"]:
|
if filename.endswith(".py") and filename not in ["__init__.py"]:
|
||||||
expected_test_file = ustr(u"test_" + filename)
|
expected_test_file = "test_" + filename
|
||||||
error_msg = u"Every Contrib Rule must have associated tests. " + \
|
error_msg = "Every Contrib Rule must have associated tests. " + \
|
||||||
"Expected test file {0} not found.".format(os.path.join(contrib_tests_dir,
|
f"Expected test file {os.path.join(contrib_tests_dir, expected_test_file)} not found."
|
||||||
expected_test_file))
|
|
||||||
self.assertIn(expected_test_file, contrib_test_files, error_msg)
|
self.assertIn(expected_test_file, contrib_test_files, error_msg)
|
||||||
|
|
||||||
def test_contrib_rule_naming_conventions(self):
|
def test_contrib_rule_naming_conventions(self):
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
gitlint: checking commit message...
|
||||||
|
{git_repo} is not a git repository.
|
|
@ -0,0 +1,2 @@
|
||||||
|
gitlint: checking commit message...
|
||||||
|
Error: The 'staged' option (--staged) can only be used when using '--msg-filename' or when piping data to gitlint via stdin.
|
|
@ -1,12 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import os
|
import os
|
||||||
|
|
||||||
try:
|
from unittest.mock import patch
|
||||||
# python 2.x
|
|
||||||
from mock import patch
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
|
|
||||||
|
|
||||||
from gitlint.shell import ErrorReturnCode, CommandNotFound
|
from gitlint.shell import ErrorReturnCode, CommandNotFound
|
||||||
|
|
||||||
|
@ -19,7 +14,7 @@ class GitTests(BaseTestCase):
|
||||||
# Expected special_args passed to 'sh'
|
# Expected special_args passed to 'sh'
|
||||||
expected_sh_special_args = {
|
expected_sh_special_args = {
|
||||||
'_tty_out': False,
|
'_tty_out': False,
|
||||||
'_cwd': u"fåke/path"
|
'_cwd': "fåke/path"
|
||||||
}
|
}
|
||||||
|
|
||||||
@patch('gitlint.git.sh')
|
@patch('gitlint.git.sh')
|
||||||
|
@ -28,7 +23,7 @@ class GitTests(BaseTestCase):
|
||||||
expected_msg = "'git' command not found. You need to install git to use gitlint on a local repository. " + \
|
expected_msg = "'git' command not found. You need to install git to use gitlint on a local repository. " + \
|
||||||
"See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git."
|
"See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git."
|
||||||
with self.assertRaisesMessage(GitNotInstalledError, expected_msg):
|
with self.assertRaisesMessage(GitNotInstalledError, expected_msg):
|
||||||
GitContext.from_local_repository(u"fåke/path")
|
GitContext.from_local_repository("fåke/path")
|
||||||
|
|
||||||
# assert that commit message was read using git command
|
# assert that commit message was read using git command
|
||||||
sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
|
sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
|
||||||
|
@ -39,8 +34,8 @@ class GitTests(BaseTestCase):
|
||||||
err = b"fatal: Not a git repository (or any of the parent directories): .git"
|
err = b"fatal: Not a git repository (or any of the parent directories): .git"
|
||||||
sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)
|
sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)
|
||||||
|
|
||||||
with self.assertRaisesMessage(GitContextError, u"fåke/path is not a git repository."):
|
with self.assertRaisesMessage(GitContextError, "fåke/path is not a git repository."):
|
||||||
GitContext.from_local_repository(u"fåke/path")
|
GitContext.from_local_repository("fåke/path")
|
||||||
|
|
||||||
# assert that commit message was read using git command
|
# assert that commit message was read using git command
|
||||||
sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
|
sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
|
||||||
|
@ -49,9 +44,9 @@ class GitTests(BaseTestCase):
|
||||||
err = b"fatal: Random git error"
|
err = b"fatal: Random git error"
|
||||||
sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)
|
sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)
|
||||||
|
|
||||||
expected_msg = u"An error occurred while executing 'git log -1 --pretty=%H': {0}".format(err)
|
expected_msg = f"An error occurred while executing 'git log -1 --pretty=%H': {err}"
|
||||||
with self.assertRaisesMessage(GitContextError, expected_msg):
|
with self.assertRaisesMessage(GitContextError, expected_msg):
|
||||||
GitContext.from_local_repository(u"fåke/path")
|
GitContext.from_local_repository("fåke/path")
|
||||||
|
|
||||||
# assert that commit message was read using git command
|
# assert that commit message was read using git command
|
||||||
sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
|
sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
|
||||||
|
@ -63,9 +58,9 @@ class GitTests(BaseTestCase):
|
||||||
|
|
||||||
sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)
|
sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)
|
||||||
|
|
||||||
expected_msg = u"Current branch has no commits. Gitlint requires at least one commit to function."
|
expected_msg = "Current branch has no commits. Gitlint requires at least one commit to function."
|
||||||
with self.assertRaisesMessage(GitContextError, expected_msg):
|
with self.assertRaisesMessage(GitContextError, expected_msg):
|
||||||
GitContext.from_local_repository(u"fåke/path")
|
GitContext.from_local_repository("fåke/path")
|
||||||
|
|
||||||
# assert that commit message was read using git command
|
# assert that commit message was read using git command
|
||||||
sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
|
sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
|
||||||
|
@ -78,12 +73,12 @@ class GitTests(BaseTestCase):
|
||||||
b"'git <command> [<revision>...] -- [<file>...]'")
|
b"'git <command> [<revision>...] -- [<file>...]'")
|
||||||
|
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
u"#\n", # git config --get core.commentchar
|
"#\n", # git config --get core.commentchar
|
||||||
ErrorReturnCode("rev-parse --abbrev-ref HEAD", b"", err)
|
ErrorReturnCode("rev-parse --abbrev-ref HEAD", b"", err)
|
||||||
]
|
]
|
||||||
|
|
||||||
with self.assertRaisesMessage(GitContextError, expected_msg):
|
with self.assertRaisesMessage(GitContextError, expected_msg):
|
||||||
context = GitContext.from_commit_msg(u"test")
|
context = GitContext.from_commit_msg("test")
|
||||||
context.current_branch
|
context.current_branch
|
||||||
|
|
||||||
# assert that commit message was read using git command
|
# assert that commit message was read using git command
|
||||||
|
@ -95,21 +90,19 @@ class GitTests(BaseTestCase):
|
||||||
self.assertEqual(git_commentchar(), "#")
|
self.assertEqual(git_commentchar(), "#")
|
||||||
|
|
||||||
git.return_value.exit_code = 0
|
git.return_value.exit_code = 0
|
||||||
git.return_value.__str__ = lambda _: u"ä"
|
git.return_value = "ä"
|
||||||
git.return_value.__unicode__ = lambda _: u"ä"
|
self.assertEqual(git_commentchar(), "ä")
|
||||||
self.assertEqual(git_commentchar(), u"ä")
|
|
||||||
|
|
||||||
git.return_value = ';\n'
|
git.return_value = ';\n'
|
||||||
self.assertEqual(git_commentchar(os.path.join(u"/föo", u"bar")), ';')
|
self.assertEqual(git_commentchar(os.path.join("/föo", "bar")), ';')
|
||||||
|
|
||||||
git.assert_called_with("config", "--get", "core.commentchar", _ok_code=[0, 1],
|
git.assert_called_with("config", "--get", "core.commentchar", _ok_code=[0, 1],
|
||||||
_cwd=os.path.join(u"/föo", u"bar"))
|
_cwd=os.path.join("/föo", "bar"))
|
||||||
|
|
||||||
@patch("gitlint.git._git")
|
@patch("gitlint.git._git")
|
||||||
def test_git_hooks_dir(self, git):
|
def test_git_hooks_dir(self, git):
|
||||||
hooks_dir = os.path.join(u"föo", ".git", "hooks")
|
hooks_dir = os.path.join("föo", ".git", "hooks")
|
||||||
git.return_value.__str__ = lambda _: hooks_dir + "\n"
|
git.return_value = hooks_dir + "\n"
|
||||||
git.return_value.__unicode__ = lambda _: hooks_dir + "\n"
|
self.assertEqual(git_hooks_dir("/blä"), os.path.abspath(os.path.join("/blä", hooks_dir)))
|
||||||
self.assertEqual(git_hooks_dir(u"/blä"), os.path.abspath(os.path.join(u"/blä", hooks_dir)))
|
|
||||||
|
|
||||||
git.assert_called_once_with("rev-parse", "--git-path", "hooks", _cwd=u"/blä")
|
git.assert_called_once_with("rev-parse", "--git-path", "hooks", _cwd="/blä")
|
||||||
|
|
|
@ -6,17 +6,11 @@ import dateutil
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
|
|
||||||
try:
|
from unittest.mock import patch, call
|
||||||
# python 2.x
|
|
||||||
from mock import patch, call
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest.mock import patch, call # pylint: disable=no-name-in-module, import-error
|
|
||||||
|
|
||||||
from gitlint.tests.base import BaseTestCase
|
from gitlint.tests.base import BaseTestCase
|
||||||
from gitlint.git import GitContext, GitCommit, GitContextError, LocalGitCommit, StagedLocalGitCommit, GitCommitMessage
|
from gitlint.git import GitContext, GitCommit, GitContextError, LocalGitCommit, StagedLocalGitCommit, GitCommitMessage
|
||||||
from gitlint.shell import ErrorReturnCode
|
from gitlint.shell import ErrorReturnCode
|
||||||
from gitlint.utils import ustr
|
|
||||||
|
|
||||||
|
|
||||||
class GitCommitTests(BaseTestCase):
|
class GitCommitTests(BaseTestCase):
|
||||||
|
@ -24,7 +18,7 @@ class GitCommitTests(BaseTestCase):
|
||||||
# Expected special_args passed to 'sh'
|
# Expected special_args passed to 'sh'
|
||||||
expected_sh_special_args = {
|
expected_sh_special_args = {
|
||||||
'_tty_out': False,
|
'_tty_out': False,
|
||||||
'_cwd': u"fåke/path"
|
'_cwd': "fåke/path"
|
||||||
}
|
}
|
||||||
|
|
||||||
@patch('gitlint.git.sh')
|
@patch('gitlint.git.sh')
|
||||||
|
@ -33,14 +27,14 @@ class GitCommitTests(BaseTestCase):
|
||||||
|
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
sample_sha,
|
sample_sha,
|
||||||
u"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
||||||
u"cömmit-title\n\ncömmit-body",
|
"cömmit-title\n\ncömmit-body",
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"file1.txt\npåth/to/file2.txt\n",
|
"file1.txt\npåth/to/file2.txt\n",
|
||||||
u"foöbar\n* hürdur\n"
|
"foöbar\n* hürdur\n"
|
||||||
]
|
]
|
||||||
|
|
||||||
context = GitContext.from_local_repository(u"fåke/path")
|
context = GitContext.from_local_repository("fåke/path")
|
||||||
# assert that commit info was read using git command
|
# assert that commit info was read using git command
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
call("log", "-1", "--pretty=%H", **self.expected_sh_special_args),
|
call("log", "-1", "--pretty=%H", **self.expected_sh_special_args),
|
||||||
|
@ -57,13 +51,13 @@ class GitCommitTests(BaseTestCase):
|
||||||
last_commit = context.commits[-1]
|
last_commit = context.commits[-1]
|
||||||
self.assertIsInstance(last_commit, LocalGitCommit)
|
self.assertIsInstance(last_commit, LocalGitCommit)
|
||||||
self.assertEqual(last_commit.sha, sample_sha)
|
self.assertEqual(last_commit.sha, sample_sha)
|
||||||
self.assertEqual(last_commit.message.title, u"cömmit-title")
|
self.assertEqual(last_commit.message.title, "cömmit-title")
|
||||||
self.assertEqual(last_commit.message.body, ["", u"cömmit-body"])
|
self.assertEqual(last_commit.message.body, ["", "cömmit-body"])
|
||||||
self.assertEqual(last_commit.author_name, u"test åuthor")
|
self.assertEqual(last_commit.author_name, "test åuthor")
|
||||||
self.assertEqual(last_commit.author_email, u"test-emåil@foo.com")
|
self.assertEqual(last_commit.author_email, "test-emåil@foo.com")
|
||||||
self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
|
self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
|
||||||
tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
|
tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
|
||||||
self.assertListEqual(last_commit.parents, [u"åbc"])
|
self.assertListEqual(last_commit.parents, ["åbc"])
|
||||||
self.assertFalse(last_commit.is_merge_commit)
|
self.assertFalse(last_commit.is_merge_commit)
|
||||||
self.assertFalse(last_commit.is_fixup_commit)
|
self.assertFalse(last_commit.is_fixup_commit)
|
||||||
self.assertFalse(last_commit.is_squash_commit)
|
self.assertFalse(last_commit.is_squash_commit)
|
||||||
|
@ -72,11 +66,11 @@ class GitCommitTests(BaseTestCase):
|
||||||
# First 2 'git log' calls should've happened at this point
|
# First 2 'git log' calls should've happened at this point
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls[:3])
|
self.assertListEqual(sh.git.mock_calls, expected_calls[:3])
|
||||||
|
|
||||||
self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
|
self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
|
||||||
# 'git diff-tree' should have happened at this point
|
# 'git diff-tree' should have happened at this point
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls[:4])
|
self.assertListEqual(sh.git.mock_calls, expected_calls[:4])
|
||||||
|
|
||||||
self.assertListEqual(last_commit.branches, [u"foöbar", u"hürdur"])
|
self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"])
|
||||||
# All expected calls should've happened at this point
|
# All expected calls should've happened at this point
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls)
|
self.assertListEqual(sh.git.mock_calls, expected_calls)
|
||||||
|
|
||||||
|
@ -86,14 +80,14 @@ class GitCommitTests(BaseTestCase):
|
||||||
|
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
sample_sha,
|
sample_sha,
|
||||||
u"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
||||||
u"cömmit-title\n\ncömmit-body",
|
"cömmit-title\n\ncömmit-body",
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"file1.txt\npåth/to/file2.txt\n",
|
"file1.txt\npåth/to/file2.txt\n",
|
||||||
u"foöbar\n* hürdur\n"
|
"foöbar\n* hürdur\n"
|
||||||
]
|
]
|
||||||
|
|
||||||
context = GitContext.from_local_repository(u"fåke/path", sample_sha)
|
context = GitContext.from_local_repository("fåke/path", sample_sha)
|
||||||
# assert that commit info was read using git command
|
# assert that commit info was read using git command
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
call("rev-list", sample_sha, **self.expected_sh_special_args),
|
call("rev-list", sample_sha, **self.expected_sh_special_args),
|
||||||
|
@ -110,13 +104,13 @@ class GitCommitTests(BaseTestCase):
|
||||||
last_commit = context.commits[-1]
|
last_commit = context.commits[-1]
|
||||||
self.assertIsInstance(last_commit, LocalGitCommit)
|
self.assertIsInstance(last_commit, LocalGitCommit)
|
||||||
self.assertEqual(last_commit.sha, sample_sha)
|
self.assertEqual(last_commit.sha, sample_sha)
|
||||||
self.assertEqual(last_commit.message.title, u"cömmit-title")
|
self.assertEqual(last_commit.message.title, "cömmit-title")
|
||||||
self.assertEqual(last_commit.message.body, ["", u"cömmit-body"])
|
self.assertEqual(last_commit.message.body, ["", "cömmit-body"])
|
||||||
self.assertEqual(last_commit.author_name, u"test åuthor")
|
self.assertEqual(last_commit.author_name, "test åuthor")
|
||||||
self.assertEqual(last_commit.author_email, u"test-emåil@foo.com")
|
self.assertEqual(last_commit.author_email, "test-emåil@foo.com")
|
||||||
self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
|
self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
|
||||||
tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
|
tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
|
||||||
self.assertListEqual(last_commit.parents, [u"åbc"])
|
self.assertListEqual(last_commit.parents, ["åbc"])
|
||||||
self.assertFalse(last_commit.is_merge_commit)
|
self.assertFalse(last_commit.is_merge_commit)
|
||||||
self.assertFalse(last_commit.is_fixup_commit)
|
self.assertFalse(last_commit.is_fixup_commit)
|
||||||
self.assertFalse(last_commit.is_squash_commit)
|
self.assertFalse(last_commit.is_squash_commit)
|
||||||
|
@ -125,11 +119,11 @@ class GitCommitTests(BaseTestCase):
|
||||||
# First 2 'git log' calls should've happened at this point
|
# First 2 'git log' calls should've happened at this point
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls[:3])
|
self.assertListEqual(sh.git.mock_calls, expected_calls[:3])
|
||||||
|
|
||||||
self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
|
self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
|
||||||
# 'git diff-tree' should have happened at this point
|
# 'git diff-tree' should have happened at this point
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls[:4])
|
self.assertListEqual(sh.git.mock_calls, expected_calls[:4])
|
||||||
|
|
||||||
self.assertListEqual(last_commit.branches, [u"foöbar", u"hürdur"])
|
self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"])
|
||||||
# All expected calls should've happened at this point
|
# All expected calls should've happened at this point
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls)
|
self.assertListEqual(sh.git.mock_calls, expected_calls)
|
||||||
|
|
||||||
|
@ -139,14 +133,14 @@ class GitCommitTests(BaseTestCase):
|
||||||
|
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
sample_sha,
|
sample_sha,
|
||||||
u"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc def\n"
|
"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc def\n"
|
||||||
u"Merge \"foo bår commit\"",
|
"Merge \"foo bår commit\"",
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"file1.txt\npåth/to/file2.txt\n",
|
"file1.txt\npåth/to/file2.txt\n",
|
||||||
u"foöbar\n* hürdur\n"
|
"foöbar\n* hürdur\n"
|
||||||
]
|
]
|
||||||
|
|
||||||
context = GitContext.from_local_repository(u"fåke/path")
|
context = GitContext.from_local_repository("fåke/path")
|
||||||
# assert that commit info was read using git command
|
# assert that commit info was read using git command
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
call("log", "-1", "--pretty=%H", **self.expected_sh_special_args),
|
call("log", "-1", "--pretty=%H", **self.expected_sh_special_args),
|
||||||
|
@ -163,13 +157,13 @@ class GitCommitTests(BaseTestCase):
|
||||||
last_commit = context.commits[-1]
|
last_commit = context.commits[-1]
|
||||||
self.assertIsInstance(last_commit, LocalGitCommit)
|
self.assertIsInstance(last_commit, LocalGitCommit)
|
||||||
self.assertEqual(last_commit.sha, sample_sha)
|
self.assertEqual(last_commit.sha, sample_sha)
|
||||||
self.assertEqual(last_commit.message.title, u"Merge \"foo bår commit\"")
|
self.assertEqual(last_commit.message.title, "Merge \"foo bår commit\"")
|
||||||
self.assertEqual(last_commit.message.body, [])
|
self.assertEqual(last_commit.message.body, [])
|
||||||
self.assertEqual(last_commit.author_name, u"test åuthor")
|
self.assertEqual(last_commit.author_name, "test åuthor")
|
||||||
self.assertEqual(last_commit.author_email, u"test-emåil@foo.com")
|
self.assertEqual(last_commit.author_email, "test-emåil@foo.com")
|
||||||
self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
|
self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
|
||||||
tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
|
tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
|
||||||
self.assertListEqual(last_commit.parents, [u"åbc", "def"])
|
self.assertListEqual(last_commit.parents, ["åbc", "def"])
|
||||||
self.assertTrue(last_commit.is_merge_commit)
|
self.assertTrue(last_commit.is_merge_commit)
|
||||||
self.assertFalse(last_commit.is_fixup_commit)
|
self.assertFalse(last_commit.is_fixup_commit)
|
||||||
self.assertFalse(last_commit.is_squash_commit)
|
self.assertFalse(last_commit.is_squash_commit)
|
||||||
|
@ -178,11 +172,11 @@ class GitCommitTests(BaseTestCase):
|
||||||
# First 2 'git log' calls should've happened at this point
|
# First 2 'git log' calls should've happened at this point
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls[:3])
|
self.assertListEqual(sh.git.mock_calls, expected_calls[:3])
|
||||||
|
|
||||||
self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
|
self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
|
||||||
# 'git diff-tree' should have happened at this point
|
# 'git diff-tree' should have happened at this point
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls[:4])
|
self.assertListEqual(sh.git.mock_calls, expected_calls[:4])
|
||||||
|
|
||||||
self.assertListEqual(last_commit.branches, [u"foöbar", u"hürdur"])
|
self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"])
|
||||||
# All expected calls should've happened at this point
|
# All expected calls should've happened at this point
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls)
|
self.assertListEqual(sh.git.mock_calls, expected_calls)
|
||||||
|
|
||||||
|
@ -194,14 +188,14 @@ class GitCommitTests(BaseTestCase):
|
||||||
|
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
sample_sha,
|
sample_sha,
|
||||||
u"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
|
||||||
u"{0}! \"foo bår commit\"".format(commit_type),
|
f"{commit_type}! \"foo bår commit\"",
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"file1.txt\npåth/to/file2.txt\n",
|
"file1.txt\npåth/to/file2.txt\n",
|
||||||
u"foöbar\n* hürdur\n"
|
"foöbar\n* hürdur\n"
|
||||||
]
|
]
|
||||||
|
|
||||||
context = GitContext.from_local_repository(u"fåke/path")
|
context = GitContext.from_local_repository("fåke/path")
|
||||||
# assert that commit info was read using git command
|
# assert that commit info was read using git command
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
call("log", "-1", "--pretty=%H", **self.expected_sh_special_args),
|
call("log", "-1", "--pretty=%H", **self.expected_sh_special_args),
|
||||||
|
@ -218,13 +212,13 @@ class GitCommitTests(BaseTestCase):
|
||||||
last_commit = context.commits[-1]
|
last_commit = context.commits[-1]
|
||||||
self.assertIsInstance(last_commit, LocalGitCommit)
|
self.assertIsInstance(last_commit, LocalGitCommit)
|
||||||
self.assertEqual(last_commit.sha, sample_sha)
|
self.assertEqual(last_commit.sha, sample_sha)
|
||||||
self.assertEqual(last_commit.message.title, u"{0}! \"foo bår commit\"".format(commit_type))
|
self.assertEqual(last_commit.message.title, f"{commit_type}! \"foo bår commit\"")
|
||||||
self.assertEqual(last_commit.message.body, [])
|
self.assertEqual(last_commit.message.body, [])
|
||||||
self.assertEqual(last_commit.author_name, u"test åuthor")
|
self.assertEqual(last_commit.author_name, "test åuthor")
|
||||||
self.assertEqual(last_commit.author_email, u"test-emåil@foo.com")
|
self.assertEqual(last_commit.author_email, "test-emåil@foo.com")
|
||||||
self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
|
self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
|
||||||
tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
|
tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
|
||||||
self.assertListEqual(last_commit.parents, [u"åbc"])
|
self.assertListEqual(last_commit.parents, ["åbc"])
|
||||||
|
|
||||||
# First 2 'git log' calls should've happened at this point
|
# First 2 'git log' calls should've happened at this point
|
||||||
self.assertEqual(sh.git.mock_calls, expected_calls[:3])
|
self.assertEqual(sh.git.mock_calls, expected_calls[:3])
|
||||||
|
@ -236,13 +230,13 @@ class GitCommitTests(BaseTestCase):
|
||||||
|
|
||||||
self.assertFalse(last_commit.is_merge_commit)
|
self.assertFalse(last_commit.is_merge_commit)
|
||||||
self.assertFalse(last_commit.is_revert_commit)
|
self.assertFalse(last_commit.is_revert_commit)
|
||||||
self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
|
self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
|
||||||
|
|
||||||
self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
|
self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
|
||||||
# 'git diff-tree' should have happened at this point
|
# 'git diff-tree' should have happened at this point
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls[:4])
|
self.assertListEqual(sh.git.mock_calls, expected_calls[:4])
|
||||||
|
|
||||||
self.assertListEqual(last_commit.branches, [u"foöbar", u"hürdur"])
|
self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"])
|
||||||
# All expected calls should've happened at this point
|
# All expected calls should've happened at this point
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls)
|
self.assertListEqual(sh.git.mock_calls, expected_calls)
|
||||||
|
|
||||||
|
@ -250,27 +244,27 @@ class GitCommitTests(BaseTestCase):
|
||||||
|
|
||||||
@patch("gitlint.git.git_commentchar")
|
@patch("gitlint.git.git_commentchar")
|
||||||
def test_from_commit_msg_full(self, commentchar):
|
def test_from_commit_msg_full(self, commentchar):
|
||||||
commentchar.return_value = u"#"
|
commentchar.return_value = "#"
|
||||||
gitcontext = GitContext.from_commit_msg(self.get_sample("commit_message/sample1"))
|
gitcontext = GitContext.from_commit_msg(self.get_sample("commit_message/sample1"))
|
||||||
|
|
||||||
expected_title = u"Commit title contåining 'WIP', as well as trailing punctuation."
|
expected_title = "Commit title contåining 'WIP', as well as trailing punctuation."
|
||||||
expected_body = ["This line should be empty",
|
expected_body = ["This line should be empty",
|
||||||
"This is the first line of the commit message body and it is meant to test a " +
|
"This is the first line of the commit message body and it is meant to test a " +
|
||||||
"line that exceeds the maximum line length of 80 characters.",
|
"line that exceeds the maximum line length of 80 characters.",
|
||||||
u"This line has a tråiling space. ",
|
"This line has a tråiling space. ",
|
||||||
"This line has a trailing tab.\t"]
|
"This line has a trailing tab.\t"]
|
||||||
expected_full = expected_title + "\n" + "\n".join(expected_body)
|
expected_full = expected_title + "\n" + "\n".join(expected_body)
|
||||||
expected_original = expected_full + (
|
expected_original = expected_full + (
|
||||||
u"\n# This is a cömmented line\n"
|
"\n# This is a cömmented line\n"
|
||||||
u"# ------------------------ >8 ------------------------\n"
|
"# ------------------------ >8 ------------------------\n"
|
||||||
u"# Anything after this line should be cleaned up\n"
|
"# Anything after this line should be cleaned up\n"
|
||||||
u"# this line appears on `git commit -v` command\n"
|
"# this line appears on `git commit -v` command\n"
|
||||||
u"diff --git a/gitlint/tests/samples/commit_message/sample1 "
|
"diff --git a/gitlint/tests/samples/commit_message/sample1 "
|
||||||
u"b/gitlint/tests/samples/commit_message/sample1\n"
|
"b/gitlint/tests/samples/commit_message/sample1\n"
|
||||||
u"index 82dbe7f..ae71a14 100644\n"
|
"index 82dbe7f..ae71a14 100644\n"
|
||||||
u"--- a/gitlint/tests/samples/commit_message/sample1\n"
|
"--- a/gitlint/tests/samples/commit_message/sample1\n"
|
||||||
u"+++ b/gitlint/tests/samples/commit_message/sample1\n"
|
"+++ b/gitlint/tests/samples/commit_message/sample1\n"
|
||||||
u"@@ -1 +1 @@\n"
|
"@@ -1 +1 @@\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
commit = gitcontext.commits[-1]
|
commit = gitcontext.commits[-1]
|
||||||
|
@ -297,10 +291,10 @@ class GitCommitTests(BaseTestCase):
|
||||||
|
|
||||||
self.assertIsInstance(commit, GitCommit)
|
self.assertIsInstance(commit, GitCommit)
|
||||||
self.assertFalse(isinstance(commit, LocalGitCommit))
|
self.assertFalse(isinstance(commit, LocalGitCommit))
|
||||||
self.assertEqual(commit.message.title, u"Just a title contåining WIP")
|
self.assertEqual(commit.message.title, "Just a title contåining WIP")
|
||||||
self.assertEqual(commit.message.body, [])
|
self.assertEqual(commit.message.body, [])
|
||||||
self.assertEqual(commit.message.full, u"Just a title contåining WIP")
|
self.assertEqual(commit.message.full, "Just a title contåining WIP")
|
||||||
self.assertEqual(commit.message.original, u"Just a title contåining WIP")
|
self.assertEqual(commit.message.original, "Just a title contåining WIP")
|
||||||
self.assertEqual(commit.author_name, None)
|
self.assertEqual(commit.author_name, None)
|
||||||
self.assertEqual(commit.author_email, None)
|
self.assertEqual(commit.author_email, None)
|
||||||
self.assertListEqual(commit.parents, [])
|
self.assertListEqual(commit.parents, [])
|
||||||
|
@ -334,16 +328,16 @@ class GitCommitTests(BaseTestCase):
|
||||||
|
|
||||||
@patch("gitlint.git.git_commentchar")
|
@patch("gitlint.git.git_commentchar")
|
||||||
def test_from_commit_msg_comment(self, commentchar):
|
def test_from_commit_msg_comment(self, commentchar):
|
||||||
commentchar.return_value = u"#"
|
commentchar.return_value = "#"
|
||||||
gitcontext = GitContext.from_commit_msg(u"Tïtle\n\nBödy 1\n#Cömment\nBody 2")
|
gitcontext = GitContext.from_commit_msg("Tïtle\n\nBödy 1\n#Cömment\nBody 2")
|
||||||
commit = gitcontext.commits[-1]
|
commit = gitcontext.commits[-1]
|
||||||
|
|
||||||
self.assertIsInstance(commit, GitCommit)
|
self.assertIsInstance(commit, GitCommit)
|
||||||
self.assertFalse(isinstance(commit, LocalGitCommit))
|
self.assertFalse(isinstance(commit, LocalGitCommit))
|
||||||
self.assertEqual(commit.message.title, u"Tïtle")
|
self.assertEqual(commit.message.title, "Tïtle")
|
||||||
self.assertEqual(commit.message.body, ["", u"Bödy 1", "Body 2"])
|
self.assertEqual(commit.message.body, ["", "Bödy 1", "Body 2"])
|
||||||
self.assertEqual(commit.message.full, u"Tïtle\n\nBödy 1\nBody 2")
|
self.assertEqual(commit.message.full, "Tïtle\n\nBödy 1\nBody 2")
|
||||||
self.assertEqual(commit.message.original, u"Tïtle\n\nBödy 1\n#Cömment\nBody 2")
|
self.assertEqual(commit.message.original, "Tïtle\n\nBödy 1\n#Cömment\nBody 2")
|
||||||
self.assertEqual(commit.author_name, None)
|
self.assertEqual(commit.author_name, None)
|
||||||
self.assertEqual(commit.author_email, None)
|
self.assertEqual(commit.author_email, None)
|
||||||
self.assertEqual(commit.date, None)
|
self.assertEqual(commit.date, None)
|
||||||
|
@ -402,7 +396,7 @@ class GitCommitTests(BaseTestCase):
|
||||||
def test_from_commit_msg_fixup_squash_commit(self):
|
def test_from_commit_msg_fixup_squash_commit(self):
|
||||||
commit_types = ["fixup", "squash"]
|
commit_types = ["fixup", "squash"]
|
||||||
for commit_type in commit_types:
|
for commit_type in commit_types:
|
||||||
commit_msg = "{0}! Test message".format(commit_type)
|
commit_msg = f"{commit_type}! Test message"
|
||||||
gitcontext = GitContext.from_commit_msg(commit_msg)
|
gitcontext = GitContext.from_commit_msg(commit_msg)
|
||||||
commit = gitcontext.commits[-1]
|
commit = gitcontext.commits[-1]
|
||||||
|
|
||||||
|
@ -431,16 +425,16 @@ class GitCommitTests(BaseTestCase):
|
||||||
# StagedLocalGitCommit()
|
# StagedLocalGitCommit()
|
||||||
|
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"test åuthor\n", # git config --get user.name
|
"test åuthor\n", # git config --get user.name
|
||||||
u"test-emåil@foo.com\n", # git config --get user.email
|
"test-emåil@foo.com\n", # git config --get user.email
|
||||||
u"my-brånch\n", # git rev-parse --abbrev-ref HEAD
|
"my-brånch\n", # git rev-parse --abbrev-ref HEAD
|
||||||
u"file1.txt\npåth/to/file2.txt\n",
|
"file1.txt\npåth/to/file2.txt\n",
|
||||||
]
|
]
|
||||||
now.side_effect = [arrow.get("2020-02-19T12:18:46.675182+01:00")]
|
now.side_effect = [arrow.get("2020-02-19T12:18:46.675182+01:00")]
|
||||||
|
|
||||||
# We use a fixup commit, just to test a non-default path
|
# We use a fixup commit, just to test a non-default path
|
||||||
context = GitContext.from_staged_commit(u"fixup! Foōbar 123\n\ncömmit-body\n", u"fåke/path")
|
context = GitContext.from_staged_commit("fixup! Foōbar 123\n\ncömmit-body\n", "fåke/path")
|
||||||
|
|
||||||
# git calls we're expexting
|
# git calls we're expexting
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
|
@ -454,15 +448,15 @@ class GitCommitTests(BaseTestCase):
|
||||||
last_commit = context.commits[-1]
|
last_commit = context.commits[-1]
|
||||||
self.assertIsInstance(last_commit, StagedLocalGitCommit)
|
self.assertIsInstance(last_commit, StagedLocalGitCommit)
|
||||||
self.assertIsNone(last_commit.sha, None)
|
self.assertIsNone(last_commit.sha, None)
|
||||||
self.assertEqual(last_commit.message.title, u"fixup! Foōbar 123")
|
self.assertEqual(last_commit.message.title, "fixup! Foōbar 123")
|
||||||
self.assertEqual(last_commit.message.body, ["", u"cömmit-body"])
|
self.assertEqual(last_commit.message.body, ["", "cömmit-body"])
|
||||||
# Only `git config --get core.commentchar` should've happened up until this point
|
# Only `git config --get core.commentchar` should've happened up until this point
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls[0:1])
|
self.assertListEqual(sh.git.mock_calls, expected_calls[0:1])
|
||||||
|
|
||||||
self.assertEqual(last_commit.author_name, u"test åuthor")
|
self.assertEqual(last_commit.author_name, "test åuthor")
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls[0:2])
|
self.assertListEqual(sh.git.mock_calls, expected_calls[0:2])
|
||||||
|
|
||||||
self.assertEqual(last_commit.author_email, u"test-emåil@foo.com")
|
self.assertEqual(last_commit.author_email, "test-emåil@foo.com")
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls[0:3])
|
self.assertListEqual(sh.git.mock_calls, expected_calls[0:3])
|
||||||
|
|
||||||
self.assertEqual(last_commit.date, datetime.datetime(2020, 2, 19, 12, 18, 46,
|
self.assertEqual(last_commit.date, datetime.datetime(2020, 2, 19, 12, 18, 46,
|
||||||
|
@ -475,10 +469,10 @@ class GitCommitTests(BaseTestCase):
|
||||||
self.assertFalse(last_commit.is_squash_commit)
|
self.assertFalse(last_commit.is_squash_commit)
|
||||||
self.assertFalse(last_commit.is_revert_commit)
|
self.assertFalse(last_commit.is_revert_commit)
|
||||||
|
|
||||||
self.assertListEqual(last_commit.branches, [u"my-brånch"])
|
self.assertListEqual(last_commit.branches, ["my-brånch"])
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls[0:4])
|
self.assertListEqual(sh.git.mock_calls, expected_calls[0:4])
|
||||||
|
|
||||||
self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
|
self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
|
||||||
self.assertListEqual(sh.git.mock_calls, expected_calls[0:5])
|
self.assertListEqual(sh.git.mock_calls, expected_calls[0:5])
|
||||||
|
|
||||||
@patch('gitlint.git.sh')
|
@patch('gitlint.git.sh')
|
||||||
|
@ -486,32 +480,32 @@ class GitCommitTests(BaseTestCase):
|
||||||
# StagedLocalGitCommit()
|
# StagedLocalGitCommit()
|
||||||
|
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
ErrorReturnCode('git config --get user.name', b"", b""),
|
ErrorReturnCode('git config --get user.name', b"", b""),
|
||||||
]
|
]
|
||||||
|
|
||||||
expected_msg = "Missing git configuration: please set user.name"
|
expected_msg = "Missing git configuration: please set user.name"
|
||||||
with self.assertRaisesMessage(GitContextError, expected_msg):
|
with self.assertRaisesMessage(GitContextError, expected_msg):
|
||||||
ctx = GitContext.from_staged_commit(u"Foōbar 123\n\ncömmit-body\n", u"fåke/path")
|
ctx = GitContext.from_staged_commit("Foōbar 123\n\ncömmit-body\n", "fåke/path")
|
||||||
[ustr(commit) for commit in ctx.commits]
|
[str(commit) for commit in ctx.commits]
|
||||||
|
|
||||||
@patch('gitlint.git.sh')
|
@patch('gitlint.git.sh')
|
||||||
def test_staged_commit_with_missing_email(self, sh):
|
def test_staged_commit_with_missing_email(self, sh):
|
||||||
# StagedLocalGitCommit()
|
# StagedLocalGitCommit()
|
||||||
|
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"test åuthor\n", # git config --get user.name
|
"test åuthor\n", # git config --get user.name
|
||||||
ErrorReturnCode('git config --get user.name', b"", b""),
|
ErrorReturnCode('git config --get user.name', b"", b""),
|
||||||
]
|
]
|
||||||
|
|
||||||
expected_msg = "Missing git configuration: please set user.email"
|
expected_msg = "Missing git configuration: please set user.email"
|
||||||
with self.assertRaisesMessage(GitContextError, expected_msg):
|
with self.assertRaisesMessage(GitContextError, expected_msg):
|
||||||
ctx = GitContext.from_staged_commit(u"Foōbar 123\n\ncömmit-body\n", u"fåke/path")
|
ctx = GitContext.from_staged_commit("Foōbar 123\n\ncömmit-body\n", "fåke/path")
|
||||||
[ustr(commit) for commit in ctx.commits]
|
[str(commit) for commit in ctx.commits]
|
||||||
|
|
||||||
def test_gitcommitmessage_equality(self):
|
def test_gitcommitmessage_equality(self):
|
||||||
commit_message1 = GitCommitMessage(GitContext(), u"tëst\n\nfoo", u"tëst\n\nfoo", u"tēst", ["", u"föo"])
|
commit_message1 = GitCommitMessage(GitContext(), "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"])
|
||||||
attrs = ['original', 'full', 'title', 'body']
|
attrs = ['original', 'full', 'title', 'body']
|
||||||
self.object_equality_test(commit_message1, attrs, {"context": commit_message1.context})
|
self.object_equality_test(commit_message1, attrs, {"context": commit_message1.context})
|
||||||
|
|
||||||
|
@ -519,20 +513,20 @@ class GitCommitTests(BaseTestCase):
|
||||||
def test_gitcommit_equality(self, git):
|
def test_gitcommit_equality(self, git):
|
||||||
# git will be called to setup the context (commentchar and current_branch), just return the same value
|
# git will be called to setup the context (commentchar and current_branch), just return the same value
|
||||||
# This only matters to test gitcontext equality, not gitcommit equality
|
# This only matters to test gitcontext equality, not gitcommit equality
|
||||||
git.return_value = u"foöbar"
|
git.return_value = "foöbar"
|
||||||
|
|
||||||
# Test simple equality case
|
# Test simple equality case
|
||||||
now = datetime.datetime.utcnow()
|
now = datetime.datetime.utcnow()
|
||||||
context1 = GitContext()
|
context1 = GitContext()
|
||||||
commit_message1 = GitCommitMessage(context1, u"tëst\n\nfoo", u"tëst\n\nfoo", u"tēst", ["", u"föo"])
|
commit_message1 = GitCommitMessage(context1, "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"])
|
||||||
commit1 = GitCommit(context1, commit_message1, u"shä", now, u"Jöhn Smith", u"jöhn.smith@test.com", None,
|
commit1 = GitCommit(context1, commit_message1, "shä", now, "Jöhn Smith", "jöhn.smith@test.com", None,
|
||||||
[u"föo/bar"], [u"brånch1", u"brånch2"])
|
["föo/bar"], ["brånch1", "brånch2"])
|
||||||
context1.commits = [commit1]
|
context1.commits = [commit1]
|
||||||
|
|
||||||
context2 = GitContext()
|
context2 = GitContext()
|
||||||
commit_message2 = GitCommitMessage(context2, u"tëst\n\nfoo", u"tëst\n\nfoo", u"tēst", ["", u"föo"])
|
commit_message2 = GitCommitMessage(context2, "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"])
|
||||||
commit2 = GitCommit(context2, commit_message1, u"shä", now, u"Jöhn Smith", u"jöhn.smith@test.com", None,
|
commit2 = GitCommit(context2, commit_message1, "shä", now, "Jöhn Smith", "jöhn.smith@test.com", None,
|
||||||
[u"föo/bar"], [u"brånch1", u"brånch2"])
|
["föo/bar"], ["brånch1", "brånch2"])
|
||||||
context2.commits = [commit2]
|
context2.commits = [commit2]
|
||||||
|
|
||||||
self.assertEqual(context1, context2)
|
self.assertEqual(context1, context2)
|
||||||
|
@ -547,8 +541,8 @@ class GitCommitTests(BaseTestCase):
|
||||||
self.object_equality_test(commit1, kwargs.keys(), {"context": commit1.context})
|
self.object_equality_test(commit1, kwargs.keys(), {"context": commit1.context})
|
||||||
|
|
||||||
# Check that the is_* attributes that are affected by the commit message affect equality
|
# Check that the is_* attributes that are affected by the commit message affect equality
|
||||||
special_messages = {'is_merge_commit': u"Merge: foöbar", 'is_fixup_commit': u"fixup! foöbar",
|
special_messages = {'is_merge_commit': "Merge: foöbar", 'is_fixup_commit': "fixup! foöbar",
|
||||||
'is_squash_commit': u"squash! foöbar", 'is_revert_commit': u"Revert: foöbar"}
|
'is_squash_commit': "squash! foöbar", 'is_revert_commit': "Revert: foöbar"}
|
||||||
for key in special_messages:
|
for key in special_messages:
|
||||||
kwargs_copy = copy.deepcopy(kwargs)
|
kwargs_copy = copy.deepcopy(kwargs)
|
||||||
clone1 = GitCommit(context=commit1.context, **kwargs_copy)
|
clone1 = GitCommit(context=commit1.context, **kwargs_copy)
|
||||||
|
@ -556,16 +550,16 @@ class GitCommitTests(BaseTestCase):
|
||||||
self.assertTrue(getattr(clone1, key))
|
self.assertTrue(getattr(clone1, key))
|
||||||
|
|
||||||
clone2 = GitCommit(context=commit1.context, **kwargs_copy)
|
clone2 = GitCommit(context=commit1.context, **kwargs_copy)
|
||||||
clone2.message = GitCommitMessage.from_full_message(context1, u"foöbar")
|
clone2.message = GitCommitMessage.from_full_message(context1, "foöbar")
|
||||||
self.assertNotEqual(clone1, clone2)
|
self.assertNotEqual(clone1, clone2)
|
||||||
|
|
||||||
@patch("gitlint.git.git_commentchar")
|
@patch("gitlint.git.git_commentchar")
|
||||||
def test_commit_msg_custom_commentchar(self, patched):
|
def test_commit_msg_custom_commentchar(self, patched):
|
||||||
patched.return_value = u"ä"
|
patched.return_value = "ä"
|
||||||
context = GitContext()
|
context = GitContext()
|
||||||
message = GitCommitMessage.from_full_message(context, u"Tïtle\n\nBödy 1\näCömment\nBody 2")
|
message = GitCommitMessage.from_full_message(context, "Tïtle\n\nBödy 1\näCömment\nBody 2")
|
||||||
|
|
||||||
self.assertEqual(message.title, u"Tïtle")
|
self.assertEqual(message.title, "Tïtle")
|
||||||
self.assertEqual(message.body, ["", u"Bödy 1", "Body 2"])
|
self.assertEqual(message.body, ["", "Bödy 1", "Body 2"])
|
||||||
self.assertEqual(message.full, u"Tïtle\n\nBödy 1\nBody 2")
|
self.assertEqual(message.full, "Tïtle\n\nBödy 1\nBody 2")
|
||||||
self.assertEqual(message.original, u"Tïtle\n\nBödy 1\näCömment\nBody 2")
|
self.assertEqual(message.original, "Tïtle\n\nBödy 1\näCömment\nBody 2")
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
try:
|
from unittest.mock import patch, call
|
||||||
# python 2.x
|
|
||||||
from mock import patch, call
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest.mock import patch, call # pylint: disable=no-name-in-module, import-error
|
|
||||||
|
|
||||||
from gitlint.tests.base import BaseTestCase
|
from gitlint.tests.base import BaseTestCase
|
||||||
from gitlint.git import GitContext
|
from gitlint.git import GitContext
|
||||||
|
@ -16,15 +11,15 @@ class GitContextTests(BaseTestCase):
|
||||||
# Expected special_args passed to 'sh'
|
# Expected special_args passed to 'sh'
|
||||||
expected_sh_special_args = {
|
expected_sh_special_args = {
|
||||||
'_tty_out': False,
|
'_tty_out': False,
|
||||||
'_cwd': u"fåke/path"
|
'_cwd': "fåke/path"
|
||||||
}
|
}
|
||||||
|
|
||||||
@patch('gitlint.git.sh')
|
@patch('gitlint.git.sh')
|
||||||
def test_gitcontext(self, sh):
|
def test_gitcontext(self, sh):
|
||||||
|
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
u"#", # git config --get core.commentchar
|
"#", # git config --get core.commentchar
|
||||||
u"\nfoöbar\n"
|
"\nfoöbar\n"
|
||||||
]
|
]
|
||||||
|
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
|
@ -32,58 +27,58 @@ class GitContextTests(BaseTestCase):
|
||||||
call("rev-parse", "--abbrev-ref", "HEAD", **self.expected_sh_special_args)
|
call("rev-parse", "--abbrev-ref", "HEAD", **self.expected_sh_special_args)
|
||||||
]
|
]
|
||||||
|
|
||||||
context = GitContext(u"fåke/path")
|
context = GitContext("fåke/path")
|
||||||
self.assertEqual(sh.git.mock_calls, [])
|
self.assertEqual(sh.git.mock_calls, [])
|
||||||
|
|
||||||
# gitcontext.comment_branch
|
# gitcontext.comment_branch
|
||||||
self.assertEqual(context.commentchar, u"#")
|
self.assertEqual(context.commentchar, "#")
|
||||||
self.assertEqual(sh.git.mock_calls, expected_calls[0:1])
|
self.assertEqual(sh.git.mock_calls, expected_calls[0:1])
|
||||||
|
|
||||||
# gitcontext.current_branch
|
# gitcontext.current_branch
|
||||||
self.assertEqual(context.current_branch, u"foöbar")
|
self.assertEqual(context.current_branch, "foöbar")
|
||||||
self.assertEqual(sh.git.mock_calls, expected_calls)
|
self.assertEqual(sh.git.mock_calls, expected_calls)
|
||||||
|
|
||||||
@patch('gitlint.git.sh')
|
@patch('gitlint.git.sh')
|
||||||
def test_gitcontext_equality(self, sh):
|
def test_gitcontext_equality(self, sh):
|
||||||
|
|
||||||
sh.git.side_effect = [
|
sh.git.side_effect = [
|
||||||
u"û\n", # context1: git config --get core.commentchar
|
"û\n", # context1: git config --get core.commentchar
|
||||||
u"û\n", # context2: git config --get core.commentchar
|
"û\n", # context2: git config --get core.commentchar
|
||||||
u"my-brånch\n", # context1: git rev-parse --abbrev-ref HEAD
|
"my-brånch\n", # context1: git rev-parse --abbrev-ref HEAD
|
||||||
u"my-brånch\n", # context2: git rev-parse --abbrev-ref HEAD
|
"my-brånch\n", # context2: git rev-parse --abbrev-ref HEAD
|
||||||
]
|
]
|
||||||
|
|
||||||
context1 = GitContext(u"fåke/path")
|
context1 = GitContext("fåke/path")
|
||||||
context1.commits = [u"fōo", u"bår"] # we don't need real commits to check for equality
|
context1.commits = ["fōo", "bår"] # we don't need real commits to check for equality
|
||||||
|
|
||||||
context2 = GitContext(u"fåke/path")
|
context2 = GitContext("fåke/path")
|
||||||
context2.commits = [u"fōo", u"bår"]
|
context2.commits = ["fōo", "bår"]
|
||||||
self.assertEqual(context1, context2)
|
self.assertEqual(context1, context2)
|
||||||
|
|
||||||
# INEQUALITY
|
# INEQUALITY
|
||||||
# Different commits
|
# Different commits
|
||||||
context2.commits = [u"hür", u"dür"]
|
context2.commits = ["hür", "dür"]
|
||||||
self.assertNotEqual(context1, context2)
|
self.assertNotEqual(context1, context2)
|
||||||
|
|
||||||
# Different repository_path
|
# Different repository_path
|
||||||
context2.commits = context1.commits
|
context2.commits = context1.commits
|
||||||
context2.repository_path = u"ōther/path"
|
context2.repository_path = "ōther/path"
|
||||||
self.assertNotEqual(context1, context2)
|
self.assertNotEqual(context1, context2)
|
||||||
|
|
||||||
# Different comment_char
|
# Different comment_char
|
||||||
context3 = GitContext(u"fåke/path")
|
context3 = GitContext("fåke/path")
|
||||||
context3.commits = [u"fōo", u"bår"]
|
context3.commits = ["fōo", "bår"]
|
||||||
sh.git.side_effect = ([
|
sh.git.side_effect = ([
|
||||||
u"ç\n", # context3: git config --get core.commentchar
|
"ç\n", # context3: git config --get core.commentchar
|
||||||
u"my-brånch\n" # context3: git rev-parse --abbrev-ref HEAD
|
"my-brånch\n" # context3: git rev-parse --abbrev-ref HEAD
|
||||||
])
|
])
|
||||||
self.assertNotEqual(context1, context3)
|
self.assertNotEqual(context1, context3)
|
||||||
|
|
||||||
# Different current_branch
|
# Different current_branch
|
||||||
context4 = GitContext(u"fåke/path")
|
context4 = GitContext("fåke/path")
|
||||||
context4.commits = [u"fōo", u"bår"]
|
context4.commits = ["fōo", "bår"]
|
||||||
sh.git.side_effect = ([
|
sh.git.side_effect = ([
|
||||||
u"û\n", # context4: git config --get core.commentchar
|
"û\n", # context4: git config --get core.commentchar
|
||||||
u"different-brånch\n" # context4: git rev-parse --abbrev-ref HEAD
|
"different-brånch\n" # context4: git rev-parse --abbrev-ref HEAD
|
||||||
])
|
])
|
||||||
self.assertNotEqual(context1, context4)
|
self.assertNotEqual(context1, context4)
|
||||||
|
|
|
@ -8,65 +8,65 @@ class BodyRuleTests(BaseTestCase):
|
||||||
rule = rules.BodyMaxLineLength()
|
rule = rules.BodyMaxLineLength()
|
||||||
|
|
||||||
# assert no error
|
# assert no error
|
||||||
violation = rule.validate(u"å" * 80, None)
|
violation = rule.validate("å" * 80, None)
|
||||||
self.assertIsNone(violation)
|
self.assertIsNone(violation)
|
||||||
|
|
||||||
# assert error on line length > 80
|
# assert error on line length > 80
|
||||||
expected_violation = rules.RuleViolation("B1", "Line exceeds max length (81>80)", u"å" * 81)
|
expected_violation = rules.RuleViolation("B1", "Line exceeds max length (81>80)", "å" * 81)
|
||||||
violations = rule.validate(u"å" * 81, None)
|
violations = rule.validate("å" * 81, None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# set line length to 120, and check no violation on length 73
|
# set line length to 120, and check no violation on length 73
|
||||||
rule = rules.BodyMaxLineLength({'line-length': 120})
|
rule = rules.BodyMaxLineLength({'line-length': 120})
|
||||||
violations = rule.validate(u"å" * 73, None)
|
violations = rule.validate("å" * 73, None)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert raise on 121
|
# assert raise on 121
|
||||||
expected_violation = rules.RuleViolation("B1", "Line exceeds max length (121>120)", u"å" * 121)
|
expected_violation = rules.RuleViolation("B1", "Line exceeds max length (121>120)", "å" * 121)
|
||||||
violations = rule.validate(u"å" * 121, None)
|
violations = rule.validate("å" * 121, None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
def test_trailing_whitespace(self):
|
def test_trailing_whitespace(self):
|
||||||
rule = rules.BodyTrailingWhitespace()
|
rule = rules.BodyTrailingWhitespace()
|
||||||
|
|
||||||
# assert no error
|
# assert no error
|
||||||
violations = rule.validate(u"å", None)
|
violations = rule.validate("å", None)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# trailing space
|
# trailing space
|
||||||
expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", u"å ")
|
expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", "å ")
|
||||||
violations = rule.validate(u"å ", None)
|
violations = rule.validate("å ", None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# trailing tab
|
# trailing tab
|
||||||
expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", u"å\t")
|
expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", "å\t")
|
||||||
violations = rule.validate(u"å\t", None)
|
violations = rule.validate("å\t", None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
def test_hard_tabs(self):
|
def test_hard_tabs(self):
|
||||||
rule = rules.BodyHardTab()
|
rule = rules.BodyHardTab()
|
||||||
|
|
||||||
# assert no error
|
# assert no error
|
||||||
violations = rule.validate(u"This is ã test", None)
|
violations = rule.validate("This is ã test", None)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# contains hard tab
|
# contains hard tab
|
||||||
expected_violation = rules.RuleViolation("B3", "Line contains hard tab characters (\\t)", u"This is å\ttest")
|
expected_violation = rules.RuleViolation("B3", "Line contains hard tab characters (\\t)", "This is å\ttest")
|
||||||
violations = rule.validate(u"This is å\ttest", None)
|
violations = rule.validate("This is å\ttest", None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
def test_body_first_line_empty(self):
|
def test_body_first_line_empty(self):
|
||||||
rule = rules.BodyFirstLineEmpty()
|
rule = rules.BodyFirstLineEmpty()
|
||||||
|
|
||||||
# assert no error
|
# assert no error
|
||||||
commit = self.gitcommit(u"Tïtle\n\nThis is the secōnd body line")
|
commit = self.gitcommit("Tïtle\n\nThis is the secōnd body line")
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# second line not empty
|
# second line not empty
|
||||||
expected_violation = rules.RuleViolation("B4", "Second line is not empty", u"nöt empty", 2)
|
expected_violation = rules.RuleViolation("B4", "Second line is not empty", "nöt empty", 2)
|
||||||
|
|
||||||
commit = self.gitcommit(u"Tïtle\nnöt empty\nThis is the secönd body line")
|
commit = self.gitcommit("Tïtle\nnöt empty\nThis is the secönd body line")
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
|
@ -80,34 +80,34 @@ class BodyRuleTests(BaseTestCase):
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert no error - no body
|
# assert no error - no body
|
||||||
commit = self.gitcommit(u"Tïtle\n")
|
commit = self.gitcommit("Tïtle\n")
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# body is too short
|
# body is too short
|
||||||
expected_violation = rules.RuleViolation("B5", "Body message is too short (8<20)", u"töoshort", 3)
|
expected_violation = rules.RuleViolation("B5", "Body message is too short (8<20)", "töoshort", 3)
|
||||||
|
|
||||||
commit = self.gitcommit(u"Tïtle\n\ntöoshort\n")
|
commit = self.gitcommit("Tïtle\n\ntöoshort\n")
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# assert error - short across multiple lines
|
# assert error - short across multiple lines
|
||||||
expected_violation = rules.RuleViolation("B5", "Body message is too short (11<20)", u"secöndthïrd", 3)
|
expected_violation = rules.RuleViolation("B5", "Body message is too short (11<20)", "secöndthïrd", 3)
|
||||||
commit = self.gitcommit(u"Tïtle\n\nsecönd\nthïrd\n")
|
commit = self.gitcommit("Tïtle\n\nsecönd\nthïrd\n")
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# set line length to 120, and check violation on length 21
|
# set line length to 120, and check violation on length 21
|
||||||
expected_violation = rules.RuleViolation("B5", "Body message is too short (21<120)", u"å" * 21, 3)
|
expected_violation = rules.RuleViolation("B5", "Body message is too short (21<120)", "å" * 21, 3)
|
||||||
|
|
||||||
rule = rules.BodyMinLength({'min-length': 120})
|
rule = rules.BodyMinLength({'min-length': 120})
|
||||||
commit = self.gitcommit(u"Title\n\n%s\n" % (u"å" * 21))
|
commit = self.gitcommit("Title\n\n%s\n" % ("å" * 21))
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# Make sure we don't get the error if the body-length is exactly the min-length
|
# Make sure we don't get the error if the body-length is exactly the min-length
|
||||||
rule = rules.BodyMinLength({'min-length': 8})
|
rule = rules.BodyMinLength({'min-length': 8})
|
||||||
commit = self.gitcommit(u"Tïtle\n\n%s\n" % (u"å" * 8))
|
commit = self.gitcommit("Tïtle\n\n%s\n" % ("å" * 8))
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
|
@ -115,14 +115,14 @@ class BodyRuleTests(BaseTestCase):
|
||||||
rule = rules.BodyMissing()
|
rule = rules.BodyMissing()
|
||||||
|
|
||||||
# assert no error - body is present
|
# assert no error - body is present
|
||||||
commit = self.gitcommit(u"Tïtle\n\nThis ïs the first body line\n")
|
commit = self.gitcommit("Tïtle\n\nThis ïs the first body line\n")
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# body is too short
|
# body is too short
|
||||||
expected_violation = rules.RuleViolation("B6", "Body message is missing", None, 3)
|
expected_violation = rules.RuleViolation("B6", "Body message is missing", None, 3)
|
||||||
|
|
||||||
commit = self.gitcommit(u"Tïtle\n")
|
commit = self.gitcommit("Tïtle\n")
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ class BodyRuleTests(BaseTestCase):
|
||||||
rule = rules.BodyMissing()
|
rule = rules.BodyMissing()
|
||||||
|
|
||||||
# assert no error - merge commit
|
# assert no error - merge commit
|
||||||
commit = self.gitcommit(u"Merge: Tïtle\n")
|
commit = self.gitcommit("Merge: Tïtle\n")
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
|
@ -144,37 +144,37 @@ class BodyRuleTests(BaseTestCase):
|
||||||
rule = rules.BodyChangedFileMention()
|
rule = rules.BodyChangedFileMention()
|
||||||
|
|
||||||
# assert no error when no files have changed and no files need to be mentioned
|
# assert no error when no files have changed and no files need to be mentioned
|
||||||
commit = self.gitcommit(u"This is a test\n\nHere is a mention of föo/test.py")
|
commit = self.gitcommit("This is a test\n\nHere is a mention of föo/test.py")
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert no error when no files have changed but certain files need to be mentioned on change
|
# assert no error when no files have changed but certain files need to be mentioned on change
|
||||||
rule = rules.BodyChangedFileMention({'files': u"bar.txt,föo/test.py"})
|
rule = rules.BodyChangedFileMention({'files': "bar.txt,föo/test.py"})
|
||||||
commit = self.gitcommit(u"This is a test\n\nHere is a mention of föo/test.py")
|
commit = self.gitcommit("This is a test\n\nHere is a mention of föo/test.py")
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert no error if a file has changed and is mentioned
|
# assert no error if a file has changed and is mentioned
|
||||||
commit = self.gitcommit(u"This is a test\n\nHere is a mention of föo/test.py", [u"föo/test.py"])
|
commit = self.gitcommit("This is a test\n\nHere is a mention of föo/test.py", ["föo/test.py"])
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert no error if multiple files have changed and are mentioned
|
# assert no error if multiple files have changed and are mentioned
|
||||||
commit_msg = u"This is a test\n\nHere is a mention of föo/test.py\nAnd here is a mention of bar.txt"
|
commit_msg = "This is a test\n\nHere is a mention of föo/test.py\nAnd here is a mention of bar.txt"
|
||||||
commit = self.gitcommit(commit_msg, [u"föo/test.py", "bar.txt"])
|
commit = self.gitcommit(commit_msg, ["föo/test.py", "bar.txt"])
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert error if file has changed and is not mentioned
|
# assert error if file has changed and is not mentioned
|
||||||
commit_msg = u"This is a test\n\nHere is å mention of\nAnd here is a mention of bar.txt"
|
commit_msg = "This is a test\n\nHere is å mention of\nAnd here is a mention of bar.txt"
|
||||||
commit = self.gitcommit(commit_msg, [u"föo/test.py", "bar.txt"])
|
commit = self.gitcommit(commit_msg, ["föo/test.py", "bar.txt"])
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
expected_violation = rules.RuleViolation("B7", u"Body does not mention changed file 'föo/test.py'", None, 4)
|
expected_violation = rules.RuleViolation("B7", "Body does not mention changed file 'föo/test.py'", None, 4)
|
||||||
self.assertEqual([expected_violation], violations)
|
self.assertEqual([expected_violation], violations)
|
||||||
|
|
||||||
# assert multiple errors if multiple files habe changed and are not mentioned
|
# assert multiple errors if multiple files habe changed and are not mentioned
|
||||||
commit_msg = u"This is å test\n\nHere is a mention of\nAnd here is a mention of"
|
commit_msg = "This is å test\n\nHere is a mention of\nAnd here is a mention of"
|
||||||
commit = self.gitcommit(commit_msg, [u"föo/test.py", "bar.txt"])
|
commit = self.gitcommit(commit_msg, ["föo/test.py", "bar.txt"])
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
expected_violation_2 = rules.RuleViolation("B7", "Body does not mention changed file 'bar.txt'", None, 4)
|
expected_violation_2 = rules.RuleViolation("B7", "Body does not mention changed file 'bar.txt'", None, 4)
|
||||||
self.assertEqual([expected_violation_2, expected_violation], violations)
|
self.assertEqual([expected_violation_2, expected_violation], violations)
|
||||||
|
@ -182,7 +182,7 @@ class BodyRuleTests(BaseTestCase):
|
||||||
def test_body_match_regex(self):
|
def test_body_match_regex(self):
|
||||||
# We intentionally add 2 newlines at the end of our commit message as that's how git will pass the
|
# We intentionally add 2 newlines at the end of our commit message as that's how git will pass the
|
||||||
# message. This way we also test that the rule strips off the last line.
|
# message. This way we also test that the rule strips off the last line.
|
||||||
commit = self.gitcommit(u"US1234: åbc\nIgnored\nBödy\nFöo\nMy-Commit-Tag: föo\n\n")
|
commit = self.gitcommit("US1234: åbc\nIgnored\nBödy\nFöo\nMy-Commit-Tag: föo\n\n")
|
||||||
|
|
||||||
# assert no violation on default regex (=everything allowed)
|
# assert no violation on default regex (=everything allowed)
|
||||||
rule = rules.BodyRegexMatches()
|
rule = rules.BodyRegexMatches()
|
||||||
|
@ -191,25 +191,25 @@ class BodyRuleTests(BaseTestCase):
|
||||||
|
|
||||||
# assert no violation on matching regex
|
# assert no violation on matching regex
|
||||||
# (also note that first body line - in between title and rest of body - is ignored)
|
# (also note that first body line - in between title and rest of body - is ignored)
|
||||||
rule = rules.BodyRegexMatches({'regex': u"^Bödy(.*)"})
|
rule = rules.BodyRegexMatches({'regex': "^Bödy(.*)"})
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert we can do end matching (and last empty line is ignored)
|
# assert we can do end matching (and last empty line is ignored)
|
||||||
# (also note that first body line - in between title and rest of body - is ignored)
|
# (also note that first body line - in between title and rest of body - is ignored)
|
||||||
rule = rules.BodyRegexMatches({'regex': u"My-Commit-Tag: föo$"})
|
rule = rules.BodyRegexMatches({'regex': "My-Commit-Tag: föo$"})
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# common use-case: matching that a given line is present
|
# common use-case: matching that a given line is present
|
||||||
rule = rules.BodyRegexMatches({'regex': u"(.*)Föo(.*)"})
|
rule = rules.BodyRegexMatches({'regex': "(.*)Föo(.*)"})
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert violation on non-matching body
|
# assert violation on non-matching body
|
||||||
rule = rules.BodyRegexMatches({'regex': u"^Tëst(.*)Foo"})
|
rule = rules.BodyRegexMatches({'regex': "^Tëst(.*)Foo"})
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
expected_violation = rules.RuleViolation("B8", u"Body does not match regex (^Tëst(.*)Foo)", None, 6)
|
expected_violation = rules.RuleViolation("B8", "Body does not match regex (^Tëst(.*)Foo)", None, 6)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# assert no violation on None regex
|
# assert no violation on None regex
|
||||||
|
@ -218,7 +218,7 @@ class BodyRuleTests(BaseTestCase):
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# Assert no issues when there's no body or a weird body variation
|
# Assert no issues when there's no body or a weird body variation
|
||||||
bodies = [u"åbc", u"åbc\n", u"åbc\nföo\n", u"åbc\n\n", u"åbc\nföo\nblå", u"åbc\nföo\nblå\n"]
|
bodies = ["åbc", "åbc\n", "åbc\nföo\n", "åbc\n\n", "åbc\nföo\nblå", "åbc\nföo\nblå\n"]
|
||||||
for body in bodies:
|
for body in bodies:
|
||||||
commit = self.gitcommit(body)
|
commit = self.gitcommit(body)
|
||||||
rule = rules.BodyRegexMatches({'regex': ".*"})
|
rule = rules.BodyRegexMatches({'regex': ".*"})
|
||||||
|
|
|
@ -6,7 +6,7 @@ from gitlint.config import LintConfig
|
||||||
|
|
||||||
class ConfigurationRuleTests(BaseTestCase):
|
class ConfigurationRuleTests(BaseTestCase):
|
||||||
def test_ignore_by_title(self):
|
def test_ignore_by_title(self):
|
||||||
commit = self.gitcommit(u"Releäse\n\nThis is the secōnd body line")
|
commit = self.gitcommit("Releäse\n\nThis is the secōnd body line")
|
||||||
|
|
||||||
# No regex specified -> Config shouldn't be changed
|
# No regex specified -> Config shouldn't be changed
|
||||||
rule = rules.IgnoreByTitle()
|
rule = rules.IgnoreByTitle()
|
||||||
|
@ -16,29 +16,29 @@ class ConfigurationRuleTests(BaseTestCase):
|
||||||
self.assert_logged([]) # nothing logged -> nothing ignored
|
self.assert_logged([]) # nothing logged -> nothing ignored
|
||||||
|
|
||||||
# Matching regex -> expect config to ignore all rules
|
# Matching regex -> expect config to ignore all rules
|
||||||
rule = rules.IgnoreByTitle({"regex": u"^Releäse(.*)"})
|
rule = rules.IgnoreByTitle({"regex": "^Releäse(.*)"})
|
||||||
expected_config = LintConfig()
|
expected_config = LintConfig()
|
||||||
expected_config.ignore = "all"
|
expected_config.ignore = "all"
|
||||||
rule.apply(config, commit)
|
rule.apply(config, commit)
|
||||||
self.assertEqual(config, expected_config)
|
self.assertEqual(config, expected_config)
|
||||||
|
|
||||||
expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \
|
expected_log_message = "DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \
|
||||||
u"Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: all"
|
"Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: all"
|
||||||
self.assert_log_contains(expected_log_message)
|
self.assert_log_contains(expected_log_message)
|
||||||
|
|
||||||
# Matching regex with specific ignore
|
# Matching regex with specific ignore
|
||||||
rule = rules.IgnoreByTitle({"regex": u"^Releäse(.*)",
|
rule = rules.IgnoreByTitle({"regex": "^Releäse(.*)",
|
||||||
"ignore": "T1,B2"})
|
"ignore": "T1,B2"})
|
||||||
expected_config = LintConfig()
|
expected_config = LintConfig()
|
||||||
expected_config.ignore = "T1,B2"
|
expected_config.ignore = "T1,B2"
|
||||||
rule.apply(config, commit)
|
rule.apply(config, commit)
|
||||||
self.assertEqual(config, expected_config)
|
self.assertEqual(config, expected_config)
|
||||||
|
|
||||||
expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \
|
expected_log_message = "DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \
|
||||||
u"Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: T1,B2"
|
"Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: T1,B2"
|
||||||
|
|
||||||
def test_ignore_by_body(self):
|
def test_ignore_by_body(self):
|
||||||
commit = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line")
|
commit = self.gitcommit("Tïtle\n\nThis is\n a relëase body\n line")
|
||||||
|
|
||||||
# No regex specified -> Config shouldn't be changed
|
# No regex specified -> Config shouldn't be changed
|
||||||
rule = rules.IgnoreByBody()
|
rule = rules.IgnoreByBody()
|
||||||
|
@ -48,32 +48,32 @@ class ConfigurationRuleTests(BaseTestCase):
|
||||||
self.assert_logged([]) # nothing logged -> nothing ignored
|
self.assert_logged([]) # nothing logged -> nothing ignored
|
||||||
|
|
||||||
# Matching regex -> expect config to ignore all rules
|
# Matching regex -> expect config to ignore all rules
|
||||||
rule = rules.IgnoreByBody({"regex": u"(.*)relëase(.*)"})
|
rule = rules.IgnoreByBody({"regex": "(.*)relëase(.*)"})
|
||||||
expected_config = LintConfig()
|
expected_config = LintConfig()
|
||||||
expected_config.ignore = "all"
|
expected_config.ignore = "all"
|
||||||
rule.apply(config, commit)
|
rule.apply(config, commit)
|
||||||
self.assertEqual(config, expected_config)
|
self.assertEqual(config, expected_config)
|
||||||
|
|
||||||
expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \
|
expected_log_message = "DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \
|
||||||
u"Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)'," + \
|
"Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)'," + \
|
||||||
u" ignoring rules: all"
|
" ignoring rules: all"
|
||||||
self.assert_log_contains(expected_log_message)
|
self.assert_log_contains(expected_log_message)
|
||||||
|
|
||||||
# Matching regex with specific ignore
|
# Matching regex with specific ignore
|
||||||
rule = rules.IgnoreByBody({"regex": u"(.*)relëase(.*)",
|
rule = rules.IgnoreByBody({"regex": "(.*)relëase(.*)",
|
||||||
"ignore": "T1,B2"})
|
"ignore": "T1,B2"})
|
||||||
expected_config = LintConfig()
|
expected_config = LintConfig()
|
||||||
expected_config.ignore = "T1,B2"
|
expected_config.ignore = "T1,B2"
|
||||||
rule.apply(config, commit)
|
rule.apply(config, commit)
|
||||||
self.assertEqual(config, expected_config)
|
self.assertEqual(config, expected_config)
|
||||||
|
|
||||||
expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \
|
expected_log_message = "DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \
|
||||||
u"Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)', ignoring rules: T1,B2"
|
"Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)', ignoring rules: T1,B2"
|
||||||
self.assert_log_contains(expected_log_message)
|
self.assert_log_contains(expected_log_message)
|
||||||
|
|
||||||
def test_ignore_body_lines(self):
|
def test_ignore_body_lines(self):
|
||||||
commit1 = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line")
|
commit1 = self.gitcommit("Tïtle\n\nThis is\n a relëase body\n line")
|
||||||
commit2 = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line")
|
commit2 = self.gitcommit("Tïtle\n\nThis is\n a relëase body\n line")
|
||||||
|
|
||||||
# no regex specified, nothing should have happened:
|
# no regex specified, nothing should have happened:
|
||||||
# commit and config should remain identical, log should be empty
|
# commit and config should remain identical, log should be empty
|
||||||
|
@ -85,22 +85,22 @@ class ConfigurationRuleTests(BaseTestCase):
|
||||||
self.assert_logged([])
|
self.assert_logged([])
|
||||||
|
|
||||||
# Matching regex
|
# Matching regex
|
||||||
rule = rules.IgnoreBodyLines({"regex": u"(.*)relëase(.*)"})
|
rule = rules.IgnoreBodyLines({"regex": "(.*)relëase(.*)"})
|
||||||
config = LintConfig()
|
config = LintConfig()
|
||||||
rule.apply(config, commit1)
|
rule.apply(config, commit1)
|
||||||
# Our modified commit should be identical to a commit that doesn't contain the specific line
|
# Our modified commit should be identical to a commit that doesn't contain the specific line
|
||||||
expected_commit = self.gitcommit(u"Tïtle\n\nThis is\n line")
|
expected_commit = self.gitcommit("Tïtle\n\nThis is\n line")
|
||||||
# The original message isn't touched by this rule, this way we always have a way to reference back to it,
|
# The original message isn't touched by this rule, this way we always have a way to reference back to it,
|
||||||
# so assert it's not modified by setting it to the same as commit1
|
# so assert it's not modified by setting it to the same as commit1
|
||||||
expected_commit.message.original = commit1.message.original
|
expected_commit.message.original = commit1.message.original
|
||||||
self.assertEqual(commit1, expected_commit)
|
self.assertEqual(commit1, expected_commit)
|
||||||
self.assertEqual(config, LintConfig()) # config shouldn't have been modified
|
self.assertEqual(config, LintConfig()) # config shouldn't have been modified
|
||||||
self.assert_log_contains(u"DEBUG: gitlint.rules Ignoring line ' a relëase body' because it " +
|
self.assert_log_contains("DEBUG: gitlint.rules Ignoring line ' a relëase body' because it " +
|
||||||
u"matches '(.*)relëase(.*)'")
|
"matches '(.*)relëase(.*)'")
|
||||||
|
|
||||||
# Non-Matching regex: no changes expected
|
# Non-Matching regex: no changes expected
|
||||||
commit1 = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line")
|
commit1 = self.gitcommit("Tïtle\n\nThis is\n a relëase body\n line")
|
||||||
rule = rules.IgnoreBodyLines({"regex": u"(.*)föobar(.*)"})
|
rule = rules.IgnoreBodyLines({"regex": "(.*)föobar(.*)"})
|
||||||
config = LintConfig()
|
config = LintConfig()
|
||||||
rule.apply(config, commit1)
|
rule.apply(config, commit1)
|
||||||
self.assertEqual(commit1, commit2)
|
self.assertEqual(commit1, commit2)
|
||||||
|
|
|
@ -8,25 +8,25 @@ class MetaRuleTests(BaseTestCase):
|
||||||
rule = AuthorValidEmail()
|
rule = AuthorValidEmail()
|
||||||
|
|
||||||
# valid email addresses
|
# valid email addresses
|
||||||
valid_email_addresses = [u"föo@bar.com", u"Jöhn.Doe@bar.com", u"jöhn+doe@bar.com", u"jöhn/doe@bar.com",
|
valid_email_addresses = ["föo@bar.com", "Jöhn.Doe@bar.com", "jöhn+doe@bar.com", "jöhn/doe@bar.com",
|
||||||
u"jöhn.doe@subdomain.bar.com"]
|
"jöhn.doe@subdomain.bar.com"]
|
||||||
for email in valid_email_addresses:
|
for email in valid_email_addresses:
|
||||||
commit = self.gitcommit(u"", author_email=email)
|
commit = self.gitcommit("", author_email=email)
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# No email address (=allowed for now, as gitlint also lints messages passed via stdin that don't have an
|
# No email address (=allowed for now, as gitlint also lints messages passed via stdin that don't have an
|
||||||
# email address)
|
# email address)
|
||||||
commit = self.gitcommit(u"")
|
commit = self.gitcommit("")
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# Invalid email addresses: no TLD, no domain, no @, space anywhere (=valid but not allowed by gitlint)
|
# Invalid email addresses: no TLD, no domain, no @, space anywhere (=valid but not allowed by gitlint)
|
||||||
invalid_email_addresses = [u"föo@bar", u"JöhnDoe", u"Jöhn Doe", u"Jöhn Doe@foo.com", u" JöhnDoe@foo.com",
|
invalid_email_addresses = ["föo@bar", "JöhnDoe", "Jöhn Doe", "Jöhn Doe@foo.com", " JöhnDoe@foo.com",
|
||||||
u"JöhnDoe@ foo.com", u"JöhnDoe@foo. com", u"JöhnDoe@foo. com", u"@bår.com",
|
"JöhnDoe@ foo.com", "JöhnDoe@foo. com", "JöhnDoe@foo. com", "@bår.com",
|
||||||
u"föo@.com"]
|
"föo@.com"]
|
||||||
for email in invalid_email_addresses:
|
for email in invalid_email_addresses:
|
||||||
commit = self.gitcommit(u"", author_email=email)
|
commit = self.gitcommit("", author_email=email)
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertListEqual(violations,
|
self.assertListEqual(violations,
|
||||||
[RuleViolation("M1", "Author email for commit is invalid", email)])
|
[RuleViolation("M1", "Author email for commit is invalid", email)])
|
||||||
|
@ -35,25 +35,25 @@ class MetaRuleTests(BaseTestCase):
|
||||||
# regex=None -> the rule isn't applied
|
# regex=None -> the rule isn't applied
|
||||||
rule = AuthorValidEmail()
|
rule = AuthorValidEmail()
|
||||||
rule.options['regex'].set(None)
|
rule.options['regex'].set(None)
|
||||||
emailadresses = [u"föo", None, u"hür dür"]
|
emailadresses = ["föo", None, "hür dür"]
|
||||||
for email in emailadresses:
|
for email in emailadresses:
|
||||||
commit = self.gitcommit(u"", author_email=email)
|
commit = self.gitcommit("", author_email=email)
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# Custom domain
|
# Custom domain
|
||||||
rule = AuthorValidEmail({'regex': u"[^@]+@bår.com"})
|
rule = AuthorValidEmail({'regex': "[^@]+@bår.com"})
|
||||||
valid_email_addresses = [
|
valid_email_addresses = [
|
||||||
u"föo@bår.com", u"Jöhn.Doe@bår.com", u"jöhn+doe@bår.com", u"jöhn/doe@bår.com"]
|
"föo@bår.com", "Jöhn.Doe@bår.com", "jöhn+doe@bår.com", "jöhn/doe@bår.com"]
|
||||||
for email in valid_email_addresses:
|
for email in valid_email_addresses:
|
||||||
commit = self.gitcommit(u"", author_email=email)
|
commit = self.gitcommit("", author_email=email)
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# Invalid email addresses
|
# Invalid email addresses
|
||||||
invalid_email_addresses = [u"föo@hur.com"]
|
invalid_email_addresses = ["föo@hur.com"]
|
||||||
for email in invalid_email_addresses:
|
for email in invalid_email_addresses:
|
||||||
commit = self.gitcommit(u"", author_email=email)
|
commit = self.gitcommit("", author_email=email)
|
||||||
violations = rule.validate(commit)
|
violations = rule.validate(commit)
|
||||||
self.assertListEqual(violations,
|
self.assertListEqual(violations,
|
||||||
[RuleViolation("M1", "Author email for commit is invalid", email)])
|
[RuleViolation("M1", "Author email for commit is invalid", email)])
|
||||||
|
|
|
@ -10,14 +10,14 @@ class RuleTests(BaseTestCase):
|
||||||
# Ensure rules are not equal if they differ on their attributes
|
# Ensure rules are not equal if they differ on their attributes
|
||||||
for attr in ["id", "name", "target", "options"]:
|
for attr in ["id", "name", "target", "options"]:
|
||||||
rule = Rule()
|
rule = Rule()
|
||||||
setattr(rule, attr, u"åbc")
|
setattr(rule, attr, "åbc")
|
||||||
self.assertNotEqual(Rule(), rule)
|
self.assertNotEqual(Rule(), rule)
|
||||||
|
|
||||||
def test_rule_log(self):
|
def test_rule_log(self):
|
||||||
rule = Rule()
|
rule = Rule()
|
||||||
rule.log.debug(u"Tēst message")
|
rule.log.debug("Tēst message")
|
||||||
self.assert_log_contains(u"DEBUG: gitlint.rules Tēst message")
|
self.assert_log_contains("DEBUG: gitlint.rules Tēst message")
|
||||||
|
|
||||||
def test_rule_violation_equality(self):
|
def test_rule_violation_equality(self):
|
||||||
violation1 = RuleViolation(u"ïd1", u"My messåge", u"My cöntent", 1)
|
violation1 = RuleViolation("ïd1", "My messåge", "My cöntent", 1)
|
||||||
self.object_equality_test(violation1, ["rule_id", "message", "content", "line_nr"])
|
self.object_equality_test(violation1, ["rule_id", "message", "content", "line_nr"])
|
||||||
|
|
|
@ -9,66 +9,66 @@ class TitleRuleTests(BaseTestCase):
|
||||||
rule = TitleMaxLength()
|
rule = TitleMaxLength()
|
||||||
|
|
||||||
# assert no error
|
# assert no error
|
||||||
violation = rule.validate(u"å" * 72, None)
|
violation = rule.validate("å" * 72, None)
|
||||||
self.assertIsNone(violation)
|
self.assertIsNone(violation)
|
||||||
|
|
||||||
# assert error on line length > 72
|
# assert error on line length > 72
|
||||||
expected_violation = RuleViolation("T1", "Title exceeds max length (73>72)", u"å" * 73)
|
expected_violation = RuleViolation("T1", "Title exceeds max length (73>72)", "å" * 73)
|
||||||
violations = rule.validate(u"å" * 73, None)
|
violations = rule.validate("å" * 73, None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# set line length to 120, and check no violation on length 73
|
# set line length to 120, and check no violation on length 73
|
||||||
rule = TitleMaxLength({'line-length': 120})
|
rule = TitleMaxLength({'line-length': 120})
|
||||||
violations = rule.validate(u"å" * 73, None)
|
violations = rule.validate("å" * 73, None)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert raise on 121
|
# assert raise on 121
|
||||||
expected_violation = RuleViolation("T1", "Title exceeds max length (121>120)", u"å" * 121)
|
expected_violation = RuleViolation("T1", "Title exceeds max length (121>120)", "å" * 121)
|
||||||
violations = rule.validate(u"å" * 121, None)
|
violations = rule.validate("å" * 121, None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
def test_trailing_whitespace(self):
|
def test_trailing_whitespace(self):
|
||||||
rule = TitleTrailingWhitespace()
|
rule = TitleTrailingWhitespace()
|
||||||
|
|
||||||
# assert no error
|
# assert no error
|
||||||
violations = rule.validate(u"å", None)
|
violations = rule.validate("å", None)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# trailing space
|
# trailing space
|
||||||
expected_violation = RuleViolation("T2", "Title has trailing whitespace", u"å ")
|
expected_violation = RuleViolation("T2", "Title has trailing whitespace", "å ")
|
||||||
violations = rule.validate(u"å ", None)
|
violations = rule.validate("å ", None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# trailing tab
|
# trailing tab
|
||||||
expected_violation = RuleViolation("T2", "Title has trailing whitespace", u"å\t")
|
expected_violation = RuleViolation("T2", "Title has trailing whitespace", "å\t")
|
||||||
violations = rule.validate(u"å\t", None)
|
violations = rule.validate("å\t", None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
def test_hard_tabs(self):
|
def test_hard_tabs(self):
|
||||||
rule = TitleHardTab()
|
rule = TitleHardTab()
|
||||||
|
|
||||||
# assert no error
|
# assert no error
|
||||||
violations = rule.validate(u"This is å test", None)
|
violations = rule.validate("This is å test", None)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# contains hard tab
|
# contains hard tab
|
||||||
expected_violation = RuleViolation("T4", "Title contains hard tab characters (\\t)", u"This is å\ttest")
|
expected_violation = RuleViolation("T4", "Title contains hard tab characters (\\t)", "This is å\ttest")
|
||||||
violations = rule.validate(u"This is å\ttest", None)
|
violations = rule.validate("This is å\ttest", None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
def test_trailing_punctuation(self):
|
def test_trailing_punctuation(self):
|
||||||
rule = TitleTrailingPunctuation()
|
rule = TitleTrailingPunctuation()
|
||||||
|
|
||||||
# assert no error
|
# assert no error
|
||||||
violations = rule.validate(u"This is å test", None)
|
violations = rule.validate("This is å test", None)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert errors for different punctuations
|
# assert errors for different punctuations
|
||||||
punctuation = u"?:!.,;"
|
punctuation = "?:!.,;"
|
||||||
for char in punctuation:
|
for char in punctuation:
|
||||||
line = u"This is å test" + char # note that make sure to include some unicode!
|
line = "This is å test" + char # note that make sure to include some unicode!
|
||||||
gitcontext = self.gitcontext(line)
|
gitcontext = self.gitcontext(line)
|
||||||
expected_violation = RuleViolation("T3", u"Title has trailing punctuation ({0})".format(char), line)
|
expected_violation = RuleViolation("T3", f"Title has trailing punctuation ({char})", line)
|
||||||
violations = rule.validate(line, gitcontext)
|
violations = rule.validate(line, gitcontext)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
|
@ -76,40 +76,40 @@ class TitleRuleTests(BaseTestCase):
|
||||||
rule = TitleMustNotContainWord()
|
rule = TitleMustNotContainWord()
|
||||||
|
|
||||||
# no violations
|
# no violations
|
||||||
violations = rule.validate(u"This is å test", None)
|
violations = rule.validate("This is å test", None)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# no violation if WIP occurs inside a wor
|
# no violation if WIP occurs inside a wor
|
||||||
violations = rule.validate(u"This is å wiping test", None)
|
violations = rule.validate("This is å wiping test", None)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# match literally
|
# match literally
|
||||||
violations = rule.validate(u"WIP This is å test", None)
|
violations = rule.validate("WIP This is å test", None)
|
||||||
expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
||||||
u"WIP This is å test")
|
"WIP This is å test")
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# match case insensitive
|
# match case insensitive
|
||||||
violations = rule.validate(u"wip This is å test", None)
|
violations = rule.validate("wip This is å test", None)
|
||||||
expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
||||||
u"wip This is å test")
|
"wip This is å test")
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# match if there is a colon after the word
|
# match if there is a colon after the word
|
||||||
violations = rule.validate(u"WIP:This is å test", None)
|
violations = rule.validate("WIP:This is å test", None)
|
||||||
expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
||||||
u"WIP:This is å test")
|
"WIP:This is å test")
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# match multiple words
|
# match multiple words
|
||||||
rule = TitleMustNotContainWord({'words': u"wip,test,å"})
|
rule = TitleMustNotContainWord({'words': "wip,test,å"})
|
||||||
violations = rule.validate(u"WIP:This is å test", None)
|
violations = rule.validate("WIP:This is å test", None)
|
||||||
expected_violation = RuleViolation("T5", "Title contains the word 'wip' (case-insensitive)",
|
expected_violation = RuleViolation("T5", "Title contains the word 'wip' (case-insensitive)",
|
||||||
u"WIP:This is å test")
|
"WIP:This is å test")
|
||||||
expected_violation2 = RuleViolation("T5", "Title contains the word 'test' (case-insensitive)",
|
expected_violation2 = RuleViolation("T5", "Title contains the word 'test' (case-insensitive)",
|
||||||
u"WIP:This is å test")
|
"WIP:This is å test")
|
||||||
expected_violation3 = RuleViolation("T5", u"Title contains the word 'å' (case-insensitive)",
|
expected_violation3 = RuleViolation("T5", "Title contains the word 'å' (case-insensitive)",
|
||||||
u"WIP:This is å test")
|
"WIP:This is å test")
|
||||||
self.assertListEqual(violations, [expected_violation, expected_violation2, expected_violation3])
|
self.assertListEqual(violations, [expected_violation, expected_violation2, expected_violation3])
|
||||||
|
|
||||||
def test_leading_whitespace(self):
|
def test_leading_whitespace(self):
|
||||||
|
@ -130,12 +130,12 @@ class TitleRuleTests(BaseTestCase):
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# unicode test
|
# unicode test
|
||||||
expected_violation = RuleViolation("T6", "Title has leading whitespace", u" ☺")
|
expected_violation = RuleViolation("T6", "Title has leading whitespace", " ☺")
|
||||||
violations = rule.validate(u" ☺", None)
|
violations = rule.validate(" ☺", None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
def test_regex_matches(self):
|
def test_regex_matches(self):
|
||||||
commit = self.gitcommit(u"US1234: åbc\n")
|
commit = self.gitcommit("US1234: åbc\n")
|
||||||
|
|
||||||
# assert no violation on default regex (=everything allowed)
|
# assert no violation on default regex (=everything allowed)
|
||||||
rule = TitleRegexMatches()
|
rule = TitleRegexMatches()
|
||||||
|
@ -143,41 +143,41 @@ class TitleRuleTests(BaseTestCase):
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert no violation on matching regex
|
# assert no violation on matching regex
|
||||||
rule = TitleRegexMatches({'regex': u"^US[0-9]*: å"})
|
rule = TitleRegexMatches({'regex': "^US[0-9]*: å"})
|
||||||
violations = rule.validate(commit.message.title, commit)
|
violations = rule.validate(commit.message.title, commit)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert violation when no matching regex
|
# assert violation when no matching regex
|
||||||
rule = TitleRegexMatches({'regex': u"^UÅ[0-9]*"})
|
rule = TitleRegexMatches({'regex': "^UÅ[0-9]*"})
|
||||||
violations = rule.validate(commit.message.title, commit)
|
violations = rule.validate(commit.message.title, commit)
|
||||||
expected_violation = RuleViolation("T7", u"Title does not match regex (^UÅ[0-9]*)", u"US1234: åbc")
|
expected_violation = RuleViolation("T7", "Title does not match regex (^UÅ[0-9]*)", "US1234: åbc")
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
def test_min_line_length(self):
|
def test_min_line_length(self):
|
||||||
rule = TitleMinLength()
|
rule = TitleMinLength()
|
||||||
|
|
||||||
# assert no error
|
# assert no error
|
||||||
violation = rule.validate(u"å" * 72, None)
|
violation = rule.validate("å" * 72, None)
|
||||||
self.assertIsNone(violation)
|
self.assertIsNone(violation)
|
||||||
|
|
||||||
# assert error on line length < 5
|
# assert error on line length < 5
|
||||||
expected_violation = RuleViolation("T8", "Title is too short (4<5)", u"å" * 4, 1)
|
expected_violation = RuleViolation("T8", "Title is too short (4<5)", "å" * 4, 1)
|
||||||
violations = rule.validate(u"å" * 4, None)
|
violations = rule.validate("å" * 4, None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# set line length to 3, and check no violation on length 4
|
# set line length to 3, and check no violation on length 4
|
||||||
rule = TitleMinLength({'min-length': 3})
|
rule = TitleMinLength({'min-length': 3})
|
||||||
violations = rule.validate(u"å" * 4, None)
|
violations = rule.validate("å" * 4, None)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert no violations on length 3 (this asserts we've implemented a *strict* less than)
|
# assert no violations on length 3 (this asserts we've implemented a *strict* less than)
|
||||||
rule = TitleMinLength({'min-length': 3})
|
rule = TitleMinLength({'min-length': 3})
|
||||||
violations = rule.validate(u"å" * 3, None)
|
violations = rule.validate("å" * 3, None)
|
||||||
self.assertIsNone(violations)
|
self.assertIsNone(violations)
|
||||||
|
|
||||||
# assert raise on 2
|
# assert raise on 2
|
||||||
expected_violation = RuleViolation("T8", "Title is too short (2<3)", u"å" * 2, 1)
|
expected_violation = RuleViolation("T8", "Title is too short (2<3)", "å" * 2, 1)
|
||||||
violations = rule.validate(u"å" * 2, None)
|
violations = rule.validate("å" * 2, None)
|
||||||
self.assertListEqual(violations, [expected_violation])
|
self.assertListEqual(violations, [expected_violation])
|
||||||
|
|
||||||
# assert raise on empty title
|
# assert raise on empty title
|
||||||
|
|
|
@ -6,7 +6,6 @@ import sys
|
||||||
from gitlint.tests.base import BaseTestCase
|
from gitlint.tests.base import BaseTestCase
|
||||||
from gitlint.rule_finder import find_rule_classes, assert_valid_rule_class
|
from gitlint.rule_finder import find_rule_classes, assert_valid_rule_class
|
||||||
from gitlint.rules import UserRuleError
|
from gitlint.rules import UserRuleError
|
||||||
from gitlint.utils import ustr
|
|
||||||
|
|
||||||
from gitlint import options, rules
|
from gitlint import options, rules
|
||||||
|
|
||||||
|
@ -25,7 +24,7 @@ class UserRuleTests(BaseTestCase):
|
||||||
# - Other members of the my_commit_rules module are ignored
|
# - Other members of the my_commit_rules module are ignored
|
||||||
# (such as func_should_be_ignored, global_variable_should_be_ignored)
|
# (such as func_should_be_ignored, global_variable_should_be_ignored)
|
||||||
# - Rules are loaded non-recursively (user_rules/import_exception directory is ignored)
|
# - Rules are loaded non-recursively (user_rules/import_exception directory is ignored)
|
||||||
self.assertEqual("[<class 'my_commit_rules.MyUserCommitRule'>]", ustr(classes))
|
self.assertEqual("[<class 'my_commit_rules.MyUserCommitRule'>]", str(classes))
|
||||||
|
|
||||||
# Assert that we added the new user_rules directory to the system path and modules
|
# Assert that we added the new user_rules directory to the system path and modules
|
||||||
self.assertIn(user_rule_path, sys.path)
|
self.assertIn(user_rule_path, sys.path)
|
||||||
|
@ -33,8 +32,8 @@ class UserRuleTests(BaseTestCase):
|
||||||
|
|
||||||
# Do some basic asserts on our user rule
|
# Do some basic asserts on our user rule
|
||||||
self.assertEqual(classes[0].id, "UC1")
|
self.assertEqual(classes[0].id, "UC1")
|
||||||
self.assertEqual(classes[0].name, u"my-üser-commit-rule")
|
self.assertEqual(classes[0].name, "my-üser-commit-rule")
|
||||||
expected_option = options.IntOption('violation-count', 1, u"Number of violåtions to return")
|
expected_option = options.IntOption('violation-count', 1, "Number of violåtions to return")
|
||||||
self.assertListEqual(classes[0].options_spec, [expected_option])
|
self.assertListEqual(classes[0].options_spec, [expected_option])
|
||||||
self.assertTrue(hasattr(classes[0], "validate"))
|
self.assertTrue(hasattr(classes[0], "validate"))
|
||||||
|
|
||||||
|
@ -42,13 +41,13 @@ class UserRuleTests(BaseTestCase):
|
||||||
# expected result
|
# expected result
|
||||||
rule_class = classes[0]()
|
rule_class = classes[0]()
|
||||||
violations = rule_class.validate("false-commit-object (ignored)")
|
violations = rule_class.validate("false-commit-object (ignored)")
|
||||||
self.assertListEqual(violations, [rules.RuleViolation("UC1", u"Commit violåtion 1", u"Contënt 1", 1)])
|
self.assertListEqual(violations, [rules.RuleViolation("UC1", "Commit violåtion 1", "Contënt 1", 1)])
|
||||||
|
|
||||||
# Have it return more violations
|
# Have it return more violations
|
||||||
rule_class.options['violation-count'].value = 2
|
rule_class.options['violation-count'].value = 2
|
||||||
violations = rule_class.validate("false-commit-object (ignored)")
|
violations = rule_class.validate("false-commit-object (ignored)")
|
||||||
self.assertListEqual(violations, [rules.RuleViolation("UC1", u"Commit violåtion 1", u"Contënt 1", 1),
|
self.assertListEqual(violations, [rules.RuleViolation("UC1", "Commit violåtion 1", "Contënt 1", 1),
|
||||||
rules.RuleViolation("UC1", u"Commit violåtion 2", u"Contënt 2", 2)])
|
rules.RuleViolation("UC1", "Commit violåtion 2", "Contënt 2", 2)])
|
||||||
|
|
||||||
def test_extra_path_specified_by_file(self):
|
def test_extra_path_specified_by_file(self):
|
||||||
# Test that find_rule_classes can handle an extra path given as a file name instead of a directory
|
# Test that find_rule_classes can handle an extra path given as a file name instead of a directory
|
||||||
|
@ -58,7 +57,7 @@ class UserRuleTests(BaseTestCase):
|
||||||
|
|
||||||
rule_class = classes[0]()
|
rule_class = classes[0]()
|
||||||
violations = rule_class.validate("false-commit-object (ignored)")
|
violations = rule_class.validate("false-commit-object (ignored)")
|
||||||
self.assertListEqual(violations, [rules.RuleViolation("UC1", u"Commit violåtion 1", u"Contënt 1", 1)])
|
self.assertListEqual(violations, [rules.RuleViolation("UC1", "Commit violåtion 1", "Contënt 1", 1)])
|
||||||
|
|
||||||
def test_rules_from_init_file(self):
|
def test_rules_from_init_file(self):
|
||||||
# Test that we can import rules that are defined in __init__.py files
|
# Test that we can import rules that are defined in __init__.py files
|
||||||
|
@ -68,8 +67,8 @@ class UserRuleTests(BaseTestCase):
|
||||||
classes = find_rule_classes(user_rule_path)
|
classes = find_rule_classes(user_rule_path)
|
||||||
|
|
||||||
# convert classes to strings and sort them so we can compare them
|
# convert classes to strings and sort them so we can compare them
|
||||||
class_strings = sorted([ustr(clazz) for clazz in classes])
|
class_strings = sorted([str(clazz) for clazz in classes])
|
||||||
expected = [u"<class 'my_commit_rules.MyUserCommitRule'>", u"<class 'parent_package.InitFileRule'>"]
|
expected = ["<class 'my_commit_rules.MyUserCommitRule'>", "<class 'parent_package.InitFileRule'>"]
|
||||||
self.assertListEqual(class_strings, expected)
|
self.assertListEqual(class_strings, expected)
|
||||||
|
|
||||||
def test_empty_user_classes(self):
|
def test_empty_user_classes(self):
|
||||||
|
@ -92,8 +91,8 @@ class UserRuleTests(BaseTestCase):
|
||||||
find_rule_classes(user_rule_path)
|
find_rule_classes(user_rule_path)
|
||||||
|
|
||||||
def test_find_rule_classes_nonexisting_path(self):
|
def test_find_rule_classes_nonexisting_path(self):
|
||||||
with self.assertRaisesMessage(UserRuleError, u"Invalid extra-path: föo/bar"):
|
with self.assertRaisesMessage(UserRuleError, "Invalid extra-path: föo/bar"):
|
||||||
find_rule_classes(u"föo/bar")
|
find_rule_classes("föo/bar")
|
||||||
|
|
||||||
def test_assert_valid_rule_class(self):
|
def test_assert_valid_rule_class(self):
|
||||||
class MyLineRuleClass(rules.LineRule):
|
class MyLineRuleClass(rules.LineRule):
|
||||||
|
@ -132,7 +131,7 @@ class UserRuleTests(BaseTestCase):
|
||||||
|
|
||||||
def test_assert_valid_rule_class_negative_parent(self):
|
def test_assert_valid_rule_class_negative_parent(self):
|
||||||
# rule class must extend from LineRule or CommitRule
|
# rule class must extend from LineRule or CommitRule
|
||||||
class MyRuleClass(object):
|
class MyRuleClass:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
expected_msg = "User-defined rule class 'MyRuleClass' must extend from gitlint.rules.LineRule, " + \
|
expected_msg = "User-defined rule class 'MyRuleClass' must extend from gitlint.rules.LineRule, " + \
|
||||||
|
@ -160,8 +159,9 @@ class UserRuleTests(BaseTestCase):
|
||||||
# Rule ids must not start with one of the reserved id letters
|
# Rule ids must not start with one of the reserved id letters
|
||||||
for letter in ["T", "R", "B", "M", "I"]:
|
for letter in ["T", "R", "B", "M", "I"]:
|
||||||
MyRuleClass.id = letter + "1"
|
MyRuleClass.id = letter + "1"
|
||||||
expected_msg = "The id '{0}' of 'MyRuleClass' is invalid. Gitlint reserves ids starting with R,T,B,M,I"
|
expected_msg = f"The id '{letter}' of 'MyRuleClass' is invalid. " + \
|
||||||
with self.assertRaisesMessage(UserRuleError, expected_msg.format(letter)):
|
"Gitlint reserves ids starting with R,T,B,M,I"
|
||||||
|
with self.assertRaisesMessage(UserRuleError, expected_msg):
|
||||||
assert_valid_rule_class(MyRuleClass)
|
assert_valid_rule_class(MyRuleClass)
|
||||||
|
|
||||||
def test_assert_valid_rule_class_negative_name(self):
|
def test_assert_valid_rule_class_negative_name(self):
|
||||||
|
@ -186,17 +186,17 @@ class UserRuleTests(BaseTestCase):
|
||||||
|
|
||||||
class MyRuleClass(parent_class):
|
class MyRuleClass(parent_class):
|
||||||
id = "UC1"
|
id = "UC1"
|
||||||
name = u"my-rüle-class"
|
name = "my-rüle-class"
|
||||||
|
|
||||||
# if set, option_spec must be a list of gitlint options
|
# if set, option_spec must be a list of gitlint options
|
||||||
MyRuleClass.options_spec = u"föo"
|
MyRuleClass.options_spec = "föo"
|
||||||
expected_msg = "The options_spec attribute of user-defined rule class 'MyRuleClass' must be a list " + \
|
expected_msg = "The options_spec attribute of user-defined rule class 'MyRuleClass' must be a list " + \
|
||||||
"of gitlint.options.RuleOption"
|
"of gitlint.options.RuleOption"
|
||||||
with self.assertRaisesMessage(UserRuleError, expected_msg):
|
with self.assertRaisesMessage(UserRuleError, expected_msg):
|
||||||
assert_valid_rule_class(MyRuleClass)
|
assert_valid_rule_class(MyRuleClass)
|
||||||
|
|
||||||
# option_spec is a list, but not of gitlint options
|
# option_spec is a list, but not of gitlint options
|
||||||
MyRuleClass.options_spec = [u"föo", 123] # pylint: disable=bad-option-value,redefined-variable-type
|
MyRuleClass.options_spec = ["föo", 123] # pylint: disable=bad-option-value,redefined-variable-type
|
||||||
with self.assertRaisesMessage(UserRuleError, expected_msg):
|
with self.assertRaisesMessage(UserRuleError, expected_msg):
|
||||||
assert_valid_rule_class(MyRuleClass)
|
assert_valid_rule_class(MyRuleClass)
|
||||||
|
|
||||||
|
@ -206,14 +206,14 @@ class UserRuleTests(BaseTestCase):
|
||||||
for clazz in baseclasses:
|
for clazz in baseclasses:
|
||||||
class MyRuleClass(clazz):
|
class MyRuleClass(clazz):
|
||||||
id = "UC1"
|
id = "UC1"
|
||||||
name = u"my-rüle-class"
|
name = "my-rüle-class"
|
||||||
|
|
||||||
with self.assertRaisesMessage(UserRuleError,
|
with self.assertRaisesMessage(UserRuleError,
|
||||||
"User-defined rule class 'MyRuleClass' must have a 'validate' method"):
|
"User-defined rule class 'MyRuleClass' must have a 'validate' method"):
|
||||||
assert_valid_rule_class(MyRuleClass)
|
assert_valid_rule_class(MyRuleClass)
|
||||||
|
|
||||||
# validate attribute - not a method
|
# validate attribute - not a method
|
||||||
MyRuleClass.validate = u"föo"
|
MyRuleClass.validate = "föo"
|
||||||
with self.assertRaisesMessage(UserRuleError,
|
with self.assertRaisesMessage(UserRuleError,
|
||||||
"User-defined rule class 'MyRuleClass' must have a 'validate' method"):
|
"User-defined rule class 'MyRuleClass' must have a 'validate' method"):
|
||||||
assert_valid_rule_class(MyRuleClass)
|
assert_valid_rule_class(MyRuleClass)
|
||||||
|
@ -221,21 +221,21 @@ class UserRuleTests(BaseTestCase):
|
||||||
def test_assert_valid_rule_class_negative_apply(self):
|
def test_assert_valid_rule_class_negative_apply(self):
|
||||||
class MyRuleClass(rules.ConfigurationRule):
|
class MyRuleClass(rules.ConfigurationRule):
|
||||||
id = "UCR1"
|
id = "UCR1"
|
||||||
name = u"my-rüle-class"
|
name = "my-rüle-class"
|
||||||
|
|
||||||
expected_msg = "User-defined Configuration rule class 'MyRuleClass' must have an 'apply' method"
|
expected_msg = "User-defined Configuration rule class 'MyRuleClass' must have an 'apply' method"
|
||||||
with self.assertRaisesMessage(UserRuleError, expected_msg):
|
with self.assertRaisesMessage(UserRuleError, expected_msg):
|
||||||
assert_valid_rule_class(MyRuleClass)
|
assert_valid_rule_class(MyRuleClass)
|
||||||
|
|
||||||
# validate attribute - not a method
|
# validate attribute - not a method
|
||||||
MyRuleClass.validate = u"föo"
|
MyRuleClass.validate = "föo"
|
||||||
with self.assertRaisesMessage(UserRuleError, expected_msg):
|
with self.assertRaisesMessage(UserRuleError, expected_msg):
|
||||||
assert_valid_rule_class(MyRuleClass)
|
assert_valid_rule_class(MyRuleClass)
|
||||||
|
|
||||||
def test_assert_valid_rule_class_negative_target(self):
|
def test_assert_valid_rule_class_negative_target(self):
|
||||||
class MyRuleClass(rules.LineRule):
|
class MyRuleClass(rules.LineRule):
|
||||||
id = "UC1"
|
id = "UC1"
|
||||||
name = u"my-rüle-class"
|
name = "my-rüle-class"
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
pass
|
pass
|
||||||
|
@ -247,7 +247,7 @@ class UserRuleTests(BaseTestCase):
|
||||||
assert_valid_rule_class(MyRuleClass)
|
assert_valid_rule_class(MyRuleClass)
|
||||||
|
|
||||||
# invalid target
|
# invalid target
|
||||||
MyRuleClass.target = u"föo"
|
MyRuleClass.target = "föo"
|
||||||
with self.assertRaisesMessage(UserRuleError, expected_msg):
|
with self.assertRaisesMessage(UserRuleError, expected_msg):
|
||||||
assert_valid_rule_class(MyRuleClass)
|
assert_valid_rule_class(MyRuleClass)
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,14 @@ from gitlint.options import IntOption
|
||||||
|
|
||||||
|
|
||||||
class MyUserCommitRule(CommitRule):
|
class MyUserCommitRule(CommitRule):
|
||||||
name = u"my-üser-commit-rule"
|
name = "my-üser-commit-rule"
|
||||||
id = "UC1"
|
id = "UC1"
|
||||||
options_spec = [IntOption('violation-count', 1, u"Number of violåtions to return")]
|
options_spec = [IntOption('violation-count', 1, "Number of violåtions to return")]
|
||||||
|
|
||||||
def validate(self, _commit):
|
def validate(self, _commit):
|
||||||
violations = []
|
violations = []
|
||||||
for i in range(1, self.options['violation-count'].value + 1):
|
for i in range(1, self.options['violation-count'].value + 1):
|
||||||
violations.append(RuleViolation(self.id, u"Commit violåtion %d" % i, u"Contënt %d" % i, i))
|
violations.append(RuleViolation(self.id, "Commit violåtion %d" % i, "Contënt %d" % i, i))
|
||||||
|
|
||||||
return violations
|
return violations
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ from gitlint.rules import CommitRule
|
||||||
|
|
||||||
|
|
||||||
class InitFileRule(CommitRule):
|
class InitFileRule(CommitRule):
|
||||||
name = u"my-init-cömmit-rule"
|
name = "my-init-cömmit-rule"
|
||||||
id = "UC1"
|
id = "UC1"
|
||||||
options_spec = []
|
options_spec = []
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from gitlint.rules import CommitRule
|
||||||
|
|
||||||
|
|
||||||
class MyUserCommitRule(CommitRule):
|
class MyUserCommitRule(CommitRule):
|
||||||
name = u"my-user-cömmit-rule"
|
name = "my-user-cömmit-rule"
|
||||||
id = "UC2"
|
id = "UC2"
|
||||||
options_spec = []
|
options_spec = []
|
||||||
|
|
||||||
|
|
|
@ -16,13 +16,13 @@ class CacheTests(BaseTestCase):
|
||||||
@cache
|
@cache
|
||||||
def foo(self):
|
def foo(self):
|
||||||
self.counter += 1
|
self.counter += 1
|
||||||
return u"bår"
|
return "bår"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@cache(cachekey=u"hür")
|
@cache(cachekey="hür")
|
||||||
def bar(self):
|
def bar(self):
|
||||||
self.counter += 1
|
self.counter += 1
|
||||||
return u"fōo"
|
return "fōo"
|
||||||
|
|
||||||
def test_cache(self):
|
def test_cache(self):
|
||||||
# Init new class with cached properties
|
# Init new class with cached properties
|
||||||
|
@ -31,14 +31,14 @@ class CacheTests(BaseTestCase):
|
||||||
self.assertDictEqual(myclass._cache, {})
|
self.assertDictEqual(myclass._cache, {})
|
||||||
|
|
||||||
# Assert that function is called on first access, cache is set
|
# Assert that function is called on first access, cache is set
|
||||||
self.assertEqual(myclass.foo, u"bår")
|
self.assertEqual(myclass.foo, "bår")
|
||||||
self.assertEqual(myclass.counter, 1)
|
self.assertEqual(myclass.counter, 1)
|
||||||
self.assertDictEqual(myclass._cache, {"foo": u"bår"})
|
self.assertDictEqual(myclass._cache, {"foo": "bår"})
|
||||||
|
|
||||||
# After function is not called on subsequent access, cache is still set
|
# After function is not called on subsequent access, cache is still set
|
||||||
self.assertEqual(myclass.foo, u"bår")
|
self.assertEqual(myclass.foo, "bår")
|
||||||
self.assertEqual(myclass.counter, 1)
|
self.assertEqual(myclass.counter, 1)
|
||||||
self.assertDictEqual(myclass._cache, {"foo": u"bår"})
|
self.assertDictEqual(myclass._cache, {"foo": "bår"})
|
||||||
|
|
||||||
def test_cache_custom_key(self):
|
def test_cache_custom_key(self):
|
||||||
# Init new class with cached properties
|
# Init new class with cached properties
|
||||||
|
@ -47,11 +47,11 @@ class CacheTests(BaseTestCase):
|
||||||
self.assertDictEqual(myclass._cache, {})
|
self.assertDictEqual(myclass._cache, {})
|
||||||
|
|
||||||
# Assert that function is called on first access, cache is set with custom key
|
# Assert that function is called on first access, cache is set with custom key
|
||||||
self.assertEqual(myclass.bar, u"fōo")
|
self.assertEqual(myclass.bar, "fōo")
|
||||||
self.assertEqual(myclass.counter, 1)
|
self.assertEqual(myclass.counter, 1)
|
||||||
self.assertDictEqual(myclass._cache, {u"hür": u"fōo"})
|
self.assertDictEqual(myclass._cache, {"hür": "fōo"})
|
||||||
|
|
||||||
# After function is not called on subsequent access, cache is still set
|
# After function is not called on subsequent access, cache is still set
|
||||||
self.assertEqual(myclass.bar, u"fōo")
|
self.assertEqual(myclass.bar, "fōo")
|
||||||
self.assertEqual(myclass.counter, 1)
|
self.assertEqual(myclass.counter, 1)
|
||||||
self.assertDictEqual(myclass._cache, {u"hür": u"fōo"})
|
self.assertDictEqual(myclass._cache, {"hür": "fōo"})
|
||||||
|
|
|
@ -1,19 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
try:
|
from io import StringIO
|
||||||
# python 2.x
|
|
||||||
from StringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
|
from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
|
||||||
try:
|
|
||||||
# python 2.x
|
|
||||||
from mock import patch
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
|
|
||||||
|
|
||||||
from gitlint.display import Display
|
from gitlint.display import Display
|
||||||
from gitlint.config import LintConfig
|
from gitlint.config import LintConfig
|
||||||
|
@ -28,21 +17,21 @@ class DisplayTests(BaseTestCase):
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
# Non exact outputting, should output both v and vv output
|
# Non exact outputting, should output both v and vv output
|
||||||
with patch('gitlint.display.stdout', new=StringIO()) as stdout:
|
with patch('gitlint.display.stdout', new=StringIO()) as stdout:
|
||||||
display.v(u"tëst")
|
display.v("tëst")
|
||||||
display.vv(u"tëst2")
|
display.vv("tëst2")
|
||||||
# vvvv should be ignored regardless
|
# vvvv should be ignored regardless
|
||||||
display.vvv(u"tëst3.1")
|
display.vvv("tëst3.1")
|
||||||
display.vvv(u"tëst3.2", exact=True)
|
display.vvv("tëst3.2", exact=True)
|
||||||
self.assertEqual(u"tëst\ntëst2\n", stdout.getvalue())
|
self.assertEqual("tëst\ntëst2\n", stdout.getvalue())
|
||||||
|
|
||||||
# exact outputting, should only output v
|
# exact outputting, should only output v
|
||||||
with patch('gitlint.display.stdout', new=StringIO()) as stdout:
|
with patch('gitlint.display.stdout', new=StringIO()) as stdout:
|
||||||
display.v(u"tëst", exact=True)
|
display.v("tëst", exact=True)
|
||||||
display.vv(u"tëst2", exact=True)
|
display.vv("tëst2", exact=True)
|
||||||
# vvvv should be ignored regardless
|
# vvvv should be ignored regardless
|
||||||
display.vvv(u"tëst3.1")
|
display.vvv("tëst3.1")
|
||||||
display.vvv(u"tëst3.2", exact=True)
|
display.vvv("tëst3.2", exact=True)
|
||||||
self.assertEqual(u"tëst2\n", stdout.getvalue())
|
self.assertEqual("tëst2\n", stdout.getvalue())
|
||||||
|
|
||||||
# standard error should be empty throughtout all of this
|
# standard error should be empty throughtout all of this
|
||||||
self.assertEqual('', stderr.getvalue())
|
self.assertEqual('', stderr.getvalue())
|
||||||
|
@ -54,21 +43,21 @@ class DisplayTests(BaseTestCase):
|
||||||
with patch('gitlint.display.stdout', new=StringIO()) as stdout:
|
with patch('gitlint.display.stdout', new=StringIO()) as stdout:
|
||||||
# Non exact outputting, should output both v and vv output
|
# Non exact outputting, should output both v and vv output
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
display.e(u"tëst")
|
display.e("tëst")
|
||||||
display.ee(u"tëst2")
|
display.ee("tëst2")
|
||||||
# vvvv should be ignored regardless
|
# vvvv should be ignored regardless
|
||||||
display.eee(u"tëst3.1")
|
display.eee("tëst3.1")
|
||||||
display.eee(u"tëst3.2", exact=True)
|
display.eee("tëst3.2", exact=True)
|
||||||
self.assertEqual(u"tëst\ntëst2\n", stderr.getvalue())
|
self.assertEqual("tëst\ntëst2\n", stderr.getvalue())
|
||||||
|
|
||||||
# exact outputting, should only output v
|
# exact outputting, should only output v
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
display.e(u"tëst", exact=True)
|
display.e("tëst", exact=True)
|
||||||
display.ee(u"tëst2", exact=True)
|
display.ee("tëst2", exact=True)
|
||||||
# vvvv should be ignored regardless
|
# vvvv should be ignored regardless
|
||||||
display.eee(u"tëst3.1")
|
display.eee("tëst3.1")
|
||||||
display.eee(u"tëst3.2", exact=True)
|
display.eee("tëst3.2", exact=True)
|
||||||
self.assertEqual(u"tëst2\n", stderr.getvalue())
|
self.assertEqual("tëst2\n", stderr.getvalue())
|
||||||
|
|
||||||
# standard output should be empty throughtout all of this
|
# standard output should be empty throughtout all of this
|
||||||
self.assertEqual('', stdout.getvalue())
|
self.assertEqual('', stdout.getvalue())
|
||||||
|
|
|
@ -2,12 +2,7 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
try:
|
from unittest.mock import patch, ANY, mock_open
|
||||||
# python 2.x
|
|
||||||
from mock import patch, ANY, mock_open
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest.mock import patch, ANY, mock_open # pylint: disable=no-name-in-module, import-error
|
|
||||||
|
|
||||||
from gitlint.tests.base import BaseTestCase
|
from gitlint.tests.base import BaseTestCase
|
||||||
from gitlint.config import LintConfig
|
from gitlint.config import LintConfig
|
||||||
|
@ -19,7 +14,7 @@ class HookTests(BaseTestCase):
|
||||||
|
|
||||||
@patch('gitlint.hooks.git_hooks_dir')
|
@patch('gitlint.hooks.git_hooks_dir')
|
||||||
def test_commit_msg_hook_path(self, git_hooks_dir):
|
def test_commit_msg_hook_path(self, git_hooks_dir):
|
||||||
git_hooks_dir.return_value = os.path.join(u"/föo", u"bar")
|
git_hooks_dir.return_value = os.path.join("/föo", "bar")
|
||||||
lint_config = LintConfig()
|
lint_config = LintConfig()
|
||||||
lint_config.target = self.SAMPLES_DIR
|
lint_config.target = self.SAMPLES_DIR
|
||||||
expected_path = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
|
expected_path = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
|
||||||
|
@ -37,8 +32,8 @@ class HookTests(BaseTestCase):
|
||||||
@patch('gitlint.hooks.git_hooks_dir')
|
@patch('gitlint.hooks.git_hooks_dir')
|
||||||
def test_install_commit_msg_hook(git_hooks_dir, isdir, path_exists, copy, stat, chmod):
|
def test_install_commit_msg_hook(git_hooks_dir, isdir, path_exists, copy, stat, chmod):
|
||||||
lint_config = LintConfig()
|
lint_config = LintConfig()
|
||||||
lint_config.target = os.path.join(u"/hür", u"dur")
|
lint_config.target = os.path.join("/hür", "dur")
|
||||||
git_hooks_dir.return_value = os.path.join(u"/föo", u"bar", ".git", "hooks")
|
git_hooks_dir.return_value = os.path.join("/föo", "bar", ".git", "hooks")
|
||||||
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
|
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
|
||||||
GitHookInstaller.install_commit_msg_hook(lint_config)
|
GitHookInstaller.install_commit_msg_hook(lint_config)
|
||||||
isdir.assert_called_with(git_hooks_dir.return_value)
|
isdir.assert_called_with(git_hooks_dir.return_value)
|
||||||
|
@ -54,11 +49,11 @@ class HookTests(BaseTestCase):
|
||||||
@patch('gitlint.hooks.git_hooks_dir')
|
@patch('gitlint.hooks.git_hooks_dir')
|
||||||
def test_install_commit_msg_hook_negative(self, git_hooks_dir, isdir, path_exists, copy):
|
def test_install_commit_msg_hook_negative(self, git_hooks_dir, isdir, path_exists, copy):
|
||||||
lint_config = LintConfig()
|
lint_config = LintConfig()
|
||||||
lint_config.target = os.path.join(u"/hür", u"dur")
|
lint_config.target = os.path.join("/hür", "dur")
|
||||||
git_hooks_dir.return_value = os.path.join(u"/föo", u"bar", ".git", "hooks")
|
git_hooks_dir.return_value = os.path.join("/föo", "bar", ".git", "hooks")
|
||||||
# mock that current dir is not a git repo
|
# mock that current dir is not a git repo
|
||||||
isdir.return_value = False
|
isdir.return_value = False
|
||||||
expected_msg = u"{0} is not a git repository.".format(lint_config.target)
|
expected_msg = f"{lint_config.target} is not a git repository."
|
||||||
with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
|
with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
|
||||||
GitHookInstaller.install_commit_msg_hook(lint_config)
|
GitHookInstaller.install_commit_msg_hook(lint_config)
|
||||||
isdir.assert_called_with(git_hooks_dir.return_value)
|
isdir.assert_called_with(git_hooks_dir.return_value)
|
||||||
|
@ -69,7 +64,7 @@ class HookTests(BaseTestCase):
|
||||||
isdir.return_value = True
|
isdir.return_value = True
|
||||||
path_exists.return_value = True
|
path_exists.return_value = True
|
||||||
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
|
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
|
||||||
expected_msg = u"There is already a commit-msg hook file present in {0}.\n".format(expected_dst) + \
|
expected_msg = f"There is already a commit-msg hook file present in {expected_dst}.\n" + \
|
||||||
"gitlint currently does not support appending to an existing commit-msg file."
|
"gitlint currently does not support appending to an existing commit-msg file."
|
||||||
with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
|
with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
|
||||||
GitHookInstaller.install_commit_msg_hook(lint_config)
|
GitHookInstaller.install_commit_msg_hook(lint_config)
|
||||||
|
@ -81,8 +76,8 @@ class HookTests(BaseTestCase):
|
||||||
@patch('gitlint.hooks.git_hooks_dir')
|
@patch('gitlint.hooks.git_hooks_dir')
|
||||||
def test_uninstall_commit_msg_hook(git_hooks_dir, isdir, path_exists, remove):
|
def test_uninstall_commit_msg_hook(git_hooks_dir, isdir, path_exists, remove):
|
||||||
lint_config = LintConfig()
|
lint_config = LintConfig()
|
||||||
git_hooks_dir.return_value = os.path.join(u"/föo", u"bar", ".git", "hooks")
|
git_hooks_dir.return_value = os.path.join("/föo", "bar", ".git", "hooks")
|
||||||
lint_config.target = os.path.join(u"/hür", u"dur")
|
lint_config.target = os.path.join("/hür", "dur")
|
||||||
read_data = "#!/bin/sh\n" + GITLINT_HOOK_IDENTIFIER
|
read_data = "#!/bin/sh\n" + GITLINT_HOOK_IDENTIFIER
|
||||||
with patch('gitlint.hooks.io.open', mock_open(read_data=read_data), create=True):
|
with patch('gitlint.hooks.io.open', mock_open(read_data=read_data), create=True):
|
||||||
GitHookInstaller.uninstall_commit_msg_hook(lint_config)
|
GitHookInstaller.uninstall_commit_msg_hook(lint_config)
|
||||||
|
@ -99,12 +94,12 @@ class HookTests(BaseTestCase):
|
||||||
@patch('gitlint.hooks.git_hooks_dir')
|
@patch('gitlint.hooks.git_hooks_dir')
|
||||||
def test_uninstall_commit_msg_hook_negative(self, git_hooks_dir, isdir, path_exists, remove):
|
def test_uninstall_commit_msg_hook_negative(self, git_hooks_dir, isdir, path_exists, remove):
|
||||||
lint_config = LintConfig()
|
lint_config = LintConfig()
|
||||||
lint_config.target = os.path.join(u"/hür", u"dur")
|
lint_config.target = os.path.join("/hür", "dur")
|
||||||
git_hooks_dir.return_value = os.path.join(u"/föo", u"bar", ".git", "hooks")
|
git_hooks_dir.return_value = os.path.join("/föo", "bar", ".git", "hooks")
|
||||||
|
|
||||||
# mock that the current directory is not a git repo
|
# mock that the current directory is not a git repo
|
||||||
isdir.return_value = False
|
isdir.return_value = False
|
||||||
expected_msg = u"{0} is not a git repository.".format(lint_config.target)
|
expected_msg = f"{lint_config.target} is not a git repository."
|
||||||
with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
|
with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
|
||||||
GitHookInstaller.uninstall_commit_msg_hook(lint_config)
|
GitHookInstaller.uninstall_commit_msg_hook(lint_config)
|
||||||
isdir.assert_called_with(git_hooks_dir.return_value)
|
isdir.assert_called_with(git_hooks_dir.return_value)
|
||||||
|
@ -115,7 +110,7 @@ class HookTests(BaseTestCase):
|
||||||
isdir.return_value = True
|
isdir.return_value = True
|
||||||
path_exists.return_value = False
|
path_exists.return_value = False
|
||||||
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
|
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
|
||||||
expected_msg = u"There is no commit-msg hook present in {0}.".format(expected_dst)
|
expected_msg = f"There is no commit-msg hook present in {expected_dst}."
|
||||||
with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
|
with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
|
||||||
GitHookInstaller.uninstall_commit_msg_hook(lint_config)
|
GitHookInstaller.uninstall_commit_msg_hook(lint_config)
|
||||||
isdir.assert_called_with(git_hooks_dir.return_value)
|
isdir.assert_called_with(git_hooks_dir.return_value)
|
||||||
|
@ -127,7 +122,7 @@ class HookTests(BaseTestCase):
|
||||||
path_exists.return_value = True
|
path_exists.return_value = True
|
||||||
read_data = "#!/bin/sh\nfoo"
|
read_data = "#!/bin/sh\nfoo"
|
||||||
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
|
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
|
||||||
expected_msg = u"The commit-msg hook in {0} was not installed by gitlint ".format(expected_dst) + \
|
expected_msg = f"The commit-msg hook in {expected_dst} was not installed by gitlint " + \
|
||||||
"(or it was modified).\nUninstallation of 3th party or modified gitlint hooks " + \
|
"(or it was modified).\nUninstallation of 3th party or modified gitlint hooks " + \
|
||||||
"is not supported."
|
"is not supported."
|
||||||
with patch('gitlint.hooks.io.open', mock_open(read_data=read_data), create=True):
|
with patch('gitlint.hooks.io.open', mock_open(read_data=read_data), create=True):
|
||||||
|
|
|
@ -1,18 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
try:
|
from io import StringIO
|
||||||
# python 2.x
|
|
||||||
from StringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
try:
|
from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
|
||||||
# python 2.x
|
|
||||||
from mock import patch
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
|
|
||||||
|
|
||||||
from gitlint.tests.base import BaseTestCase
|
from gitlint.tests.base import BaseTestCase
|
||||||
from gitlint.lint import GitLinter
|
from gitlint.lint import GitLinter
|
||||||
|
@ -27,14 +17,14 @@ class LintTests(BaseTestCase):
|
||||||
gitcontext = self.gitcontext(self.get_sample("commit_message/sample1"))
|
gitcontext = self.gitcontext(self.get_sample("commit_message/sample1"))
|
||||||
violations = linter.lint(gitcontext.commits[-1])
|
violations = linter.lint(gitcontext.commits[-1])
|
||||||
expected_errors = [RuleViolation("T3", "Title has trailing punctuation (.)",
|
expected_errors = [RuleViolation("T3", "Title has trailing punctuation (.)",
|
||||||
u"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
|
"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
|
||||||
RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
||||||
u"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
|
"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
|
||||||
RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
|
RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
|
||||||
RuleViolation("B1", "Line exceeds max length (135>80)",
|
RuleViolation("B1", "Line exceeds max length (135>80)",
|
||||||
"This is the first line of the commit message body and it is meant to test " +
|
"This is the first line of the commit message body and it is meant to test " +
|
||||||
"a line that exceeds the maximum line length of 80 characters.", 3),
|
"a line that exceeds the maximum line length of 80 characters.", 3),
|
||||||
RuleViolation("B2", "Line has trailing whitespace", u"This line has a tråiling space. ", 4),
|
RuleViolation("B2", "Line has trailing whitespace", "This line has a tråiling space. ", 4),
|
||||||
RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing tab.\t", 5),
|
RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing tab.\t", 5),
|
||||||
RuleViolation("B3", "Line contains hard tab characters (\\t)",
|
RuleViolation("B3", "Line contains hard tab characters (\\t)",
|
||||||
"This line has a trailing tab.\t", 5)]
|
"This line has a trailing tab.\t", 5)]
|
||||||
|
@ -46,7 +36,7 @@ class LintTests(BaseTestCase):
|
||||||
gitcontext = self.gitcontext(self.get_sample("commit_message/sample2"))
|
gitcontext = self.gitcontext(self.get_sample("commit_message/sample2"))
|
||||||
violations = linter.lint(gitcontext.commits[-1])
|
violations = linter.lint(gitcontext.commits[-1])
|
||||||
expected = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
expected = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
||||||
u"Just a title contåining WIP", 1),
|
"Just a title contåining WIP", 1),
|
||||||
RuleViolation("B6", "Body message is missing", None, 3)]
|
RuleViolation("B6", "Body message is missing", None, 3)]
|
||||||
|
|
||||||
self.assertListEqual(violations, expected)
|
self.assertListEqual(violations, expected)
|
||||||
|
@ -56,7 +46,7 @@ class LintTests(BaseTestCase):
|
||||||
gitcontext = self.gitcontext(self.get_sample("commit_message/sample3"))
|
gitcontext = self.gitcontext(self.get_sample("commit_message/sample3"))
|
||||||
violations = linter.lint(gitcontext.commits[-1])
|
violations = linter.lint(gitcontext.commits[-1])
|
||||||
|
|
||||||
title = u" Commit title containing 'WIP', \tleading and tråiling whitespace and longer than 72 characters."
|
title = " Commit title containing 'WIP', \tleading and tråiling whitespace and longer than 72 characters."
|
||||||
expected = [RuleViolation("T1", "Title exceeds max length (95>72)", title, 1),
|
expected = [RuleViolation("T1", "Title exceeds max length (95>72)", title, 1),
|
||||||
RuleViolation("T3", "Title has trailing punctuation (.)", title, 1),
|
RuleViolation("T3", "Title has trailing punctuation (.)", title, 1),
|
||||||
RuleViolation("T4", "Title contains hard tab characters (\\t)", title, 1),
|
RuleViolation("T4", "Title contains hard tab characters (\\t)", title, 1),
|
||||||
|
@ -64,12 +54,12 @@ class LintTests(BaseTestCase):
|
||||||
RuleViolation("T6", "Title has leading whitespace", title, 1),
|
RuleViolation("T6", "Title has leading whitespace", title, 1),
|
||||||
RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
|
RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
|
||||||
RuleViolation("B1", "Line exceeds max length (101>80)",
|
RuleViolation("B1", "Line exceeds max length (101>80)",
|
||||||
u"This is the first line is meånt to test a line that exceeds the maximum line " +
|
"This is the first line is meånt to test a line that exceeds the maximum line " +
|
||||||
"length of 80 characters.", 3),
|
"length of 80 characters.", 3),
|
||||||
RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing space. ", 4),
|
RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing space. ", 4),
|
||||||
RuleViolation("B2", "Line has trailing whitespace", u"This line has a tråiling tab.\t", 5),
|
RuleViolation("B2", "Line has trailing whitespace", "This line has a tråiling tab.\t", 5),
|
||||||
RuleViolation("B3", "Line contains hard tab characters (\\t)",
|
RuleViolation("B3", "Line contains hard tab characters (\\t)",
|
||||||
u"This line has a tråiling tab.\t", 5)]
|
"This line has a tråiling tab.\t", 5)]
|
||||||
|
|
||||||
self.assertListEqual(violations, expected)
|
self.assertListEqual(violations, expected)
|
||||||
|
|
||||||
|
@ -90,13 +80,13 @@ class LintTests(BaseTestCase):
|
||||||
linter = GitLinter(config_builder.build())
|
linter = GitLinter(config_builder.build())
|
||||||
violations = linter.lint(commit)
|
violations = linter.lint(commit)
|
||||||
|
|
||||||
title = u" Commit title containing 'WIP', \tleading and tråiling whitespace and longer than 72 characters."
|
title = " Commit title containing 'WIP', \tleading and tråiling whitespace and longer than 72 characters."
|
||||||
# expect only certain violations because sample5 has a 'gitlint-ignore: T3, T6, body-max-line-length'
|
# expect only certain violations because sample5 has a 'gitlint-ignore: T3, T6, body-max-line-length'
|
||||||
expected = [RuleViolation("T1", "Title exceeds max length (95>72)", title, 1),
|
expected = [RuleViolation("T1", "Title exceeds max length (95>72)", title, 1),
|
||||||
RuleViolation("T4", "Title contains hard tab characters (\\t)", title, 1),
|
RuleViolation("T4", "Title contains hard tab characters (\\t)", title, 1),
|
||||||
RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", title, 1),
|
RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", title, 1),
|
||||||
RuleViolation("B4", "Second line is not empty", u"This line should be ëmpty", 2),
|
RuleViolation("B4", "Second line is not empty", "This line should be ëmpty", 2),
|
||||||
RuleViolation("B2", "Line has trailing whitespace", u"This line has a tråiling space. ", 4),
|
RuleViolation("B2", "Line has trailing whitespace", "This line has a tråiling space. ", 4),
|
||||||
RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing tab.\t", 5),
|
RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing tab.\t", 5),
|
||||||
RuleViolation("B3", "Line contains hard tab characters (\\t)",
|
RuleViolation("B3", "Line contains hard tab characters (\\t)",
|
||||||
"This line has a trailing tab.\t", 5)]
|
"This line has a trailing tab.\t", 5)]
|
||||||
|
@ -106,11 +96,11 @@ class LintTests(BaseTestCase):
|
||||||
""" Lint sample2 but also add some metadata to the commit so we that gets linted as well """
|
""" Lint sample2 but also add some metadata to the commit so we that gets linted as well """
|
||||||
linter = GitLinter(LintConfig())
|
linter = GitLinter(LintConfig())
|
||||||
gitcontext = self.gitcontext(self.get_sample("commit_message/sample2"))
|
gitcontext = self.gitcontext(self.get_sample("commit_message/sample2"))
|
||||||
gitcontext.commits[0].author_email = u"foo bår"
|
gitcontext.commits[0].author_email = "foo bår"
|
||||||
violations = linter.lint(gitcontext.commits[-1])
|
violations = linter.lint(gitcontext.commits[-1])
|
||||||
expected = [RuleViolation("M1", "Author email for commit is invalid", u"foo bår", None),
|
expected = [RuleViolation("M1", "Author email for commit is invalid", "foo bår", None),
|
||||||
RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
||||||
u"Just a title contåining WIP", 1),
|
"Just a title contåining WIP", 1),
|
||||||
RuleViolation("B6", "Body message is missing", None, 3)]
|
RuleViolation("B6", "Body message is missing", None, 3)]
|
||||||
|
|
||||||
self.assertListEqual(violations, expected)
|
self.assertListEqual(violations, expected)
|
||||||
|
@ -123,7 +113,7 @@ class LintTests(BaseTestCase):
|
||||||
|
|
||||||
expected = [RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
|
expected = [RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
|
||||||
RuleViolation("B3", "Line contains hard tab characters (\\t)",
|
RuleViolation("B3", "Line contains hard tab characters (\\t)",
|
||||||
u"This line has a tråiling tab.\t", 5)]
|
"This line has a tråiling tab.\t", 5)]
|
||||||
|
|
||||||
self.assertListEqual(violations, expected)
|
self.assertListEqual(violations, expected)
|
||||||
|
|
||||||
|
@ -146,19 +136,19 @@ class LintTests(BaseTestCase):
|
||||||
|
|
||||||
# Normally we'd expect a B6 violation, but that one is skipped because of the specific ignore set above
|
# Normally we'd expect a B6 violation, but that one is skipped because of the specific ignore set above
|
||||||
expected = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
expected = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
||||||
u"Just a title contåining WIP", 1)]
|
"Just a title contåining WIP", 1)]
|
||||||
|
|
||||||
self.assertListEqual(violations, expected)
|
self.assertListEqual(violations, expected)
|
||||||
|
|
||||||
# Test ignoring body lines
|
# Test ignoring body lines
|
||||||
lint_config = LintConfig()
|
lint_config = LintConfig()
|
||||||
linter = GitLinter(lint_config)
|
linter = GitLinter(lint_config)
|
||||||
lint_config.set_rule_option("I3", "regex", u"(.*)tråiling(.*)")
|
lint_config.set_rule_option("I3", "regex", "(.*)tråiling(.*)")
|
||||||
violations = linter.lint(self.gitcommit(self.get_sample("commit_message/sample1")))
|
violations = linter.lint(self.gitcommit(self.get_sample("commit_message/sample1")))
|
||||||
expected_errors = [RuleViolation("T3", "Title has trailing punctuation (.)",
|
expected_errors = [RuleViolation("T3", "Title has trailing punctuation (.)",
|
||||||
u"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
|
"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
|
||||||
RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
|
||||||
u"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
|
"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
|
||||||
RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
|
RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
|
||||||
RuleViolation("B1", "Line exceeds max length (135>80)",
|
RuleViolation("B1", "Line exceeds max length (135>80)",
|
||||||
"This is the first line of the commit message body and it is meant to test " +
|
"This is the first line of the commit message body and it is meant to test " +
|
||||||
|
@ -171,7 +161,7 @@ class LintTests(BaseTestCase):
|
||||||
|
|
||||||
def test_lint_special_commit(self):
|
def test_lint_special_commit(self):
|
||||||
for commit_type in ["merge", "revert", "squash", "fixup"]:
|
for commit_type in ["merge", "revert", "squash", "fixup"]:
|
||||||
commit = self.gitcommit(self.get_sample("commit_message/{0}".format(commit_type)))
|
commit = self.gitcommit(self.get_sample(f"commit_message/{commit_type}"))
|
||||||
lintconfig = LintConfig()
|
lintconfig = LintConfig()
|
||||||
linter = GitLinter(lintconfig)
|
linter = GitLinter(lintconfig)
|
||||||
violations = linter.lint(commit)
|
violations = linter.lint(commit)
|
||||||
|
@ -180,7 +170,7 @@ class LintTests(BaseTestCase):
|
||||||
self.assertListEqual(violations, [])
|
self.assertListEqual(violations, [])
|
||||||
|
|
||||||
# Check that we do see violations if we disable 'ignore-merge-commits'
|
# Check that we do see violations if we disable 'ignore-merge-commits'
|
||||||
setattr(lintconfig, "ignore_{0}_commits".format(commit_type), False)
|
setattr(lintconfig, f"ignore_{commit_type}_commits", False)
|
||||||
linter = GitLinter(lintconfig)
|
linter = GitLinter(lintconfig)
|
||||||
violations = linter.lint(commit)
|
violations = linter.lint(commit)
|
||||||
self.assertTrue(len(violations) > 0)
|
self.assertTrue(len(violations) > 0)
|
||||||
|
@ -195,7 +185,7 @@ class LintTests(BaseTestCase):
|
||||||
self.assertListEqual(violations, [])
|
self.assertListEqual(violations, [])
|
||||||
|
|
||||||
# Matching regexes shouldn't be a problem
|
# Matching regexes shouldn't be a problem
|
||||||
rule_regexes = [("title-match-regex", u"Tïtle$"), ("body-match-regex", u"Sïgned-Off-By: (.*)$")]
|
rule_regexes = [("title-match-regex", "Tïtle$"), ("body-match-regex", "Sïgned-Off-By: (.*)$")]
|
||||||
for rule_regex in rule_regexes:
|
for rule_regex in rule_regexes:
|
||||||
lintconfig.set_rule_option(rule_regex[0], "regex", rule_regex[1])
|
lintconfig.set_rule_option(rule_regex[0], "regex", rule_regex[1])
|
||||||
violations = linter.lint(commit)
|
violations = linter.lint(commit)
|
||||||
|
@ -203,16 +193,16 @@ class LintTests(BaseTestCase):
|
||||||
|
|
||||||
# Non-matching regexes should return violations
|
# Non-matching regexes should return violations
|
||||||
rule_regexes = [("title-match-regex", ), ("body-match-regex",)]
|
rule_regexes = [("title-match-regex", ), ("body-match-regex",)]
|
||||||
lintconfig.set_rule_option("title-match-regex", "regex", u"^Tïtle")
|
lintconfig.set_rule_option("title-match-regex", "regex", "^Tïtle")
|
||||||
lintconfig.set_rule_option("body-match-regex", "regex", u"Sügned-Off-By: (.*)$")
|
lintconfig.set_rule_option("body-match-regex", "regex", "Sügned-Off-By: (.*)$")
|
||||||
expected_violations = [RuleViolation("T7", u"Title does not match regex (^Tïtle)", u"Normal Commit Tïtle", 1),
|
expected_violations = [RuleViolation("T7", "Title does not match regex (^Tïtle)", "Normal Commit Tïtle", 1),
|
||||||
RuleViolation("B8", u"Body does not match regex (Sügned-Off-By: (.*)$)", None, 6)]
|
RuleViolation("B8", "Body does not match regex (Sügned-Off-By: (.*)$)", None, 6)]
|
||||||
violations = linter.lint(commit)
|
violations = linter.lint(commit)
|
||||||
self.assertListEqual(violations, expected_violations)
|
self.assertListEqual(violations, expected_violations)
|
||||||
|
|
||||||
def test_print_violations(self):
|
def test_print_violations(self):
|
||||||
violations = [RuleViolation("RULE_ID_1", u"Error Messåge 1", "Violating Content 1", None),
|
violations = [RuleViolation("RULE_ID_1", "Error Messåge 1", "Violating Content 1", None),
|
||||||
RuleViolation("RULE_ID_2", "Error Message 2", u"Violåting Content 2", 2)]
|
RuleViolation("RULE_ID_2", "Error Message 2", "Violåting Content 2", 2)]
|
||||||
linter = GitLinter(LintConfig())
|
linter = GitLinter(LintConfig())
|
||||||
|
|
||||||
# test output with increasing verbosity
|
# test output with increasing verbosity
|
||||||
|
@ -224,54 +214,54 @@ class LintTests(BaseTestCase):
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
linter.config.verbosity = 1
|
linter.config.verbosity = 1
|
||||||
linter.print_violations(violations)
|
linter.print_violations(violations)
|
||||||
expected = u"-: RULE_ID_1\n2: RULE_ID_2\n"
|
expected = "-: RULE_ID_1\n2: RULE_ID_2\n"
|
||||||
self.assertEqual(expected, stderr.getvalue())
|
self.assertEqual(expected, stderr.getvalue())
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
linter.config.verbosity = 2
|
linter.config.verbosity = 2
|
||||||
linter.print_violations(violations)
|
linter.print_violations(violations)
|
||||||
expected = u"-: RULE_ID_1 Error Messåge 1\n2: RULE_ID_2 Error Message 2\n"
|
expected = "-: RULE_ID_1 Error Messåge 1\n2: RULE_ID_2 Error Message 2\n"
|
||||||
self.assertEqual(expected, stderr.getvalue())
|
self.assertEqual(expected, stderr.getvalue())
|
||||||
|
|
||||||
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
|
||||||
linter.config.verbosity = 3
|
linter.config.verbosity = 3
|
||||||
linter.print_violations(violations)
|
linter.print_violations(violations)
|
||||||
expected = u"-: RULE_ID_1 Error Messåge 1: \"Violating Content 1\"\n" + \
|
expected = "-: RULE_ID_1 Error Messåge 1: \"Violating Content 1\"\n" + \
|
||||||
u"2: RULE_ID_2 Error Message 2: \"Violåting Content 2\"\n"
|
"2: RULE_ID_2 Error Message 2: \"Violåting Content 2\"\n"
|
||||||
self.assertEqual(expected, stderr.getvalue())
|
self.assertEqual(expected, stderr.getvalue())
|
||||||
|
|
||||||
def test_named_rules(self):
|
def test_named_rules(self):
|
||||||
""" Test that when named rules are present, both them and the original (non-named) rules executed """
|
""" Test that when named rules are present, both them and the original (non-named) rules executed """
|
||||||
|
|
||||||
lint_config = LintConfig()
|
lint_config = LintConfig()
|
||||||
for rule_name in [u"my-ïd", u"another-rule-ïd"]:
|
for rule_name in ["my-ïd", "another-rule-ïd"]:
|
||||||
rule_id = TitleMustNotContainWord.id + ":" + rule_name
|
rule_id = TitleMustNotContainWord.id + ":" + rule_name
|
||||||
lint_config.rules.add_rule(TitleMustNotContainWord, rule_id)
|
lint_config.rules.add_rule(TitleMustNotContainWord, rule_id)
|
||||||
lint_config.set_rule_option(rule_id, "words", [u"Föo"])
|
lint_config.set_rule_option(rule_id, "words", ["Föo"])
|
||||||
linter = GitLinter(lint_config)
|
linter = GitLinter(lint_config)
|
||||||
|
|
||||||
violations = [RuleViolation("T5", u"Title contains the word 'WIP' (case-insensitive)", u"WIP: Föo bar", 1),
|
violations = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", "WIP: Föo bar", 1),
|
||||||
RuleViolation(u"T5:another-rule-ïd", u"Title contains the word 'Föo' (case-insensitive)",
|
RuleViolation("T5:another-rule-ïd", "Title contains the word 'Föo' (case-insensitive)",
|
||||||
u"WIP: Föo bar", 1),
|
"WIP: Föo bar", 1),
|
||||||
RuleViolation(u"T5:my-ïd", u"Title contains the word 'Föo' (case-insensitive)",
|
RuleViolation("T5:my-ïd", "Title contains the word 'Föo' (case-insensitive)",
|
||||||
u"WIP: Föo bar", 1)]
|
"WIP: Föo bar", 1)]
|
||||||
self.assertListEqual(violations, linter.lint(self.gitcommit(u"WIP: Föo bar\n\nFoo bår hur dur bla bla")))
|
self.assertListEqual(violations, linter.lint(self.gitcommit("WIP: Föo bar\n\nFoo bår hur dur bla bla")))
|
||||||
|
|
||||||
def test_ignore_named_rules(self):
|
def test_ignore_named_rules(self):
|
||||||
""" Test that named rules can be ignored """
|
""" Test that named rules can be ignored """
|
||||||
|
|
||||||
# Add named rule to lint config
|
# Add named rule to lint config
|
||||||
config_builder = LintConfigBuilder()
|
config_builder = LintConfigBuilder()
|
||||||
rule_id = TitleMustNotContainWord.id + u":my-ïd"
|
rule_id = TitleMustNotContainWord.id + ":my-ïd"
|
||||||
config_builder.set_option(rule_id, "words", [u"Föo"])
|
config_builder.set_option(rule_id, "words", ["Föo"])
|
||||||
lint_config = config_builder.build()
|
lint_config = config_builder.build()
|
||||||
linter = GitLinter(lint_config)
|
linter = GitLinter(lint_config)
|
||||||
commit = self.gitcommit(u"WIP: Föo bar\n\nFoo bår hur dur bla bla")
|
commit = self.gitcommit("WIP: Föo bar\n\nFoo bår hur dur bla bla")
|
||||||
|
|
||||||
# By default, we expect both the violations of the regular rule as well as the named rule to show up
|
# By default, we expect both the violations of the regular rule as well as the named rule to show up
|
||||||
violations = [RuleViolation("T5", u"Title contains the word 'WIP' (case-insensitive)", u"WIP: Föo bar", 1),
|
violations = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", "WIP: Föo bar", 1),
|
||||||
RuleViolation(u"T5:my-ïd", u"Title contains the word 'Föo' (case-insensitive)",
|
RuleViolation("T5:my-ïd", "Title contains the word 'Föo' (case-insensitive)",
|
||||||
u"WIP: Föo bar", 1)]
|
"WIP: Föo bar", 1)]
|
||||||
self.assertListEqual(violations, linter.lint(commit))
|
self.assertListEqual(violations, linter.lint(commit))
|
||||||
|
|
||||||
# ignore regular rule: only named rule violations show up
|
# ignore regular rule: only named rule violations show up
|
||||||
|
@ -283,5 +273,5 @@ class LintTests(BaseTestCase):
|
||||||
self.assertListEqual(violations[:-1], linter.lint(commit))
|
self.assertListEqual(violations[:-1], linter.lint(commit))
|
||||||
|
|
||||||
# ignore named rule by name: only regular rule violations show up
|
# ignore named rule by name: only regular rule violations show up
|
||||||
lint_config.ignore = [TitleMustNotContainWord.name + u":my-ïd"]
|
lint_config.ignore = [TitleMustNotContainWord.name + ":my-ïd"]
|
||||||
self.assertListEqual(violations[:-1], linter.lint(commit))
|
self.assertListEqual(violations[:-1], linter.lint(commit))
|
||||||
|
|
|
@ -9,25 +9,25 @@ from gitlint.options import IntOption, BoolOption, StrOption, ListOption, PathOp
|
||||||
|
|
||||||
class RuleOptionTests(BaseTestCase):
|
class RuleOptionTests(BaseTestCase):
|
||||||
def test_option_equality(self):
|
def test_option_equality(self):
|
||||||
options = {IntOption: 123, StrOption: u"foöbar", BoolOption: False, ListOption: ["a", "b"],
|
options = {IntOption: 123, StrOption: "foöbar", BoolOption: False, ListOption: ["a", "b"],
|
||||||
PathOption: ".", RegexOption: u"^foöbar(.*)"}
|
PathOption: ".", RegexOption: "^foöbar(.*)"}
|
||||||
for clazz, val in options.items():
|
for clazz, val in options.items():
|
||||||
# 2 options are equal if their name, value and description match
|
# 2 options are equal if their name, value and description match
|
||||||
option1 = clazz(u"test-öption", val, u"Test Dëscription")
|
option1 = clazz("test-öption", val, "Test Dëscription")
|
||||||
option2 = clazz(u"test-öption", val, u"Test Dëscription")
|
option2 = clazz("test-öption", val, "Test Dëscription")
|
||||||
self.assertEqual(option1, option2)
|
self.assertEqual(option1, option2)
|
||||||
|
|
||||||
# Not equal: class, name, description, value are different
|
# Not equal: class, name, description, value are different
|
||||||
self.assertNotEqual(option1, IntOption(u"tëst-option1", 123, u"Test Dëscription"))
|
self.assertNotEqual(option1, IntOption("tëst-option1", 123, "Test Dëscription"))
|
||||||
self.assertNotEqual(option1, StrOption(u"tëst-option1", u"åbc", u"Test Dëscription"))
|
self.assertNotEqual(option1, StrOption("tëst-option1", "åbc", "Test Dëscription"))
|
||||||
self.assertNotEqual(option1, StrOption(u"tëst-option", u"åbcd", u"Test Dëscription"))
|
self.assertNotEqual(option1, StrOption("tëst-option", "åbcd", "Test Dëscription"))
|
||||||
self.assertNotEqual(option1, StrOption(u"tëst-option", u"åbc", u"Test Dëscription2"))
|
self.assertNotEqual(option1, StrOption("tëst-option", "åbc", "Test Dëscription2"))
|
||||||
|
|
||||||
def test_int_option(self):
|
def test_int_option(self):
|
||||||
# normal behavior
|
# normal behavior
|
||||||
option = IntOption(u"tëst-name", 123, u"Tëst Description")
|
option = IntOption("tëst-name", 123, "Tëst Description")
|
||||||
self.assertEqual(option.name, u"tëst-name")
|
self.assertEqual(option.name, "tëst-name")
|
||||||
self.assertEqual(option.description, u"Tëst Description")
|
self.assertEqual(option.description, "Tëst Description")
|
||||||
self.assertEqual(option.value, 123)
|
self.assertEqual(option.value, 123)
|
||||||
|
|
||||||
# re-set value
|
# re-set value
|
||||||
|
@ -39,12 +39,12 @@ class RuleOptionTests(BaseTestCase):
|
||||||
self.assertEqual(option.value, None)
|
self.assertEqual(option.value, None)
|
||||||
|
|
||||||
# error on negative int when not allowed
|
# error on negative int when not allowed
|
||||||
expected_error = u"Option 'tëst-name' must be a positive integer (current value: '-123')"
|
expected_error = "Option 'tëst-name' must be a positive integer (current value: '-123')"
|
||||||
with self.assertRaisesMessage(RuleOptionError, expected_error):
|
with self.assertRaisesMessage(RuleOptionError, expected_error):
|
||||||
option.set(-123)
|
option.set(-123)
|
||||||
|
|
||||||
# error on non-int value
|
# error on non-int value
|
||||||
expected_error = u"Option 'tëst-name' must be a positive integer (current value: 'foo')"
|
expected_error = "Option 'tëst-name' must be a positive integer (current value: 'foo')"
|
||||||
with self.assertRaisesMessage(RuleOptionError, expected_error):
|
with self.assertRaisesMessage(RuleOptionError, expected_error):
|
||||||
option.set("foo")
|
option.set("foo")
|
||||||
|
|
||||||
|
@ -54,20 +54,20 @@ class RuleOptionTests(BaseTestCase):
|
||||||
self.assertEqual(option.value, -456)
|
self.assertEqual(option.value, -456)
|
||||||
|
|
||||||
# error on non-int value when negative int is allowed
|
# error on non-int value when negative int is allowed
|
||||||
expected_error = u"Option 'test-name' must be an integer (current value: 'foo')"
|
expected_error = "Option 'test-name' must be an integer (current value: 'foo')"
|
||||||
with self.assertRaisesMessage(RuleOptionError, expected_error):
|
with self.assertRaisesMessage(RuleOptionError, expected_error):
|
||||||
option.set("foo")
|
option.set("foo")
|
||||||
|
|
||||||
def test_str_option(self):
|
def test_str_option(self):
|
||||||
# normal behavior
|
# normal behavior
|
||||||
option = StrOption(u"tëst-name", u"föo", u"Tëst Description")
|
option = StrOption("tëst-name", "föo", "Tëst Description")
|
||||||
self.assertEqual(option.name, u"tëst-name")
|
self.assertEqual(option.name, "tëst-name")
|
||||||
self.assertEqual(option.description, u"Tëst Description")
|
self.assertEqual(option.description, "Tëst Description")
|
||||||
self.assertEqual(option.value, u"föo")
|
self.assertEqual(option.value, "föo")
|
||||||
|
|
||||||
# re-set value
|
# re-set value
|
||||||
option.set(u"bår")
|
option.set("bår")
|
||||||
self.assertEqual(option.value, u"bår")
|
self.assertEqual(option.value, "bår")
|
||||||
|
|
||||||
# conversion to str
|
# conversion to str
|
||||||
option.set(123)
|
option.set(123)
|
||||||
|
@ -83,9 +83,9 @@ class RuleOptionTests(BaseTestCase):
|
||||||
|
|
||||||
def test_boolean_option(self):
|
def test_boolean_option(self):
|
||||||
# normal behavior
|
# normal behavior
|
||||||
option = BoolOption(u"tëst-name", "true", u"Tëst Description")
|
option = BoolOption("tëst-name", "true", "Tëst Description")
|
||||||
self.assertEqual(option.name, u"tëst-name")
|
self.assertEqual(option.name, "tëst-name")
|
||||||
self.assertEqual(option.description, u"Tëst Description")
|
self.assertEqual(option.description, "Tëst Description")
|
||||||
self.assertEqual(option.value, True)
|
self.assertEqual(option.value, True)
|
||||||
|
|
||||||
# re-set value
|
# re-set value
|
||||||
|
@ -97,25 +97,25 @@ class RuleOptionTests(BaseTestCase):
|
||||||
self.assertEqual(option.value, True)
|
self.assertEqual(option.value, True)
|
||||||
|
|
||||||
# error on incorrect value
|
# error on incorrect value
|
||||||
incorrect_values = [1, -1, "foo", u"bår", ["foo"], {'foo': "bar"}, None]
|
incorrect_values = [1, -1, "foo", "bår", ["foo"], {'foo': "bar"}, None]
|
||||||
for value in incorrect_values:
|
for value in incorrect_values:
|
||||||
with self.assertRaisesMessage(RuleOptionError, u"Option 'tëst-name' must be either 'true' or 'false'"):
|
with self.assertRaisesMessage(RuleOptionError, "Option 'tëst-name' must be either 'true' or 'false'"):
|
||||||
option.set(value)
|
option.set(value)
|
||||||
|
|
||||||
def test_list_option(self):
|
def test_list_option(self):
|
||||||
# normal behavior
|
# normal behavior
|
||||||
option = ListOption(u"tëst-name", u"å,b,c,d", u"Tëst Description")
|
option = ListOption("tëst-name", "å,b,c,d", "Tëst Description")
|
||||||
self.assertEqual(option.name, u"tëst-name")
|
self.assertEqual(option.name, "tëst-name")
|
||||||
self.assertEqual(option.description, u"Tëst Description")
|
self.assertEqual(option.description, "Tëst Description")
|
||||||
self.assertListEqual(option.value, [u"å", u"b", u"c", u"d"])
|
self.assertListEqual(option.value, ["å", "b", "c", "d"])
|
||||||
|
|
||||||
# re-set value
|
# re-set value
|
||||||
option.set(u"1,2,3,4")
|
option.set("1,2,3,4")
|
||||||
self.assertListEqual(option.value, [u"1", u"2", u"3", u"4"])
|
self.assertListEqual(option.value, ["1", "2", "3", "4"])
|
||||||
|
|
||||||
# set list
|
# set list
|
||||||
option.set([u"foo", u"bår", u"test"])
|
option.set(["foo", "bår", "test"])
|
||||||
self.assertListEqual(option.value, [u"foo", u"bår", u"test"])
|
self.assertListEqual(option.value, ["foo", "bår", "test"])
|
||||||
|
|
||||||
# None
|
# None
|
||||||
option.set(None)
|
option.set(None)
|
||||||
|
@ -134,8 +134,8 @@ class RuleOptionTests(BaseTestCase):
|
||||||
self.assertListEqual(option.value, [])
|
self.assertListEqual(option.value, [])
|
||||||
|
|
||||||
# trailing comma
|
# trailing comma
|
||||||
option.set(u"ë,f,g,")
|
option.set("ë,f,g,")
|
||||||
self.assertListEqual(option.value, [u"ë", u"f", u"g"])
|
self.assertListEqual(option.value, ["ë", "f", "g"])
|
||||||
|
|
||||||
# leading and trailing whitespace should be trimmed, but only deduped within text
|
# leading and trailing whitespace should be trimmed, but only deduped within text
|
||||||
option.set(" abc , def , ghi \t , jkl mno ")
|
option.set(" abc , def , ghi \t , jkl mno ")
|
||||||
|
@ -150,11 +150,11 @@ class RuleOptionTests(BaseTestCase):
|
||||||
self.assertListEqual(option.value, ["123"])
|
self.assertListEqual(option.value, ["123"])
|
||||||
|
|
||||||
def test_path_option(self):
|
def test_path_option(self):
|
||||||
option = PathOption(u"tëst-directory", ".", u"Tëst Description", type=u"dir")
|
option = PathOption("tëst-directory", ".", "Tëst Description", type="dir")
|
||||||
self.assertEqual(option.name, u"tëst-directory")
|
self.assertEqual(option.name, "tëst-directory")
|
||||||
self.assertEqual(option.description, u"Tëst Description")
|
self.assertEqual(option.description, "Tëst Description")
|
||||||
self.assertEqual(option.value, os.getcwd())
|
self.assertEqual(option.value, os.getcwd())
|
||||||
self.assertEqual(option.type, u"dir")
|
self.assertEqual(option.type, "dir")
|
||||||
|
|
||||||
# re-set value
|
# re-set value
|
||||||
option.set(self.SAMPLES_DIR)
|
option.set(self.SAMPLES_DIR)
|
||||||
|
@ -165,33 +165,32 @@ class RuleOptionTests(BaseTestCase):
|
||||||
self.assertIsNone(option.value)
|
self.assertIsNone(option.value)
|
||||||
|
|
||||||
# set to int
|
# set to int
|
||||||
expected = u"Option tëst-directory must be an existing directory (current value: '1234')"
|
expected = "Option tëst-directory must be an existing directory (current value: '1234')"
|
||||||
with self.assertRaisesMessage(RuleOptionError, expected):
|
with self.assertRaisesMessage(RuleOptionError, expected):
|
||||||
option.set(1234)
|
option.set(1234)
|
||||||
|
|
||||||
# set to non-existing directory
|
# set to non-existing directory
|
||||||
non_existing_path = os.path.join(u"/föo", u"bar")
|
non_existing_path = os.path.join("/föo", "bar")
|
||||||
expected = u"Option tëst-directory must be an existing directory (current value: '{0}')"
|
expected = f"Option tëst-directory must be an existing directory (current value: '{non_existing_path}')"
|
||||||
with self.assertRaisesMessage(RuleOptionError, expected.format(non_existing_path)):
|
with self.assertRaisesMessage(RuleOptionError, expected):
|
||||||
option.set(non_existing_path)
|
option.set(non_existing_path)
|
||||||
|
|
||||||
# set to a file, should raise exception since option.type = dir
|
# set to a file, should raise exception since option.type = dir
|
||||||
sample_path = self.get_sample_path(os.path.join("commit_message", "sample1"))
|
sample_path = self.get_sample_path(os.path.join("commit_message", "sample1"))
|
||||||
expected = u"Option tëst-directory must be an existing directory (current value: '{0}')".format(sample_path)
|
expected = f"Option tëst-directory must be an existing directory (current value: '{sample_path}')"
|
||||||
with self.assertRaisesMessage(RuleOptionError, expected):
|
with self.assertRaisesMessage(RuleOptionError, expected):
|
||||||
option.set(sample_path)
|
option.set(sample_path)
|
||||||
|
|
||||||
# set option.type = file, file should now be accepted, directories not
|
# set option.type = file, file should now be accepted, directories not
|
||||||
option.type = u"file"
|
option.type = "file"
|
||||||
option.set(sample_path)
|
option.set(sample_path)
|
||||||
self.assertEqual(option.value, sample_path)
|
self.assertEqual(option.value, sample_path)
|
||||||
expected = u"Option tëst-directory must be an existing file (current value: '{0}')".format(
|
expected = f"Option tëst-directory must be an existing file (current value: '{self.get_sample_path()}')"
|
||||||
self.get_sample_path())
|
|
||||||
with self.assertRaisesMessage(RuleOptionError, expected):
|
with self.assertRaisesMessage(RuleOptionError, expected):
|
||||||
option.set(self.get_sample_path())
|
option.set(self.get_sample_path())
|
||||||
|
|
||||||
# set option.type = both, files and directories should now be accepted
|
# set option.type = both, files and directories should now be accepted
|
||||||
option.type = u"both"
|
option.type = "both"
|
||||||
option.set(sample_path)
|
option.set(sample_path)
|
||||||
self.assertEqual(option.value, sample_path)
|
self.assertEqual(option.value, sample_path)
|
||||||
option.set(self.get_sample_path())
|
option.set(self.get_sample_path())
|
||||||
|
@ -199,27 +198,27 @@ class RuleOptionTests(BaseTestCase):
|
||||||
|
|
||||||
# Expect exception if path type is invalid
|
# Expect exception if path type is invalid
|
||||||
option.type = u'föo'
|
option.type = u'föo'
|
||||||
expected = u"Option tëst-directory type must be one of: 'file', 'dir', 'both' (current: 'föo')"
|
expected = "Option tëst-directory type must be one of: 'file', 'dir', 'both' (current: 'föo')"
|
||||||
with self.assertRaisesMessage(RuleOptionError, expected):
|
with self.assertRaisesMessage(RuleOptionError, expected):
|
||||||
option.set("haha")
|
option.set("haha")
|
||||||
|
|
||||||
def test_regex_option(self):
|
def test_regex_option(self):
|
||||||
# normal behavior
|
# normal behavior
|
||||||
option = RegexOption(u"tëst-regex", u"^myrëgex(.*)foo$", u"Tëst Regex Description")
|
option = RegexOption("tëst-regex", "^myrëgex(.*)foo$", "Tëst Regex Description")
|
||||||
self.assertEqual(option.name, u"tëst-regex")
|
self.assertEqual(option.name, "tëst-regex")
|
||||||
self.assertEqual(option.description, u"Tëst Regex Description")
|
self.assertEqual(option.description, "Tëst Regex Description")
|
||||||
self.assertEqual(option.value, re.compile(u"^myrëgex(.*)foo$", re.UNICODE))
|
self.assertEqual(option.value, re.compile("^myrëgex(.*)foo$", re.UNICODE))
|
||||||
|
|
||||||
# re-set value
|
# re-set value
|
||||||
option.set(u"[0-9]föbar.*")
|
option.set("[0-9]föbar.*")
|
||||||
self.assertEqual(option.value, re.compile(u"[0-9]föbar.*", re.UNICODE))
|
self.assertEqual(option.value, re.compile("[0-9]föbar.*", re.UNICODE))
|
||||||
|
|
||||||
# set None
|
# set None
|
||||||
option.set(None)
|
option.set(None)
|
||||||
self.assertIsNone(option.value)
|
self.assertIsNone(option.value)
|
||||||
|
|
||||||
# error on invalid regex
|
# error on invalid regex
|
||||||
incorrect_values = [u"foo(", 123, -1]
|
incorrect_values = ["foo(", 123, -1]
|
||||||
for value in incorrect_values:
|
for value in incorrect_values:
|
||||||
with self.assertRaisesRegex(RuleOptionError, u"Invalid regular expression"):
|
with self.assertRaisesRegex(RuleOptionError, "Invalid regular expression"):
|
||||||
option.set(value)
|
option.set(value)
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from gitlint import utils
|
from gitlint import utils
|
||||||
from gitlint.tests.base import BaseTestCase
|
from gitlint.tests.base import BaseTestCase
|
||||||
|
|
||||||
try:
|
|
||||||
# python 2.x
|
|
||||||
from mock import patch
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
|
|
||||||
|
|
||||||
|
|
||||||
class UtilsTests(BaseTestCase):
|
class UtilsTests(BaseTestCase):
|
||||||
|
|
||||||
|
@ -24,7 +19,7 @@ class UtilsTests(BaseTestCase):
|
||||||
self.assertEqual(utils.use_sh_library(), True)
|
self.assertEqual(utils.use_sh_library(), True)
|
||||||
patched_env.get.assert_called_once_with("GITLINT_USE_SH_LIB", None)
|
patched_env.get.assert_called_once_with("GITLINT_USE_SH_LIB", None)
|
||||||
|
|
||||||
for invalid_val in ["0", u"foöbar"]:
|
for invalid_val in ["0", "foöbar"]:
|
||||||
patched_env.get.reset_mock() # reset mock call count
|
patched_env.get.reset_mock() # reset mock call count
|
||||||
patched_env.get.return_value = invalid_val
|
patched_env.get.return_value = invalid_val
|
||||||
self.assertEqual(utils.use_sh_library(), False, invalid_val)
|
self.assertEqual(utils.use_sh_library(), False, invalid_val)
|
||||||
|
@ -41,12 +36,12 @@ class UtilsTests(BaseTestCase):
|
||||||
@patch('gitlint.utils.locale')
|
@patch('gitlint.utils.locale')
|
||||||
def test_default_encoding_non_windows(self, mocked_locale):
|
def test_default_encoding_non_windows(self, mocked_locale):
|
||||||
utils.PLATFORM_IS_WINDOWS = False
|
utils.PLATFORM_IS_WINDOWS = False
|
||||||
mocked_locale.getpreferredencoding.return_value = u"foöbar"
|
mocked_locale.getpreferredencoding.return_value = "foöbar"
|
||||||
self.assertEqual(utils.getpreferredencoding(), u"foöbar")
|
self.assertEqual(utils.getpreferredencoding(), "foöbar")
|
||||||
mocked_locale.getpreferredencoding.assert_called_once()
|
mocked_locale.getpreferredencoding.assert_called_once()
|
||||||
|
|
||||||
mocked_locale.getpreferredencoding.return_value = False
|
mocked_locale.getpreferredencoding.return_value = False
|
||||||
self.assertEqual(utils.getpreferredencoding(), u"UTF-8")
|
self.assertEqual(utils.getpreferredencoding(), "UTF-8")
|
||||||
|
|
||||||
@patch('os.environ')
|
@patch('os.environ')
|
||||||
def test_default_encoding_windows(self, patched_env):
|
def test_default_encoding_windows(self, patched_env):
|
||||||
|
@ -60,23 +55,23 @@ class UtilsTests(BaseTestCase):
|
||||||
patched_env.get.side_effect = mocked_get
|
patched_env.get.side_effect = mocked_get
|
||||||
|
|
||||||
# Assert getpreferredencoding reads env vars in order: LC_ALL, LC_CTYPE, LANG
|
# Assert getpreferredencoding reads env vars in order: LC_ALL, LC_CTYPE, LANG
|
||||||
mock_env = {"LC_ALL": u"ASCII", "LC_CTYPE": u"UTF-16", "LANG": u"CP1251"}
|
mock_env = {"LC_ALL": "ASCII", "LC_CTYPE": "UTF-16", "LANG": "CP1251"}
|
||||||
self.assertEqual(utils.getpreferredencoding(), u"ASCII")
|
self.assertEqual(utils.getpreferredencoding(), "ASCII")
|
||||||
mock_env = {"LC_CTYPE": u"UTF-16", "LANG": u"CP1251"}
|
mock_env = {"LC_CTYPE": "UTF-16", "LANG": "CP1251"}
|
||||||
self.assertEqual(utils.getpreferredencoding(), u"UTF-16")
|
self.assertEqual(utils.getpreferredencoding(), "UTF-16")
|
||||||
mock_env = {"LANG": u"CP1251"}
|
mock_env = {"LANG": "CP1251"}
|
||||||
self.assertEqual(utils.getpreferredencoding(), u"CP1251")
|
self.assertEqual(utils.getpreferredencoding(), "CP1251")
|
||||||
|
|
||||||
# Assert split on dot
|
# Assert split on dot
|
||||||
mock_env = {"LANG": u"foo.UTF-16"}
|
mock_env = {"LANG": "foo.UTF-16"}
|
||||||
self.assertEqual(utils.getpreferredencoding(), u"UTF-16")
|
self.assertEqual(utils.getpreferredencoding(), "UTF-16")
|
||||||
|
|
||||||
# assert default encoding is UTF-8
|
# assert default encoding is UTF-8
|
||||||
mock_env = {}
|
mock_env = {}
|
||||||
self.assertEqual(utils.getpreferredencoding(), "UTF-8")
|
self.assertEqual(utils.getpreferredencoding(), "UTF-8")
|
||||||
mock_env = {"FOO": u"föo"}
|
mock_env = {"FOO": "föo"}
|
||||||
self.assertEqual(utils.getpreferredencoding(), "UTF-8")
|
self.assertEqual(utils.getpreferredencoding(), "UTF-8")
|
||||||
|
|
||||||
# assert fallback encoding is UTF-8 in case we set an unavailable encoding
|
# assert fallback encoding is UTF-8 in case we set an unavailable encoding
|
||||||
mock_env = {"LC_ALL": u"foo"}
|
mock_env = {"LC_ALL": "foo"}
|
||||||
self.assertEqual(utils.getpreferredencoding(), u"UTF-8")
|
self.assertEqual(utils.getpreferredencoding(), "UTF-8")
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable,no-else-return
|
# pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable,no-else-return
|
||||||
import codecs
|
import codecs
|
||||||
import platform
|
import platform
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import locale
|
import locale
|
||||||
|
@ -24,16 +23,6 @@ def platform_is_windows():
|
||||||
|
|
||||||
PLATFORM_IS_WINDOWS = platform_is_windows()
|
PLATFORM_IS_WINDOWS = platform_is_windows()
|
||||||
|
|
||||||
########################################################################################################################
|
|
||||||
# IS_PY2
|
|
||||||
|
|
||||||
|
|
||||||
def is_py2():
|
|
||||||
return sys.version_info[0] == 2
|
|
||||||
|
|
||||||
|
|
||||||
IS_PY2 = is_py2()
|
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
# USE_SH_LIB
|
# USE_SH_LIB
|
||||||
# Determine whether to use the `sh` library
|
# Determine whether to use the `sh` library
|
||||||
|
@ -90,39 +79,3 @@ def getpreferredencoding():
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_ENCODING = getpreferredencoding()
|
DEFAULT_ENCODING = getpreferredencoding()
|
||||||
|
|
||||||
########################################################################################################################
|
|
||||||
# Unicode utility functions
|
|
||||||
|
|
||||||
|
|
||||||
def ustr(obj):
|
|
||||||
""" Python 2 and 3 utility method that converts an obj to unicode in python 2 and to a str object in python 3"""
|
|
||||||
if IS_PY2:
|
|
||||||
# If we are getting a string, then do an explicit decode
|
|
||||||
# else, just call the unicode method of the object
|
|
||||||
if type(obj) in [str, basestring]: # pragma: no cover # noqa
|
|
||||||
return unicode(obj, DEFAULT_ENCODING) # pragma: no cover # noqa
|
|
||||||
else:
|
|
||||||
return unicode(obj) # pragma: no cover # noqa
|
|
||||||
else:
|
|
||||||
if type(obj) in [bytes]:
|
|
||||||
return obj.decode(DEFAULT_ENCODING)
|
|
||||||
else:
|
|
||||||
return str(obj)
|
|
||||||
|
|
||||||
|
|
||||||
def sstr(obj):
|
|
||||||
""" Python 2 and 3 utility method that converts an obj to a DEFAULT_ENCODING encoded string in python 2
|
|
||||||
and to unicode in python 3.
|
|
||||||
Especially useful for implementing __str__ methods in python 2: http://stackoverflow.com/a/1307210/381010"""
|
|
||||||
if IS_PY2:
|
|
||||||
# For lists and tuples in python2, remove unicode string representation characters.
|
|
||||||
# i.e. ensure lists are printed as ['a', 'b'] and not [u'a', u'b']
|
|
||||||
if type(obj) in [list]:
|
|
||||||
return [sstr(item) for item in obj] # pragma: no cover # noqa
|
|
||||||
elif type(obj) in [tuple]:
|
|
||||||
return tuple(sstr(item) for item in obj) # pragma: no cover # noqa
|
|
||||||
|
|
||||||
return unicode(obj).encode(DEFAULT_ENCODING) # pragma: no cover # noqa
|
|
||||||
else:
|
|
||||||
return obj # pragma: no cover
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ site_name: Gitlint
|
||||||
site_description: Linting for your git commit messages
|
site_description: Linting for your git commit messages
|
||||||
site_url: http://jorisroovers.github.io/gitlint/
|
site_url: http://jorisroovers.github.io/gitlint/
|
||||||
repo_url: https://github.com/jorisroovers/gitlint
|
repo_url: https://github.com/jorisroovers/gitlint
|
||||||
|
edit_uri: edit/main/docs
|
||||||
nav:
|
nav:
|
||||||
- Home: index.md
|
- Home: index.md
|
||||||
- Configuration: configuration.md
|
- Configuration: configuration.md
|
||||||
|
|
20
qa/base.py
20
qa/base.py
|
@ -10,18 +10,13 @@ import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
|
|
||||||
try:
|
|
||||||
# python 2.x
|
|
||||||
from unittest2 import TestCase
|
|
||||||
except ImportError:
|
|
||||||
# python 3.x
|
|
||||||
from unittest import TestCase
|
|
||||||
|
|
||||||
from qa.shell import git, gitlint, RunningCommand
|
from qa.shell import git, gitlint, RunningCommand
|
||||||
from qa.utils import DEFAULT_ENCODING, ustr
|
from qa.utils import DEFAULT_ENCODING
|
||||||
|
|
||||||
|
|
||||||
class BaseTestCase(TestCase):
|
class BaseTestCase(TestCase):
|
||||||
|
@ -56,13 +51,14 @@ class BaseTestCase(TestCase):
|
||||||
|
|
||||||
def assertEqualStdout(self, output, expected): # pylint: disable=invalid-name
|
def assertEqualStdout(self, output, expected): # pylint: disable=invalid-name
|
||||||
self.assertIsInstance(output, RunningCommand)
|
self.assertIsInstance(output, RunningCommand)
|
||||||
output = ustr(output.stdout)
|
output = output.stdout.decode(DEFAULT_ENCODING)
|
||||||
output = output.replace('\r', '')
|
output = output.replace('\r', '')
|
||||||
self.assertMultiLineEqual(output, expected)
|
self.assertMultiLineEqual(output, expected)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_temp_path(cls):
|
def generate_temp_path(cls):
|
||||||
return os.path.realpath("/tmp/gitlint-test-{0}".format(datetime.now().strftime("%Y%m%d-%H%M%S-%f")))
|
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S-%f")
|
||||||
|
return os.path.realpath(f"/tmp/gitlint-test-{timestamp}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_tmp_git_repo(cls):
|
def create_tmp_git_repo(cls):
|
||||||
|
@ -89,7 +85,7 @@ class BaseTestCase(TestCase):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_file(parent_dir):
|
def create_file(parent_dir):
|
||||||
""" Creates a file inside a passed directory. Returns filename."""
|
""" Creates a file inside a passed directory. Returns filename."""
|
||||||
test_filename = u"test-fïle-" + str(uuid4())
|
test_filename = "test-fïle-" + str(uuid4())
|
||||||
io.open(os.path.join(parent_dir, test_filename), 'a', encoding=DEFAULT_ENCODING).close()
|
io.open(os.path.join(parent_dir, test_filename), 'a', encoding=DEFAULT_ENCODING).close()
|
||||||
return test_filename
|
return test_filename
|
||||||
|
|
||||||
|
@ -171,8 +167,8 @@ class BaseTestCase(TestCase):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_system_info_dict():
|
def get_system_info_dict():
|
||||||
""" Returns a dict with items related to system values logged by `gitlint --debug` """
|
""" Returns a dict with items related to system values logged by `gitlint --debug` """
|
||||||
expected_gitlint_version = gitlint("--version").replace("gitlint, version ", "").replace("\n", "")
|
expected_gitlint_version = gitlint("--version").replace("gitlint, version ", "").strip()
|
||||||
expected_git_version = git("--version").replace("\n", "")
|
expected_git_version = git("--version").strip()
|
||||||
return {'platform': platform.platform(), 'python_version': sys.version,
|
return {'platform': platform.platform(), 'python_version': sys.version,
|
||||||
'git_version': expected_git_version, 'gitlint_version': expected_gitlint_version,
|
'git_version': expected_git_version, 'gitlint_version': expected_gitlint_version,
|
||||||
'GITLINT_USE_SH_LIB': BaseTestCase.GITLINT_USE_SH_LIB, 'DEFAULT_ENCODING': DEFAULT_ENCODING}
|
'GITLINT_USE_SH_LIB': BaseTestCase.GITLINT_USE_SH_LIB, 'DEFAULT_ENCODING': DEFAULT_ENCODING}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
sh==1.12.14
|
sh==1.14.1
|
||||||
pytest==4.6.3;
|
pytest==6.1.2;
|
||||||
arrow==0.15.5;
|
arrow==0.17.0;
|
||||||
gitlint # no version as you want to test the currently installed version
|
gitlint # no version as you want to test the currently installed version
|
||||||
|
|
|
@ -2,18 +2,17 @@
|
||||||
|
|
||||||
from gitlint.rules import CommitRule, RuleViolation, ConfigurationRule
|
from gitlint.rules import CommitRule, RuleViolation, ConfigurationRule
|
||||||
from gitlint.options import IntOption, StrOption, ListOption
|
from gitlint.options import IntOption, StrOption, ListOption
|
||||||
from gitlint.utils import sstr
|
|
||||||
|
|
||||||
|
|
||||||
class GitContextRule(CommitRule):
|
class GitContextRule(CommitRule):
|
||||||
""" Rule that tests whether we can correctly access certain gitcontext properties """
|
""" Rule that tests whether we can correctly access certain gitcontext properties """
|
||||||
name = u"gïtcontext"
|
name = "gïtcontext"
|
||||||
id = "UC1"
|
id = "UC1"
|
||||||
|
|
||||||
def validate(self, commit):
|
def validate(self, commit):
|
||||||
violations = [
|
violations = [
|
||||||
RuleViolation(self.id, u"GitContext.current_branch: {0}".format(commit.context.current_branch), line_nr=1),
|
RuleViolation(self.id, f"GitContext.current_branch: {commit.context.current_branch}", line_nr=1),
|
||||||
RuleViolation(self.id, u"GitContext.commentchar: {0}".format(commit.context.commentchar), line_nr=1)
|
RuleViolation(self.id, f"GitContext.commentchar: {commit.context.commentchar}", line_nr=1)
|
||||||
]
|
]
|
||||||
|
|
||||||
return violations
|
return violations
|
||||||
|
@ -21,13 +20,13 @@ class GitContextRule(CommitRule):
|
||||||
|
|
||||||
class GitCommitRule(CommitRule):
|
class GitCommitRule(CommitRule):
|
||||||
""" Rule that tests whether we can correctly access certain commit properties """
|
""" Rule that tests whether we can correctly access certain commit properties """
|
||||||
name = u"gïtcommit"
|
name = "gïtcommit"
|
||||||
id = "UC2"
|
id = "UC2"
|
||||||
|
|
||||||
def validate(self, commit):
|
def validate(self, commit):
|
||||||
violations = [
|
violations = [
|
||||||
RuleViolation(self.id, u"GitCommit.branches: {0}".format(sstr(commit.branches)), line_nr=1),
|
RuleViolation(self.id, f"GitCommit.branches: {commit.branches}", line_nr=1),
|
||||||
RuleViolation(self.id, u"GitCommit.custom_prop: {0}".format(commit.custom_prop), line_nr=1),
|
RuleViolation(self.id, f"GitCommit.custom_prop: {commit.custom_prop}", line_nr=1),
|
||||||
]
|
]
|
||||||
|
|
||||||
return violations
|
return violations
|
||||||
|
@ -35,16 +34,16 @@ class GitCommitRule(CommitRule):
|
||||||
|
|
||||||
class GitlintConfigurationRule(ConfigurationRule):
|
class GitlintConfigurationRule(ConfigurationRule):
|
||||||
""" Rule that tests whether we can correctly access the config as well as modify the commit message """
|
""" Rule that tests whether we can correctly access the config as well as modify the commit message """
|
||||||
name = u"cönfigrule"
|
name = "cönfigrule"
|
||||||
id = "UC3"
|
id = "UC3"
|
||||||
|
|
||||||
def apply(self, config, commit):
|
def apply(self, config, commit):
|
||||||
# We add a line to the commit message body that pulls a value from config, this proves we can modify the body
|
# We add a line to the commit message body that pulls a value from config, this proves we can modify the body
|
||||||
# and read the config contents
|
# and read the config contents
|
||||||
commit.message.body.append("{0} ".format(config.target)) # trailing whitespace deliberate to trigger violation
|
commit.message.body.append(f"{config.target} ") # trailing whitespace deliberate to trigger violation
|
||||||
|
|
||||||
# We set a custom property that we access in CommitRule, to prove we can add extra properties to the commit
|
# We set a custom property that we access in CommitRule, to prove we can add extra properties to the commit
|
||||||
commit.custom_prop = u"foöbar"
|
commit.custom_prop = "foöbar"
|
||||||
|
|
||||||
# We also ignore some extra rules, proving that we can modify the config
|
# We also ignore some extra rules, proving that we can modify the config
|
||||||
config.ignore.append("B4")
|
config.ignore.append("B4")
|
||||||
|
@ -52,18 +51,18 @@ class GitlintConfigurationRule(ConfigurationRule):
|
||||||
|
|
||||||
class ConfigurableCommitRule(CommitRule):
|
class ConfigurableCommitRule(CommitRule):
|
||||||
""" Rule that tests that we can add configuration to user-defined rules """
|
""" Rule that tests that we can add configuration to user-defined rules """
|
||||||
name = u"configürable"
|
name = "configürable"
|
||||||
id = "UC4"
|
id = "UC4"
|
||||||
|
|
||||||
options_spec = [IntOption(u"int-öption", 2, u"int-öption description"),
|
options_spec = [IntOption("int-öption", 2, "int-öption description"),
|
||||||
StrOption(u"str-öption", u"föo", u"int-öption description"),
|
StrOption("str-öption", "föo", "int-öption description"),
|
||||||
ListOption(u"list-öption", [u"foo", u"bar"], u"list-öption description")]
|
ListOption("list-öption", ["foo", "bar"], "list-öption description")]
|
||||||
|
|
||||||
def validate(self, _):
|
def validate(self, _):
|
||||||
violations = [
|
violations = [
|
||||||
RuleViolation(self.id, u"int-öption: {0}".format(self.options[u'int-öption'].value), line_nr=1),
|
RuleViolation(self.id, f"int-öption: {self.options[u'int-öption'].value}", line_nr=1),
|
||||||
RuleViolation(self.id, u"str-öption: {0}".format(self.options[u'str-öption'].value), line_nr=1),
|
RuleViolation(self.id, f"str-öption: {self.options[u'str-öption'].value}", line_nr=1),
|
||||||
RuleViolation(self.id, u"list-öption: {0}".format(sstr(self.options[u'list-öption'].value)), line_nr=1),
|
RuleViolation(self.id, f"list-öption: {self.options[u'list-öption'].value}", line_nr=1),
|
||||||
]
|
]
|
||||||
|
|
||||||
return violations
|
return violations
|
||||||
|
|
20
qa/shell.py
20
qa/shell.py
|
@ -3,10 +3,11 @@
|
||||||
# on gitlint internals for our integration testing framework.
|
# on gitlint internals for our integration testing framework.
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from qa.utils import ustr, USE_SH_LIB, IS_PY2
|
from qa.utils import USE_SH_LIB, DEFAULT_ENCODING
|
||||||
|
|
||||||
if USE_SH_LIB:
|
if USE_SH_LIB:
|
||||||
from sh import git, echo, gitlint # pylint: disable=unused-import,no-name-in-module,import-error
|
from sh import git, echo, gitlint # pylint: disable=unused-import,no-name-in-module,import-error
|
||||||
|
gitlint = gitlint.bake(_unify_ttys=True, _tty_in=True) # pylint: disable=invalid-name
|
||||||
|
|
||||||
# import exceptions separately, this makes it a little easier to mock them out in the unit tests
|
# import exceptions separately, this makes it a little easier to mock them out in the unit tests
|
||||||
from sh import CommandNotFound, ErrorReturnCode, RunningCommand # pylint: disable=import-error
|
from sh import CommandNotFound, ErrorReturnCode, RunningCommand # pylint: disable=import-error
|
||||||
|
@ -16,7 +17,7 @@ else:
|
||||||
""" Exception indicating a command was not found during execution """
|
""" Exception indicating a command was not found during execution """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class RunningCommand(object):
|
class RunningCommand:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class ShResult(RunningCommand):
|
class ShResult(RunningCommand):
|
||||||
|
@ -27,7 +28,7 @@ else:
|
||||||
self.full_cmd = full_cmd
|
self.full_cmd = full_cmd
|
||||||
# TODO(jorisroovers): The 'sh' library by default will merge stdout and stderr. We mimic this behavior
|
# TODO(jorisroovers): The 'sh' library by default will merge stdout and stderr. We mimic this behavior
|
||||||
# for now until we fully remove the 'sh' library.
|
# for now until we fully remove the 'sh' library.
|
||||||
self.stdout = stdout + ustr(stderr)
|
self.stdout = stdout + stderr.decode(DEFAULT_ENCODING)
|
||||||
self.stderr = stderr
|
self.stderr = stderr
|
||||||
self.exit_code = exitcode
|
self.exit_code = exitcode
|
||||||
|
|
||||||
|
@ -55,14 +56,9 @@ else:
|
||||||
# a non-zero exit code -> just return the entire result
|
# a non-zero exit code -> just return the entire result
|
||||||
if hasattr(result, 'exit_code') and result.exit_code > 0:
|
if hasattr(result, 'exit_code') and result.exit_code > 0:
|
||||||
return result
|
return result
|
||||||
return ustr(result)
|
return str(result)
|
||||||
|
|
||||||
def _exec(*args, **kwargs):
|
def _exec(*args, **kwargs):
|
||||||
if IS_PY2:
|
|
||||||
no_command_error = OSError # noqa pylint: disable=undefined-variable,invalid-name
|
|
||||||
else:
|
|
||||||
no_command_error = FileNotFoundError # noqa pylint: disable=undefined-variable
|
|
||||||
|
|
||||||
pipe = subprocess.PIPE
|
pipe = subprocess.PIPE
|
||||||
popen_kwargs = {'stdout': pipe, 'stderr': pipe, 'shell': kwargs.get('_tty_out', False)}
|
popen_kwargs = {'stdout': pipe, 'stderr': pipe, 'shell': kwargs.get('_tty_out', False)}
|
||||||
if '_cwd' in kwargs:
|
if '_cwd' in kwargs:
|
||||||
|
@ -73,11 +69,11 @@ else:
|
||||||
try:
|
try:
|
||||||
p = subprocess.Popen(args, **popen_kwargs)
|
p = subprocess.Popen(args, **popen_kwargs)
|
||||||
result = p.communicate()
|
result = p.communicate()
|
||||||
except no_command_error:
|
except FileNotFoundError as exc:
|
||||||
raise CommandNotFound
|
raise CommandNotFound from exc
|
||||||
|
|
||||||
exit_code = p.returncode
|
exit_code = p.returncode
|
||||||
stdout = ustr(result[0])
|
stdout = result[0].decode(DEFAULT_ENCODING)
|
||||||
stderr = result[1] # 'sh' does not decode the stderr bytes to unicode
|
stderr = result[1] # 'sh' does not decode the stderr bytes to unicode
|
||||||
full_cmd = '' if args is None else ' '.join(args)
|
full_cmd = '' if args is None else ' '.join(args)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import arrow
|
||||||
|
|
||||||
from qa.shell import echo, git, gitlint
|
from qa.shell import echo, git, gitlint
|
||||||
from qa.base import BaseTestCase
|
from qa.base import BaseTestCase
|
||||||
from qa.utils import sstr
|
|
||||||
|
|
||||||
|
|
||||||
class CommitsTests(BaseTestCase):
|
class CommitsTests(BaseTestCase):
|
||||||
|
@ -16,10 +15,10 @@ class CommitsTests(BaseTestCase):
|
||||||
def test_successful(self):
|
def test_successful(self):
|
||||||
""" Test linting multiple commits without violations """
|
""" Test linting multiple commits without violations """
|
||||||
git("checkout", "-b", "test-branch-commits-base", _cwd=self.tmp_git_repo)
|
git("checkout", "-b", "test-branch-commits-base", _cwd=self.tmp_git_repo)
|
||||||
self.create_simple_commit(u"Sïmple title\n\nSimple bödy describing the commit")
|
self.create_simple_commit("Sïmple title\n\nSimple bödy describing the commit")
|
||||||
git("checkout", "-b", "test-branch-commits", _cwd=self.tmp_git_repo)
|
git("checkout", "-b", "test-branch-commits", _cwd=self.tmp_git_repo)
|
||||||
self.create_simple_commit(u"Sïmple title2\n\nSimple bödy describing the commit2")
|
self.create_simple_commit("Sïmple title2\n\nSimple bödy describing the commit2")
|
||||||
self.create_simple_commit(u"Sïmple title3\n\nSimple bödy describing the commit3")
|
self.create_simple_commit("Sïmple title3\n\nSimple bödy describing the commit3")
|
||||||
output = gitlint("--commits", "test-branch-commits-base...test-branch-commits",
|
output = gitlint("--commits", "test-branch-commits-base...test-branch-commits",
|
||||||
_cwd=self.tmp_git_repo, _tty_in=True)
|
_cwd=self.tmp_git_repo, _tty_in=True)
|
||||||
self.assertEqualStdout(output, "")
|
self.assertEqualStdout(output, "")
|
||||||
|
@ -27,12 +26,12 @@ class CommitsTests(BaseTestCase):
|
||||||
def test_violations(self):
|
def test_violations(self):
|
||||||
""" Test linting multiple commits with violations """
|
""" Test linting multiple commits with violations """
|
||||||
git("checkout", "-b", "test-branch-commits-violations-base", _cwd=self.tmp_git_repo)
|
git("checkout", "-b", "test-branch-commits-violations-base", _cwd=self.tmp_git_repo)
|
||||||
self.create_simple_commit(u"Sïmple title.\n")
|
self.create_simple_commit("Sïmple title.\n")
|
||||||
git("checkout", "-b", "test-branch-commits-violations", _cwd=self.tmp_git_repo)
|
git("checkout", "-b", "test-branch-commits-violations", _cwd=self.tmp_git_repo)
|
||||||
|
|
||||||
self.create_simple_commit(u"Sïmple title2.\n")
|
self.create_simple_commit("Sïmple title2.\n")
|
||||||
commit_sha1 = self.get_last_commit_hash()[:10]
|
commit_sha1 = self.get_last_commit_hash()[:10]
|
||||||
self.create_simple_commit(u"Sïmple title3.\n")
|
self.create_simple_commit("Sïmple title3.\n")
|
||||||
commit_sha2 = self.get_last_commit_hash()[:10]
|
commit_sha2 = self.get_last_commit_hash()[:10]
|
||||||
output = gitlint("--commits", "test-branch-commits-violations-base...test-branch-commits-violations",
|
output = gitlint("--commits", "test-branch-commits-violations-base...test-branch-commits-violations",
|
||||||
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4])
|
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4])
|
||||||
|
@ -43,14 +42,14 @@ class CommitsTests(BaseTestCase):
|
||||||
|
|
||||||
def test_lint_single_commit(self):
|
def test_lint_single_commit(self):
|
||||||
""" Tests `gitlint --commits <sha>` """
|
""" Tests `gitlint --commits <sha>` """
|
||||||
self.create_simple_commit(u"Sïmple title.\n")
|
self.create_simple_commit("Sïmple title.\n")
|
||||||
self.create_simple_commit(u"Sïmple title2.\n")
|
self.create_simple_commit("Sïmple title2.\n")
|
||||||
commit_sha = self.get_last_commit_hash()
|
commit_sha = self.get_last_commit_hash()
|
||||||
refspec = "{0}^...{0}".format(commit_sha)
|
refspec = f"{commit_sha}^...{commit_sha}"
|
||||||
self.create_simple_commit(u"Sïmple title3.\n")
|
self.create_simple_commit("Sïmple title3.\n")
|
||||||
output = gitlint("--commits", refspec, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
|
output = gitlint("--commits", refspec, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
|
||||||
expected = (u"1: T3 Title has trailing punctuation (.): \"Sïmple title2.\"\n" +
|
expected = ("1: T3 Title has trailing punctuation (.): \"Sïmple title2.\"\n" +
|
||||||
u"3: B6 Body message is missing\n")
|
"3: B6 Body message is missing\n")
|
||||||
self.assertEqual(output.exit_code, 2)
|
self.assertEqual(output.exit_code, 2)
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
||||||
|
@ -61,7 +60,7 @@ class CommitsTests(BaseTestCase):
|
||||||
echo "WIP: Pïpe test." | gitlint --staged --debug
|
echo "WIP: Pïpe test." | gitlint --staged --debug
|
||||||
"""
|
"""
|
||||||
# Create a commit first, before we stage changes. This ensures the repo is properly initialized.
|
# Create a commit first, before we stage changes. This ensures the repo is properly initialized.
|
||||||
self.create_simple_commit(u"Sïmple title.\n")
|
self.create_simple_commit("Sïmple title.\n")
|
||||||
|
|
||||||
# Add some files, stage them: they should show up in the debug output as changed file
|
# Add some files, stage them: they should show up in the debug output as changed file
|
||||||
filename1 = self.create_file(self.tmp_git_repo)
|
filename1 = self.create_file(self.tmp_git_repo)
|
||||||
|
@ -69,12 +68,12 @@ class CommitsTests(BaseTestCase):
|
||||||
filename2 = self.create_file(self.tmp_git_repo)
|
filename2 = self.create_file(self.tmp_git_repo)
|
||||||
git("add", filename2, _cwd=self.tmp_git_repo)
|
git("add", filename2, _cwd=self.tmp_git_repo)
|
||||||
|
|
||||||
output = gitlint(echo(u"WIP: Pïpe test."), "--staged", "--debug",
|
output = gitlint(echo("WIP: Pïpe test."), "--staged", "--debug",
|
||||||
_cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
|
_cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
|
||||||
|
|
||||||
# Determine variable parts of expected output
|
# Determine variable parts of expected output
|
||||||
expected_kwargs = self.get_debug_vars_last_commit()
|
expected_kwargs = self.get_debug_vars_last_commit()
|
||||||
expected_kwargs.update({'changed_files': sstr(sorted([filename1, filename2]))})
|
expected_kwargs.update({'changed_files': sorted([filename1, filename2])})
|
||||||
|
|
||||||
# It's not really possible to determine the "Date: ..." line that is part of the debug output as this date
|
# It's not really possible to determine the "Date: ..." line that is part of the debug output as this date
|
||||||
# is not taken from git but instead generated by gitlint itself. As a workaround, we extract the date from the
|
# is not taken from git but instead generated by gitlint itself. As a workaround, we extract the date from the
|
||||||
|
@ -95,7 +94,7 @@ class CommitsTests(BaseTestCase):
|
||||||
gitlint --msg-filename /tmp/my-commit-msg --staged --debug
|
gitlint --msg-filename /tmp/my-commit-msg --staged --debug
|
||||||
"""
|
"""
|
||||||
# Create a commit first, before we stage changes. This ensures the repo is properly initialized.
|
# Create a commit first, before we stage changes. This ensures the repo is properly initialized.
|
||||||
self.create_simple_commit(u"Sïmple title.\n")
|
self.create_simple_commit("Sïmple title.\n")
|
||||||
|
|
||||||
# Add some files, stage them: they should show up in the debug output as changed file
|
# Add some files, stage them: they should show up in the debug output as changed file
|
||||||
filename1 = self.create_file(self.tmp_git_repo)
|
filename1 = self.create_file(self.tmp_git_repo)
|
||||||
|
@ -103,14 +102,14 @@ class CommitsTests(BaseTestCase):
|
||||||
filename2 = self.create_file(self.tmp_git_repo)
|
filename2 = self.create_file(self.tmp_git_repo)
|
||||||
git("add", filename2, _cwd=self.tmp_git_repo)
|
git("add", filename2, _cwd=self.tmp_git_repo)
|
||||||
|
|
||||||
tmp_commit_msg_file = self.create_tmpfile(u"WIP: from fïle test.")
|
tmp_commit_msg_file = self.create_tmpfile("WIP: from fïle test.")
|
||||||
|
|
||||||
output = gitlint("--msg-filename", tmp_commit_msg_file, "--staged", "--debug",
|
output = gitlint("--msg-filename", tmp_commit_msg_file, "--staged", "--debug",
|
||||||
_cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
|
_cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
|
||||||
|
|
||||||
# Determine variable parts of expected output
|
# Determine variable parts of expected output
|
||||||
expected_kwargs = self.get_debug_vars_last_commit()
|
expected_kwargs = self.get_debug_vars_last_commit()
|
||||||
expected_kwargs.update({'changed_files': sstr(sorted([filename1, filename2]))})
|
expected_kwargs.update({'changed_files': sorted([filename1, filename2])})
|
||||||
|
|
||||||
# It's not really possible to determine the "Date: ..." line that is part of the debug output as this date
|
# It's not really possible to determine the "Date: ..." line that is part of the debug output as this date
|
||||||
# is not taken from git but instead generated by gitlint itself. As a workaround, we extract the date from the
|
# is not taken from git but instead generated by gitlint itself. As a workaround, we extract the date from the
|
||||||
|
@ -128,9 +127,9 @@ class CommitsTests(BaseTestCase):
|
||||||
def test_lint_head(self):
|
def test_lint_head(self):
|
||||||
""" Testing whether we can also recognize special refs like 'HEAD' """
|
""" Testing whether we can also recognize special refs like 'HEAD' """
|
||||||
tmp_git_repo = self.create_tmp_git_repo()
|
tmp_git_repo = self.create_tmp_git_repo()
|
||||||
self.create_simple_commit(u"Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
|
self.create_simple_commit("Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
|
||||||
self.create_simple_commit(u"Sïmple title", git_repo=tmp_git_repo)
|
self.create_simple_commit("Sïmple title", git_repo=tmp_git_repo)
|
||||||
self.create_simple_commit(u"WIP: Sïmple title\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
|
self.create_simple_commit("WIP: Sïmple title\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
|
||||||
output = gitlint("--commits", "HEAD", _cwd=tmp_git_repo, _tty_in=True, _ok_code=[3])
|
output = gitlint("--commits", "HEAD", _cwd=tmp_git_repo, _tty_in=True, _ok_code=[3])
|
||||||
revlist = git("rev-list", "HEAD", _tty_in=True, _cwd=tmp_git_repo).split()
|
revlist = git("rev-list", "HEAD", _tty_in=True, _cwd=tmp_git_repo).split()
|
||||||
|
|
||||||
|
@ -143,14 +142,14 @@ class CommitsTests(BaseTestCase):
|
||||||
""" Tests multiple commits of which some rules get igonored because of ignore-* rules """
|
""" Tests multiple commits of which some rules get igonored because of ignore-* rules """
|
||||||
# Create repo and some commits
|
# Create repo and some commits
|
||||||
tmp_git_repo = self.create_tmp_git_repo()
|
tmp_git_repo = self.create_tmp_git_repo()
|
||||||
self.create_simple_commit(u"Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
|
self.create_simple_commit("Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
|
||||||
# Normally, this commit will give T3 (trailing-punctuation), T5 (WIP) and B5 (bod-too-short) violations
|
# Normally, this commit will give T3 (trailing-punctuation), T5 (WIP) and B5 (bod-too-short) violations
|
||||||
# But in this case only B5 because T3 and T5 are being ignored because of config
|
# But in this case only B5 because T3 and T5 are being ignored because of config
|
||||||
self.create_simple_commit(u"Release: WIP tïtle.\n\nShort", git_repo=tmp_git_repo)
|
self.create_simple_commit("Release: WIP tïtle.\n\nShort", git_repo=tmp_git_repo)
|
||||||
# In the following 2 commits, the T3 violations are as normal
|
# In the following 2 commits, the T3 violations are as normal
|
||||||
self.create_simple_commit(
|
self.create_simple_commit(
|
||||||
u"Sïmple WIP title3.\n\nThis is \ta relëase commit\nMore info", git_repo=tmp_git_repo)
|
"Sïmple WIP title3.\n\nThis is \ta relëase commit\nMore info", git_repo=tmp_git_repo)
|
||||||
self.create_simple_commit(u"Sïmple title4.\n\nSimple bödy describing the commit4", git_repo=tmp_git_repo)
|
self.create_simple_commit("Sïmple title4.\n\nSimple bödy describing the commit4", git_repo=tmp_git_repo)
|
||||||
revlist = git("rev-list", "HEAD", _tty_in=True, _cwd=tmp_git_repo).split()
|
revlist = git("rev-list", "HEAD", _tty_in=True, _cwd=tmp_git_repo).split()
|
||||||
|
|
||||||
config_path = self.get_sample_path("config/ignore-release-commits")
|
config_path = self.get_sample_path("config/ignore-release-commits")
|
||||||
|
|
|
@ -5,30 +5,30 @@ import re
|
||||||
|
|
||||||
from qa.shell import gitlint
|
from qa.shell import gitlint
|
||||||
from qa.base import BaseTestCase
|
from qa.base import BaseTestCase
|
||||||
from qa.utils import sstr, ustr
|
from qa.utils import DEFAULT_ENCODING
|
||||||
|
|
||||||
|
|
||||||
class ConfigTests(BaseTestCase):
|
class ConfigTests(BaseTestCase):
|
||||||
""" Integration tests for gitlint configuration and configuration precedence. """
|
""" Integration tests for gitlint configuration and configuration precedence. """
|
||||||
|
|
||||||
def test_ignore_by_id(self):
|
def test_ignore_by_id(self):
|
||||||
self.create_simple_commit(u"WIP: Thïs is a title.\nContënt on the second line")
|
self.create_simple_commit("WIP: Thïs is a title.\nContënt on the second line")
|
||||||
output = gitlint("--ignore", "T5,B4", _tty_in=True, _cwd=self.tmp_git_repo, _ok_code=[1])
|
output = gitlint("--ignore", "T5,B4", _tty_in=True, _cwd=self.tmp_git_repo, _ok_code=[1])
|
||||||
expected = u"1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n"
|
expected = "1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n"
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
||||||
def test_ignore_by_name(self):
|
def test_ignore_by_name(self):
|
||||||
self.create_simple_commit(u"WIP: Thïs is a title.\nContënt on the second line")
|
self.create_simple_commit("WIP: Thïs is a title.\nContënt on the second line")
|
||||||
output = gitlint("--ignore", "title-must-not-contain-word,body-first-line-empty",
|
output = gitlint("--ignore", "title-must-not-contain-word,body-first-line-empty",
|
||||||
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
|
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
|
||||||
expected = u"1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n"
|
expected = "1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n"
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
||||||
def test_verbosity(self):
|
def test_verbosity(self):
|
||||||
self.create_simple_commit(u"WIP: Thïs is a title.\nContënt on the second line")
|
self.create_simple_commit("WIP: Thïs is a title.\nContënt on the second line")
|
||||||
output = gitlint("-v", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
|
output = gitlint("-v", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
|
||||||
|
|
||||||
expected = u"1: T3\n1: T5\n2: B4\n"
|
expected = "1: T3\n1: T5\n2: B4\n"
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
||||||
output = gitlint("-vv", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
|
output = gitlint("-vv", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
|
||||||
|
@ -42,12 +42,12 @@ class ConfigTests(BaseTestCase):
|
||||||
self.assertEqualStdout(output, "")
|
self.assertEqualStdout(output, "")
|
||||||
|
|
||||||
def test_set_rule_option(self):
|
def test_set_rule_option(self):
|
||||||
self.create_simple_commit(u"This ïs a title.")
|
self.create_simple_commit("This ïs a title.")
|
||||||
output = gitlint("-c", "title-max-length.line-length=5", _tty_in=True, _cwd=self.tmp_git_repo, _ok_code=[3])
|
output = gitlint("-c", "title-max-length.line-length=5", _tty_in=True, _cwd=self.tmp_git_repo, _ok_code=[3])
|
||||||
self.assertEqualStdout(output, self.get_expected("test_config/test_set_rule_option_1"))
|
self.assertEqualStdout(output, self.get_expected("test_config/test_set_rule_option_1"))
|
||||||
|
|
||||||
def test_config_from_file(self):
|
def test_config_from_file(self):
|
||||||
commit_msg = u"WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n" + \
|
commit_msg = "WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n" + \
|
||||||
"This line of the body is here because we need it"
|
"This line of the body is here because we need it"
|
||||||
self.create_simple_commit(commit_msg)
|
self.create_simple_commit(commit_msg)
|
||||||
config_path = self.get_sample_path("config/gitlintconfig")
|
config_path = self.get_sample_path("config/gitlintconfig")
|
||||||
|
@ -58,14 +58,14 @@ class ConfigTests(BaseTestCase):
|
||||||
# Test both on existing and new repo (we've had a bug in the past that was unique to empty repos)
|
# Test both on existing and new repo (we've had a bug in the past that was unique to empty repos)
|
||||||
repos = [self.tmp_git_repo, self.create_tmp_git_repo()]
|
repos = [self.tmp_git_repo, self.create_tmp_git_repo()]
|
||||||
for target_repo in repos:
|
for target_repo in repos:
|
||||||
commit_msg = u"WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n" + \
|
commit_msg = "WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n" + \
|
||||||
"This line of the body is here because we need it"
|
"This line of the body is here because we need it"
|
||||||
filename = self.create_simple_commit(commit_msg, git_repo=target_repo)
|
filename = self.create_simple_commit(commit_msg, git_repo=target_repo)
|
||||||
config_path = self.get_sample_path("config/gitlintconfig")
|
config_path = self.get_sample_path("config/gitlintconfig")
|
||||||
output = gitlint("--config", config_path, "--debug", _cwd=target_repo, _tty_in=True, _ok_code=[5])
|
output = gitlint("--config", config_path, "--debug", _cwd=target_repo, _tty_in=True, _ok_code=[5])
|
||||||
|
|
||||||
expected_kwargs = self.get_debug_vars_last_commit(git_repo=target_repo)
|
expected_kwargs = self.get_debug_vars_last_commit(git_repo=target_repo)
|
||||||
expected_kwargs.update({'config_path': config_path, 'changed_files': sstr([filename])})
|
expected_kwargs.update({'config_path': config_path, 'changed_files': [filename]})
|
||||||
self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_file_debug_1",
|
self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_file_debug_1",
|
||||||
expected_kwargs))
|
expected_kwargs))
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class ConfigTests(BaseTestCase):
|
||||||
# We invoke gitlint, configuring it via env variables, we can check whether gitlint picks these up correctly
|
# We invoke gitlint, configuring it via env variables, we can check whether gitlint picks these up correctly
|
||||||
# by comparing the debug output with what we'd expect
|
# by comparing the debug output with what we'd expect
|
||||||
target_repo = self.create_tmp_git_repo()
|
target_repo = self.create_tmp_git_repo()
|
||||||
commit_msg = u"WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n" + \
|
commit_msg = "WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n" + \
|
||||||
"This line of the body is here because we need it"
|
"This line of the body is here because we need it"
|
||||||
filename = self.create_simple_commit(commit_msg, git_repo=target_repo)
|
filename = self.create_simple_commit(commit_msg, git_repo=target_repo)
|
||||||
env = self.create_environment({"GITLINT_DEBUG": "1", "GITLINT_VERBOSITY": "2",
|
env = self.create_environment({"GITLINT_DEBUG": "1", "GITLINT_VERBOSITY": "2",
|
||||||
|
@ -84,12 +84,12 @@ class ConfigTests(BaseTestCase):
|
||||||
"GITLINT_COMMITS": self.get_last_commit_hash(git_repo=target_repo)})
|
"GITLINT_COMMITS": self.get_last_commit_hash(git_repo=target_repo)})
|
||||||
output = gitlint(_env=env, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
|
output = gitlint(_env=env, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
|
||||||
expected_kwargs = self.get_debug_vars_last_commit(git_repo=target_repo)
|
expected_kwargs = self.get_debug_vars_last_commit(git_repo=target_repo)
|
||||||
expected_kwargs.update({'changed_files': sstr([filename])})
|
expected_kwargs.update({'changed_files': [filename]})
|
||||||
|
|
||||||
self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_env_1", expected_kwargs))
|
self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_env_1", expected_kwargs))
|
||||||
|
|
||||||
# For some env variables, we need a separate test ast they are mutually exclusive with the ones tested above
|
# For some env variables, we need a separate test ast they are mutually exclusive with the ones tested above
|
||||||
tmp_commit_msg_file = self.create_tmpfile(u"WIP: msg-fïlename test.")
|
tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename test.")
|
||||||
env = self.create_environment({"GITLINT_DEBUG": "1", "GITLINT_TARGET": target_repo,
|
env = self.create_environment({"GITLINT_DEBUG": "1", "GITLINT_TARGET": target_repo,
|
||||||
"GITLINT_SILENT": "1", "GITLINT_STAGED": "1"})
|
"GITLINT_SILENT": "1", "GITLINT_STAGED": "1"})
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ class ConfigTests(BaseTestCase):
|
||||||
# Extract date from actual output to insert it into the expected output
|
# Extract date from actual output to insert it into the expected output
|
||||||
# We have to do this since there's no way for us to deterministically know that date otherwise
|
# We have to do this since there's no way for us to deterministically know that date otherwise
|
||||||
p = re.compile("Date: (.*)\n", re.UNICODE | re.MULTILINE)
|
p = re.compile("Date: (.*)\n", re.UNICODE | re.MULTILINE)
|
||||||
result = p.search(ustr(output.stdout))
|
result = p.search(output.stdout.decode(DEFAULT_ENCODING))
|
||||||
date = result.group(1).strip()
|
date = result.group(1).strip()
|
||||||
expected_kwargs.update({"date": date})
|
expected_kwargs.update({"date": date})
|
||||||
|
|
||||||
|
|
|
@ -8,19 +8,19 @@ class ContribRuleTests(BaseTestCase):
|
||||||
""" Integration tests for contrib rules."""
|
""" Integration tests for contrib rules."""
|
||||||
|
|
||||||
def test_contrib_rules(self):
|
def test_contrib_rules(self):
|
||||||
self.create_simple_commit(u"WIP Thi$ is å title\n\nMy bödy that is a bit longer than 20 chars")
|
self.create_simple_commit("WIP Thi$ is å title\n\nMy bödy that is a bit longer than 20 chars")
|
||||||
output = gitlint("--contrib", "contrib-title-conventional-commits,CC1",
|
output = gitlint("--contrib", "contrib-title-conventional-commits,CC1",
|
||||||
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4])
|
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4])
|
||||||
self.assertEqualStdout(output, self.get_expected("test_contrib/test_contrib_rules_1"))
|
self.assertEqualStdout(output, self.get_expected("test_contrib/test_contrib_rules_1"))
|
||||||
|
|
||||||
def test_contrib_rules_with_config(self):
|
def test_contrib_rules_with_config(self):
|
||||||
self.create_simple_commit(u"WIP Thi$ is å title\n\nMy bödy that is a bit longer than 20 chars")
|
self.create_simple_commit("WIP Thi$ is å title\n\nMy bödy that is a bit longer than 20 chars")
|
||||||
output = gitlint("--contrib", "contrib-title-conventional-commits,CC1",
|
output = gitlint("--contrib", "contrib-title-conventional-commits,CC1",
|
||||||
"-c", u"contrib-title-conventional-commits.types=föo,bår",
|
"-c", "contrib-title-conventional-commits.types=föo,bår",
|
||||||
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4])
|
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4])
|
||||||
self.assertEqualStdout(output, self.get_expected("test_contrib/test_contrib_rules_with_config_1"))
|
self.assertEqualStdout(output, self.get_expected("test_contrib/test_contrib_rules_with_config_1"))
|
||||||
|
|
||||||
def test_invalid_contrib_rules(self):
|
def test_invalid_contrib_rules(self):
|
||||||
self.create_simple_commit("WIP: test")
|
self.create_simple_commit("WIP: test")
|
||||||
output = gitlint("--contrib", u"föobar,CC1", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[255])
|
output = gitlint("--contrib", "föobar,CC1", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[255])
|
||||||
self.assertEqualStdout(output, u"Config Error: No contrib rule with id or name 'föobar' found.\n")
|
self.assertEqualStdout(output, "Config Error: No contrib rule with id or name 'föobar' found.\n")
|
||||||
|
|
|
@ -12,7 +12,7 @@ class IntegrationTests(BaseTestCase):
|
||||||
|
|
||||||
def test_successful(self):
|
def test_successful(self):
|
||||||
# Test for STDIN with and without a TTY attached
|
# Test for STDIN with and without a TTY attached
|
||||||
self.create_simple_commit(u"Sïmple title\n\nSimple bödy describing the commit")
|
self.create_simple_commit("Sïmple title\n\nSimple bödy describing the commit")
|
||||||
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _err_to_out=True)
|
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _err_to_out=True)
|
||||||
self.assertEqualStdout(output, "")
|
self.assertEqualStdout(output, "")
|
||||||
|
|
||||||
|
@ -22,26 +22,26 @@ class IntegrationTests(BaseTestCase):
|
||||||
|
|
||||||
# Different commentchar (Note: tried setting this to a special unicode char, but git doesn't like that)
|
# Different commentchar (Note: tried setting this to a special unicode char, but git doesn't like that)
|
||||||
git("config", "--add", "core.commentchar", "$", _cwd=self.tmp_git_repo)
|
git("config", "--add", "core.commentchar", "$", _cwd=self.tmp_git_repo)
|
||||||
self.create_simple_commit(u"Sïmple title\n\nSimple bödy describing the commit\n$after commentchar\t ignored")
|
self.create_simple_commit("Sïmple title\n\nSimple bödy describing the commit\n$after commentchar\t ignored")
|
||||||
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _err_to_out=True)
|
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _err_to_out=True)
|
||||||
self.assertEqualStdout(output, "")
|
self.assertEqualStdout(output, "")
|
||||||
|
|
||||||
def test_successful_merge_commit(self):
|
def test_successful_merge_commit(self):
|
||||||
# Create branch on master
|
# Create branch on master
|
||||||
self.create_simple_commit(u"Cömmit on master\n\nSimple bödy")
|
self.create_simple_commit("Cömmit on master\n\nSimple bödy")
|
||||||
|
|
||||||
# Create test branch, add a commit and determine the commit hash
|
# Create test branch, add a commit and determine the commit hash
|
||||||
git("checkout", "-b", "test-branch", _cwd=self.tmp_git_repo)
|
git("checkout", "-b", "test-branch", _cwd=self.tmp_git_repo)
|
||||||
git("checkout", "test-branch", _cwd=self.tmp_git_repo)
|
git("checkout", "test-branch", _cwd=self.tmp_git_repo)
|
||||||
commit_title = u"Commit on test-brånch with a pretty long title that will cause issues when merging"
|
commit_title = "Commit on test-brånch with a pretty long title that will cause issues when merging"
|
||||||
self.create_simple_commit(u"{0}\n\nSïmple body".format(commit_title))
|
self.create_simple_commit(f"{commit_title}\n\nSïmple body")
|
||||||
hash = self.get_last_commit_hash()
|
hash = self.get_last_commit_hash()
|
||||||
|
|
||||||
# Checkout master and merge the commit
|
# Checkout master and merge the commit
|
||||||
# We explicitly set the title of the merge commit to the title of the previous commit as this or similar
|
# We explicitly set the title of the merge commit to the title of the previous commit as this or similar
|
||||||
# behavior is what many tools do that handle merges (like github, gerrit, etc).
|
# behavior is what many tools do that handle merges (like github, gerrit, etc).
|
||||||
git("checkout", "master", _cwd=self.tmp_git_repo)
|
git("checkout", "master", _cwd=self.tmp_git_repo)
|
||||||
git("merge", "--no-ff", "-m", u"Merge '{0}'".format(commit_title), hash, _cwd=self.tmp_git_repo)
|
git("merge", "--no-ff", "-m", f"Merge '{commit_title}'", hash, _cwd=self.tmp_git_repo)
|
||||||
|
|
||||||
# Run gitlint and assert output is empty
|
# Run gitlint and assert output is empty
|
||||||
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True)
|
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True)
|
||||||
|
@ -50,14 +50,13 @@ class IntegrationTests(BaseTestCase):
|
||||||
# Assert that we do see the error if we disable the ignore-merge-commits option
|
# Assert that we do see the error if we disable the ignore-merge-commits option
|
||||||
output = gitlint("-c", "general.ignore-merge-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
|
output = gitlint("-c", "general.ignore-merge-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
|
||||||
self.assertEqual(output.exit_code, 1)
|
self.assertEqual(output.exit_code, 1)
|
||||||
self.assertEqualStdout(output,
|
self.assertEqualStdout(output, f"1: T1 Title exceeds max length (90>72): \"Merge '{commit_title}'\"\n")
|
||||||
u"1: T1 Title exceeds max length (90>72): \"Merge '{0}'\"\n".format(commit_title))
|
|
||||||
|
|
||||||
def test_fixup_commit(self):
|
def test_fixup_commit(self):
|
||||||
# Create a normal commit and assert that it has a violation
|
# Create a normal commit and assert that it has a violation
|
||||||
test_filename = self.create_simple_commit(u"Cömmit on WIP master\n\nSimple bödy that is long enough")
|
test_filename = self.create_simple_commit("Cömmit on WIP master\n\nSimple bödy that is long enough")
|
||||||
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
|
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
|
||||||
expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP master\"\n"
|
expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP master\"\n"
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
||||||
# Make a small modification to the commit and commit it using fixup commit
|
# Make a small modification to the commit and commit it using fixup commit
|
||||||
|
@ -66,7 +65,7 @@ class IntegrationTests(BaseTestCase):
|
||||||
# https://stackoverflow.com/questions/22392377/
|
# https://stackoverflow.com/questions/22392377/
|
||||||
# error-writing-a-file-with-file-write-in-python-unicodeencodeerror
|
# error-writing-a-file-with-file-write-in-python-unicodeencodeerror
|
||||||
# So just keeping it simple - ASCII will here
|
# So just keeping it simple - ASCII will here
|
||||||
fh.write(u"Appending some stuff\n")
|
fh.write("Appending some stuff\n")
|
||||||
|
|
||||||
git("add", test_filename, _cwd=self.tmp_git_repo)
|
git("add", test_filename, _cwd=self.tmp_git_repo)
|
||||||
|
|
||||||
|
@ -79,13 +78,13 @@ class IntegrationTests(BaseTestCase):
|
||||||
|
|
||||||
# Make sure that if we set the ignore-fixup-commits option to false that we do still see the violations
|
# Make sure that if we set the ignore-fixup-commits option to false that we do still see the violations
|
||||||
output = gitlint("-c", "general.ignore-fixup-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
|
output = gitlint("-c", "general.ignore-fixup-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
|
||||||
expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"fixup! Cömmit on WIP master\"\n" + \
|
expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"fixup! Cömmit on WIP master\"\n" + \
|
||||||
u"3: B6 Body message is missing\n"
|
"3: B6 Body message is missing\n"
|
||||||
|
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
||||||
def test_revert_commit(self):
|
def test_revert_commit(self):
|
||||||
self.create_simple_commit(u"WIP: Cömmit on master.\n\nSimple bödy")
|
self.create_simple_commit("WIP: Cömmit on master.\n\nSimple bödy")
|
||||||
hash = self.get_last_commit_hash()
|
hash = self.get_last_commit_hash()
|
||||||
git("revert", hash, _cwd=self.tmp_git_repo)
|
git("revert", hash, _cwd=self.tmp_git_repo)
|
||||||
|
|
||||||
|
@ -97,14 +96,14 @@ class IntegrationTests(BaseTestCase):
|
||||||
output = gitlint("-c", "general.ignore-revert-commits=false",
|
output = gitlint("-c", "general.ignore-revert-commits=false",
|
||||||
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
|
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
|
||||||
self.assertEqual(output.exit_code, 1)
|
self.assertEqual(output.exit_code, 1)
|
||||||
expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"Revert \"WIP: Cömmit on master.\"\"\n"
|
expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"Revert \"WIP: Cömmit on master.\"\"\n"
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
||||||
def test_squash_commit(self):
|
def test_squash_commit(self):
|
||||||
# Create a normal commit and assert that it has a violation
|
# Create a normal commit and assert that it has a violation
|
||||||
test_filename = self.create_simple_commit(u"Cömmit on WIP master\n\nSimple bödy that is long enough")
|
test_filename = self.create_simple_commit("Cömmit on WIP master\n\nSimple bödy that is long enough")
|
||||||
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
|
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
|
||||||
expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP master\"\n"
|
expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP master\"\n"
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
||||||
# Make a small modification to the commit and commit it using squash commit
|
# Make a small modification to the commit and commit it using squash commit
|
||||||
|
@ -113,11 +112,11 @@ class IntegrationTests(BaseTestCase):
|
||||||
# https://stackoverflow.com/questions/22392377/
|
# https://stackoverflow.com/questions/22392377/
|
||||||
# error-writing-a-file-with-file-write-in-python-unicodeencodeerror
|
# error-writing-a-file-with-file-write-in-python-unicodeencodeerror
|
||||||
# So just keeping it simple - ASCII will here
|
# So just keeping it simple - ASCII will here
|
||||||
fh.write(u"Appending some stuff\n")
|
fh.write("Appending some stuff\n")
|
||||||
|
|
||||||
git("add", test_filename, _cwd=self.tmp_git_repo)
|
git("add", test_filename, _cwd=self.tmp_git_repo)
|
||||||
|
|
||||||
git("commit", "--squash", self.get_last_commit_hash(), "-m", u"Töo short body", _cwd=self.tmp_git_repo)
|
git("commit", "--squash", self.get_last_commit_hash(), "-m", "Töo short body", _cwd=self.tmp_git_repo)
|
||||||
|
|
||||||
# Assert that gitlint does not show an error for the fixup commit
|
# Assert that gitlint does not show an error for the fixup commit
|
||||||
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True)
|
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True)
|
||||||
|
@ -127,25 +126,25 @@ class IntegrationTests(BaseTestCase):
|
||||||
# Make sure that if we set the ignore-squash-commits option to false that we do still see the violations
|
# Make sure that if we set the ignore-squash-commits option to false that we do still see the violations
|
||||||
output = gitlint("-c", "general.ignore-squash-commits=false",
|
output = gitlint("-c", "general.ignore-squash-commits=false",
|
||||||
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
|
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
|
||||||
expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"squash! Cömmit on WIP master\"\n" + \
|
expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"squash! Cömmit on WIP master\"\n" + \
|
||||||
u"3: B5 Body message is too short (14<20): \"Töo short body\"\n"
|
"3: B5 Body message is too short (14<20): \"Töo short body\"\n"
|
||||||
|
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
||||||
def test_violations(self):
|
def test_violations(self):
|
||||||
commit_msg = u"WIP: This ïs a title.\nContent on the sëcond line"
|
commit_msg = "WIP: This ïs a title.\nContent on the sëcond line"
|
||||||
self.create_simple_commit(commit_msg)
|
self.create_simple_commit(commit_msg)
|
||||||
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
|
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
|
||||||
self.assertEqualStdout(output, self.get_expected("test_gitlint/test_violations_1"))
|
self.assertEqualStdout(output, self.get_expected("test_gitlint/test_violations_1"))
|
||||||
|
|
||||||
def test_msg_filename(self):
|
def test_msg_filename(self):
|
||||||
tmp_commit_msg_file = self.create_tmpfile(u"WIP: msg-fïlename test.")
|
tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename test.")
|
||||||
output = gitlint("--msg-filename", tmp_commit_msg_file, _tty_in=True, _ok_code=[3])
|
output = gitlint("--msg-filename", tmp_commit_msg_file, _tty_in=True, _ok_code=[3])
|
||||||
self.assertEqualStdout(output, self.get_expected("test_gitlint/test_msg_filename_1"))
|
self.assertEqualStdout(output, self.get_expected("test_gitlint/test_msg_filename_1"))
|
||||||
|
|
||||||
def test_msg_filename_no_tty(self):
|
def test_msg_filename_no_tty(self):
|
||||||
""" Make sure --msg-filename option also works with no TTY attached """
|
""" Make sure --msg-filename option also works with no TTY attached """
|
||||||
tmp_commit_msg_file = self.create_tmpfile(u"WIP: msg-fïlename NO TTY test.")
|
tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename NO TTY test.")
|
||||||
|
|
||||||
# We need to set _err_to_out explicitly for sh to merge stdout and stderr output in case there's
|
# We need to set _err_to_out explicitly for sh to merge stdout and stderr output in case there's
|
||||||
# no TTY attached to STDIN
|
# no TTY attached to STDIN
|
||||||
|
@ -159,24 +158,24 @@ class IntegrationTests(BaseTestCase):
|
||||||
|
|
||||||
def test_no_git_name_set(self):
|
def test_no_git_name_set(self):
|
||||||
""" Ensure we print out a helpful message if user.name is not set """
|
""" Ensure we print out a helpful message if user.name is not set """
|
||||||
tmp_commit_msg_file = self.create_tmpfile(u"WIP: msg-fïlename NO name test.")
|
tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename NO name test.")
|
||||||
# Name is checked before email so this isn't strictly
|
# Name is checked before email so this isn't strictly
|
||||||
# necessary but seems good for consistency.
|
# necessary but seems good for consistency.
|
||||||
env = self.create_tmp_git_config(u"[user]\n email = test-emåil@foo.com\n")
|
env = self.create_tmp_git_config("[user]\n email = test-emåil@foo.com\n")
|
||||||
output = gitlint("--staged", "--msg-filename", tmp_commit_msg_file,
|
output = gitlint("--staged", "--msg-filename", tmp_commit_msg_file,
|
||||||
_ok_code=[self.GIT_CONTEXT_ERROR_CODE],
|
_ok_code=[self.GIT_CONTEXT_ERROR_CODE],
|
||||||
_env=env)
|
_env=env)
|
||||||
expected = u"Missing git configuration: please set user.name\n"
|
expected = "Missing git configuration: please set user.name\n"
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
||||||
def test_no_git_email_set(self):
|
def test_no_git_email_set(self):
|
||||||
""" Ensure we print out a helpful message if user.email is not set """
|
""" Ensure we print out a helpful message if user.email is not set """
|
||||||
tmp_commit_msg_file = self.create_tmpfile(u"WIP: msg-fïlename NO email test.")
|
tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename NO email test.")
|
||||||
env = self.create_tmp_git_config(u"[user]\n name = test åuthor\n")
|
env = self.create_tmp_git_config("[user]\n name = test åuthor\n")
|
||||||
output = gitlint("--staged", "--msg-filename", tmp_commit_msg_file,
|
output = gitlint("--staged", "--msg-filename", tmp_commit_msg_file,
|
||||||
_ok_code=[self.GIT_CONTEXT_ERROR_CODE],
|
_ok_code=[self.GIT_CONTEXT_ERROR_CODE],
|
||||||
_env=env)
|
_env=env)
|
||||||
expected = u"Missing git configuration: please set user.email\n"
|
expected = "Missing git configuration: please set user.email\n"
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
||||||
def test_git_errors(self):
|
def test_git_errors(self):
|
||||||
|
@ -184,10 +183,10 @@ class IntegrationTests(BaseTestCase):
|
||||||
empty_git_repo = self.create_tmp_git_repo()
|
empty_git_repo = self.create_tmp_git_repo()
|
||||||
output = gitlint(_cwd=empty_git_repo, _tty_in=True, _ok_code=[self.GIT_CONTEXT_ERROR_CODE])
|
output = gitlint(_cwd=empty_git_repo, _tty_in=True, _ok_code=[self.GIT_CONTEXT_ERROR_CODE])
|
||||||
|
|
||||||
expected = u"Current branch has no commits. Gitlint requires at least one commit to function.\n"
|
expected = "Current branch has no commits. Gitlint requires at least one commit to function.\n"
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
||||||
# Repo has no commits: caused by `git rev-parse`
|
# Repo has no commits: caused by `git rev-parse`
|
||||||
output = gitlint(echo(u"WIP: Pïpe test."), "--staged", _cwd=empty_git_repo, _tty_in=False,
|
output = gitlint(echo("WIP: Pïpe test."), "--staged", _cwd=empty_git_repo, _tty_in=False,
|
||||||
_err_to_out=True, _ok_code=[self.GIT_CONTEXT_ERROR_CODE])
|
_err_to_out=True, _ok_code=[self.GIT_CONTEXT_ERROR_CODE])
|
||||||
self.assertEqualStdout(output, expected)
|
self.assertEqualStdout(output, expected)
|
||||||
|
|
|
@ -24,18 +24,18 @@ class HookTests(BaseTestCase):
|
||||||
# The '--staged' flag used in the commit-msg hook fetches additional information from the underlying
|
# The '--staged' flag used in the commit-msg hook fetches additional information from the underlying
|
||||||
# git repo which means there already needs to be a commit in the repo
|
# git repo which means there already needs to be a commit in the repo
|
||||||
# (as gitlint --staged doesn't work against empty repos)
|
# (as gitlint --staged doesn't work against empty repos)
|
||||||
self.create_simple_commit(u"Commït Title\n\nCommit Body explaining commit.")
|
self.create_simple_commit("Commït Title\n\nCommit Body explaining commit.")
|
||||||
|
|
||||||
# install git commit-msg hook and assert output
|
# install git commit-msg hook and assert output
|
||||||
output_installed = gitlint("install-hook", _cwd=self.tmp_git_repo)
|
output_installed = gitlint("install-hook", _cwd=self.tmp_git_repo)
|
||||||
expected_installed = u"Successfully installed gitlint commit-msg hook in %s/.git/hooks/commit-msg\n" % \
|
expected_installed = "Successfully installed gitlint commit-msg hook in %s/.git/hooks/commit-msg\n" % \
|
||||||
self.tmp_git_repo
|
self.tmp_git_repo
|
||||||
self.assertEqualStdout(output_installed, expected_installed)
|
self.assertEqualStdout(output_installed, expected_installed)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# uninstall git commit-msg hook and assert output
|
# uninstall git commit-msg hook and assert output
|
||||||
output_uninstalled = gitlint("uninstall-hook", _cwd=self.tmp_git_repo)
|
output_uninstalled = gitlint("uninstall-hook", _cwd=self.tmp_git_repo)
|
||||||
expected_uninstalled = u"Successfully uninstalled gitlint commit-msg hook from %s/.git/hooks/commit-msg\n" % \
|
expected_uninstalled = "Successfully uninstalled gitlint commit-msg hook from %s/.git/hooks/commit-msg\n" % \
|
||||||
self.tmp_git_repo
|
self.tmp_git_repo
|
||||||
self.assertEqualStdout(output_uninstalled, expected_uninstalled)
|
self.assertEqualStdout(output_uninstalled, expected_uninstalled)
|
||||||
|
|
||||||
|
@ -50,24 +50,24 @@ class HookTests(BaseTestCase):
|
||||||
# Answer 'yes' to question to keep violating commit-msg
|
# Answer 'yes' to question to keep violating commit-msg
|
||||||
if "Your commit message contains the above violations" in line:
|
if "Your commit message contains the above violations" in line:
|
||||||
response = self.responses[self.response_index]
|
response = self.responses[self.response_index]
|
||||||
stdin.put("{0}\n".format(response))
|
stdin.put(f"{response}\n")
|
||||||
self.response_index = (self.response_index + 1) % len(self.responses)
|
self.response_index = (self.response_index + 1) % len(self.responses)
|
||||||
|
|
||||||
def test_commit_hook_no_violations(self):
|
def test_commit_hook_no_violations(self):
|
||||||
test_filename = self.create_simple_commit(u"This ïs a title\n\nBody contënt that should work",
|
test_filename = self.create_simple_commit("This ïs a title\n\nBody contënt that should work",
|
||||||
out=self._interact, tty_in=True)
|
out=self._interact, tty_in=True)
|
||||||
|
|
||||||
short_hash = self.get_last_commit_short_hash()
|
short_hash = self.get_last_commit_short_hash()
|
||||||
expected_output = ["gitlint: checking commit message...\n",
|
expected_output = ["gitlint: checking commit message...\n",
|
||||||
"gitlint: \x1b[32mOK\x1b[0m (no violations in commit message)\n",
|
"gitlint: \x1b[32mOK\x1b[0m (no violations in commit message)\n",
|
||||||
u"[master %s] This ïs a title\n" % short_hash,
|
"[master %s] This ïs a title\n" % short_hash,
|
||||||
" 1 file changed, 0 insertions(+), 0 deletions(-)\n",
|
" 1 file changed, 0 insertions(+), 0 deletions(-)\n",
|
||||||
u" create mode 100644 %s\n" % test_filename]
|
" create mode 100644 %s\n" % test_filename]
|
||||||
self.assertListEqual(expected_output, self.githook_output)
|
self.assertListEqual(expected_output, self.githook_output)
|
||||||
|
|
||||||
def test_commit_hook_continue(self):
|
def test_commit_hook_continue(self):
|
||||||
self.responses = ["y"]
|
self.responses = ["y"]
|
||||||
test_filename = self.create_simple_commit(u"WIP: This ïs a title.\nContënt on the second line",
|
test_filename = self.create_simple_commit("WIP: This ïs a title.\nContënt on the second line",
|
||||||
out=self._interact, tty_in=True)
|
out=self._interact, tty_in=True)
|
||||||
|
|
||||||
# Determine short commit-msg hash, needed to determine expected output
|
# Determine short commit-msg hash, needed to determine expected output
|
||||||
|
@ -76,10 +76,10 @@ class HookTests(BaseTestCase):
|
||||||
expected_output = self._violations()
|
expected_output = self._violations()
|
||||||
expected_output += ["Continue with commit anyways (this keeps the current commit message)? " +
|
expected_output += ["Continue with commit anyways (this keeps the current commit message)? " +
|
||||||
"[y(es)/n(no)/e(dit)] " +
|
"[y(es)/n(no)/e(dit)] " +
|
||||||
u"[master %s] WIP: This ïs a title. Contënt on the second line\n"
|
"[master %s] WIP: This ïs a title. Contënt on the second line\n"
|
||||||
% short_hash,
|
% short_hash,
|
||||||
" 1 file changed, 0 insertions(+), 0 deletions(-)\n",
|
" 1 file changed, 0 insertions(+), 0 deletions(-)\n",
|
||||||
u" create mode 100644 %s\n" % test_filename]
|
" create mode 100644 %s\n" % test_filename]
|
||||||
|
|
||||||
assert len(self.githook_output) == len(expected_output)
|
assert len(self.githook_output) == len(expected_output)
|
||||||
for output, expected in zip(self.githook_output, expected_output):
|
for output, expected in zip(self.githook_output, expected_output):
|
||||||
|
@ -89,7 +89,7 @@ class HookTests(BaseTestCase):
|
||||||
|
|
||||||
def test_commit_hook_abort(self):
|
def test_commit_hook_abort(self):
|
||||||
self.responses = ["n"]
|
self.responses = ["n"]
|
||||||
test_filename = self.create_simple_commit(u"WIP: This ïs a title.\nContënt on the second line",
|
test_filename = self.create_simple_commit("WIP: This ïs a title.\nContënt on the second line",
|
||||||
out=self._interact, ok_code=1, tty_in=True)
|
out=self._interact, ok_code=1, tty_in=True)
|
||||||
git("rm", "-f", test_filename, _cwd=self.tmp_git_repo)
|
git("rm", "-f", test_filename, _cwd=self.tmp_git_repo)
|
||||||
|
|
||||||
|
@ -101,8 +101,8 @@ class HookTests(BaseTestCase):
|
||||||
"Commit aborted.\n",
|
"Commit aborted.\n",
|
||||||
"Your commit message: \n",
|
"Your commit message: \n",
|
||||||
"-----------------------------------------------\n",
|
"-----------------------------------------------\n",
|
||||||
u"WIP: This ïs a title.\n",
|
"WIP: This ïs a title.\n",
|
||||||
u"Contënt on the second line\n",
|
"Contënt on the second line\n",
|
||||||
"-----------------------------------------------\n"]
|
"-----------------------------------------------\n"]
|
||||||
|
|
||||||
self.assertListEqual(expected_output, self.githook_output)
|
self.assertListEqual(expected_output, self.githook_output)
|
||||||
|
@ -110,7 +110,7 @@ class HookTests(BaseTestCase):
|
||||||
def test_commit_hook_edit(self):
|
def test_commit_hook_edit(self):
|
||||||
self.responses = ["e", "y"]
|
self.responses = ["e", "y"]
|
||||||
env = {"EDITOR": ":"}
|
env = {"EDITOR": ":"}
|
||||||
test_filename = self.create_simple_commit(u"WIP: This ïs a title.\nContënt on the second line",
|
test_filename = self.create_simple_commit("WIP: This ïs a title.\nContënt on the second line",
|
||||||
out=self._interact, env=env, tty_in=True)
|
out=self._interact, env=env, tty_in=True)
|
||||||
git("rm", "-f", test_filename, _cwd=self.tmp_git_repo)
|
git("rm", "-f", test_filename, _cwd=self.tmp_git_repo)
|
||||||
|
|
||||||
|
@ -124,9 +124,9 @@ class HookTests(BaseTestCase):
|
||||||
expected_output += self._violations()[1:]
|
expected_output += self._violations()[1:]
|
||||||
expected_output += ['Continue with commit anyways (this keeps the current commit message)? ' +
|
expected_output += ['Continue with commit anyways (this keeps the current commit message)? ' +
|
||||||
"[y(es)/n(no)/e(dit)] " +
|
"[y(es)/n(no)/e(dit)] " +
|
||||||
u"[master %s] WIP: This ïs a title. Contënt on the second line\n" % short_hash,
|
"[master %s] WIP: This ïs a title. Contënt on the second line\n" % short_hash,
|
||||||
" 1 file changed, 0 insertions(+), 0 deletions(-)\n",
|
" 1 file changed, 0 insertions(+), 0 deletions(-)\n",
|
||||||
u" create mode 100644 %s\n" % test_filename]
|
" create mode 100644 %s\n" % test_filename]
|
||||||
|
|
||||||
assert len(self.githook_output) == len(expected_output)
|
assert len(self.githook_output) == len(expected_output)
|
||||||
for output, expected in zip(self.githook_output, expected_output):
|
for output, expected in zip(self.githook_output, expected_output):
|
||||||
|
@ -147,7 +147,7 @@ class HookTests(BaseTestCase):
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
tmp_git_repo = self.create_tmp_git_repo()
|
tmp_git_repo = self.create_tmp_git_repo()
|
||||||
self.create_simple_commit(u"Simple title\n\nContënt in the body", git_repo=tmp_git_repo)
|
self.create_simple_commit("Simple title\n\nContënt in the body", git_repo=tmp_git_repo)
|
||||||
|
|
||||||
worktree_dir = self.generate_temp_path()
|
worktree_dir = self.generate_temp_path()
|
||||||
self.tmp_git_repos.append(worktree_dir) # make sure we clean up the worktree afterwards
|
self.tmp_git_repos.append(worktree_dir) # make sure we clean up the worktree afterwards
|
||||||
|
@ -156,10 +156,10 @@ class HookTests(BaseTestCase):
|
||||||
|
|
||||||
output_installed = gitlint("install-hook", _cwd=worktree_dir)
|
output_installed = gitlint("install-hook", _cwd=worktree_dir)
|
||||||
expected_hook_path = os.path.join(tmp_git_repo, ".git", "hooks", "commit-msg")
|
expected_hook_path = os.path.join(tmp_git_repo, ".git", "hooks", "commit-msg")
|
||||||
expected_msg = "Successfully installed gitlint commit-msg hook in {0}\n".format(expected_hook_path)
|
expected_msg = f"Successfully installed gitlint commit-msg hook in {expected_hook_path}\r\n"
|
||||||
self.assertEqual(output_installed, expected_msg)
|
self.assertEqual(output_installed, expected_msg)
|
||||||
|
|
||||||
output_uninstalled = gitlint("uninstall-hook", _cwd=worktree_dir)
|
output_uninstalled = gitlint("uninstall-hook", _cwd=worktree_dir)
|
||||||
expected_hook_path = os.path.join(tmp_git_repo, ".git", "hooks", "commit-msg")
|
expected_hook_path = os.path.join(tmp_git_repo, ".git", "hooks", "commit-msg")
|
||||||
expected_msg = "Successfully uninstalled gitlint commit-msg hook from {0}\n".format(expected_hook_path)
|
expected_msg = f"Successfully uninstalled gitlint commit-msg hook from {expected_hook_path}\r\n"
|
||||||
self.assertEqual(output_uninstalled, expected_msg)
|
self.assertEqual(output_uninstalled, expected_msg)
|
||||||
|
|
|
@ -7,14 +7,14 @@ class NamedRuleTests(BaseTestCase):
|
||||||
""" Integration tests for named rules."""
|
""" Integration tests for named rules."""
|
||||||
|
|
||||||
def test_named_rule(self):
|
def test_named_rule(self):
|
||||||
commit_msg = u"WIP: thåt dûr bår\n\nSïmple commit body"
|
commit_msg = "WIP: thåt dûr bår\n\nSïmple commit body"
|
||||||
self.create_simple_commit(commit_msg)
|
self.create_simple_commit(commit_msg)
|
||||||
config_path = self.get_sample_path("config/named-rules")
|
config_path = self.get_sample_path("config/named-rules")
|
||||||
output = gitlint("--config", config_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
|
output = gitlint("--config", config_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
|
||||||
self.assertEqualStdout(output, self.get_expected("test_named_rules/test_named_rule_1"))
|
self.assertEqualStdout(output, self.get_expected("test_named_rules/test_named_rule_1"))
|
||||||
|
|
||||||
def test_named_user_rule(self):
|
def test_named_user_rule(self):
|
||||||
commit_msg = u"Normal cömmit title\n\nSïmple commit message body"
|
commit_msg = "Normal cömmit title\n\nSïmple commit message body"
|
||||||
self.create_simple_commit(commit_msg)
|
self.create_simple_commit(commit_msg)
|
||||||
config_path = self.get_sample_path("config/named-user-rules")
|
config_path = self.get_sample_path("config/named-user-rules")
|
||||||
extra_path = self.get_sample_path("user_rules/extra")
|
extra_path = self.get_sample_path("user_rules/extra")
|
||||||
|
|
|
@ -4,7 +4,7 @@ import io
|
||||||
import subprocess
|
import subprocess
|
||||||
from qa.shell import echo, gitlint
|
from qa.shell import echo, gitlint
|
||||||
from qa.base import BaseTestCase
|
from qa.base import BaseTestCase
|
||||||
from qa.utils import ustr, DEFAULT_ENCODING
|
from qa.utils import DEFAULT_ENCODING
|
||||||
|
|
||||||
|
|
||||||
class StdInTests(BaseTestCase):
|
class StdInTests(BaseTestCase):
|
||||||
|
@ -18,7 +18,7 @@ class StdInTests(BaseTestCase):
|
||||||
# NOTE: There is no use in testing this with _tty_in=True, because if you pipe something into a command
|
# NOTE: There is no use in testing this with _tty_in=True, because if you pipe something into a command
|
||||||
# there never is a TTY connected to stdin (per definition). We're setting _tty_in=False here to be explicit
|
# there never is a TTY connected to stdin (per definition). We're setting _tty_in=False here to be explicit
|
||||||
# but note that this is always true when piping something into a command.
|
# but note that this is always true when piping something into a command.
|
||||||
output = gitlint(echo(u"WIP: Pïpe test."),
|
output = gitlint(echo("WIP: Pïpe test."),
|
||||||
_cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
|
_cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
|
||||||
self.assertEqualStdout(output, self.get_expected("test_stdin/test_stdin_pipe_1"))
|
self.assertEqualStdout(output, self.get_expected("test_stdin/test_stdin_pipe_1"))
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ class StdInTests(BaseTestCase):
|
||||||
This is the equivalent of doing:
|
This is the equivalent of doing:
|
||||||
$ echo -n "" | gitlint
|
$ echo -n "" | gitlint
|
||||||
"""
|
"""
|
||||||
commit_msg = u"WIP: This ïs a title.\nContent on the sëcond line"
|
commit_msg = "WIP: This ïs a title.\nContent on the sëcond line"
|
||||||
self.create_simple_commit(commit_msg)
|
self.create_simple_commit(commit_msg)
|
||||||
|
|
||||||
# We need to set _err_to_out explicitly for sh to merge stdout and stderr output in case there's
|
# We need to set _err_to_out explicitly for sh to merge stdout and stderr output in case there's
|
||||||
|
@ -36,21 +36,21 @@ class StdInTests(BaseTestCase):
|
||||||
# http://amoffat.github.io/sh/sections/special_arguments.html?highlight=_tty_in#err-to-out
|
# http://amoffat.github.io/sh/sections/special_arguments.html?highlight=_tty_in#err-to-out
|
||||||
output = gitlint(echo("-n", ""), _cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
|
output = gitlint(echo("-n", ""), _cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
|
||||||
|
|
||||||
self.assertEqual(ustr(output), self.get_expected("test_stdin/test_stdin_pipe_empty_1"))
|
self.assertEqual(output, self.get_expected("test_stdin/test_stdin_pipe_empty_1"))
|
||||||
|
|
||||||
def test_stdin_file(self):
|
def test_stdin_file(self):
|
||||||
""" Test the scenario where STDIN is a regular file (stat.S_ISREG = True)
|
""" Test the scenario where STDIN is a regular file (stat.S_ISREG = True)
|
||||||
This is the equivalent of doing:
|
This is the equivalent of doing:
|
||||||
$ gitlint < myfile
|
$ gitlint < myfile
|
||||||
"""
|
"""
|
||||||
tmp_commit_msg_file = self.create_tmpfile(u"WIP: STDIN ïs a file test.")
|
tmp_commit_msg_file = self.create_tmpfile("WIP: STDIN ïs a file test.")
|
||||||
|
|
||||||
with io.open(tmp_commit_msg_file, encoding=DEFAULT_ENCODING) as file_handle:
|
with io.open(tmp_commit_msg_file, encoding=DEFAULT_ENCODING) as file_handle:
|
||||||
|
|
||||||
# We need to use subprocess.Popen() here instead of sh because when passing a file_handle to sh, it will
|
# We need to use subprocess.Popen() here instead of sh because when passing a file_handle to sh, it will
|
||||||
# deal with reading the file itself instead of passing it on to gitlint as a STDIN. Since we're trying to
|
# deal with reading the file itself instead of passing it on to gitlint as a STDIN. Since we're trying to
|
||||||
# test for the condition where stat.S_ISREG == True that won't work for us here.
|
# test for the condition where stat.S_ISREG == True that won't work for us here.
|
||||||
p = subprocess.Popen(u"gitlint", stdin=file_handle, cwd=self.tmp_git_repo,
|
p = subprocess.Popen("gitlint", stdin=file_handle, cwd=self.tmp_git_repo,
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
output, _ = p.communicate()
|
output, _ = p.communicate()
|
||||||
self.assertEqual(ustr(output), self.get_expected("test_stdin/test_stdin_file_1"))
|
self.assertEqual(output.decode(DEFAULT_ENCODING), self.get_expected("test_stdin/test_stdin_file_1"))
|
||||||
|
|
|
@ -10,7 +10,7 @@ class UserDefinedRuleTests(BaseTestCase):
|
||||||
def test_user_defined_rules_examples1(self):
|
def test_user_defined_rules_examples1(self):
|
||||||
""" Test the user defined rules in the top-level `examples/` directory """
|
""" Test the user defined rules in the top-level `examples/` directory """
|
||||||
extra_path = self.get_example_path()
|
extra_path = self.get_example_path()
|
||||||
commit_msg = u"WIP: Thi$ is å title\nContent on the second line"
|
commit_msg = "WIP: Thi$ is å title\nContent on the second line"
|
||||||
self.create_simple_commit(commit_msg)
|
self.create_simple_commit(commit_msg)
|
||||||
output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
|
output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
|
||||||
self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_examples_1"))
|
self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_examples_1"))
|
||||||
|
@ -18,7 +18,7 @@ class UserDefinedRuleTests(BaseTestCase):
|
||||||
def test_user_defined_rules_examples2(self):
|
def test_user_defined_rules_examples2(self):
|
||||||
""" Test the user defined rules in the top-level `examples/` directory """
|
""" Test the user defined rules in the top-level `examples/` directory """
|
||||||
extra_path = self.get_example_path()
|
extra_path = self.get_example_path()
|
||||||
commit_msg = u"Release: Thi$ is å title\nContent on the second line\n$This line is ignored \nThis isn't\t\n"
|
commit_msg = "Release: Thi$ is å title\nContent on the second line\n$This line is ignored \nThis isn't\t\n"
|
||||||
self.create_simple_commit(commit_msg)
|
self.create_simple_commit(commit_msg)
|
||||||
output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4])
|
output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4])
|
||||||
self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_examples_2"))
|
self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_examples_2"))
|
||||||
|
@ -26,7 +26,7 @@ class UserDefinedRuleTests(BaseTestCase):
|
||||||
def test_user_defined_rules_examples_with_config(self):
|
def test_user_defined_rules_examples_with_config(self):
|
||||||
""" Test the user defined rules in the top-level `examples/` directory """
|
""" Test the user defined rules in the top-level `examples/` directory """
|
||||||
extra_path = self.get_example_path()
|
extra_path = self.get_example_path()
|
||||||
commit_msg = u"WIP: Thi$ is å title\nContent on the second line"
|
commit_msg = "WIP: Thi$ is å title\nContent on the second line"
|
||||||
self.create_simple_commit(commit_msg)
|
self.create_simple_commit(commit_msg)
|
||||||
output = gitlint("--extra-path", extra_path, "-c", "body-max-line-count.max-line-count=1",
|
output = gitlint("--extra-path", extra_path, "-c", "body-max-line-count.max-line-count=1",
|
||||||
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[6])
|
_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[6])
|
||||||
|
@ -35,7 +35,7 @@ class UserDefinedRuleTests(BaseTestCase):
|
||||||
|
|
||||||
def test_user_defined_rules_extra(self):
|
def test_user_defined_rules_extra(self):
|
||||||
extra_path = self.get_sample_path("user_rules/extra")
|
extra_path = self.get_sample_path("user_rules/extra")
|
||||||
commit_msg = u"WIP: Thi$ is å title\nContent on the second line"
|
commit_msg = "WIP: Thi$ is å title\nContent on the second line"
|
||||||
self.create_simple_commit(commit_msg)
|
self.create_simple_commit(commit_msg)
|
||||||
output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[9])
|
output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[9])
|
||||||
self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_extra_1",
|
self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_extra_1",
|
||||||
|
|
49
qa/utils.py
49
qa/utils.py
|
@ -1,6 +1,5 @@
|
||||||
# pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable,no-else-return
|
# pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable,no-else-return
|
||||||
import platform
|
import platform
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import locale
|
import locale
|
||||||
|
@ -15,16 +14,6 @@ def platform_is_windows():
|
||||||
|
|
||||||
PLATFORM_IS_WINDOWS = platform_is_windows()
|
PLATFORM_IS_WINDOWS = platform_is_windows()
|
||||||
|
|
||||||
########################################################################################################################
|
|
||||||
# IS_PY2
|
|
||||||
|
|
||||||
|
|
||||||
def is_py2():
|
|
||||||
return sys.version_info[0] == 2
|
|
||||||
|
|
||||||
|
|
||||||
IS_PY2 = is_py2()
|
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
# USE_SH_LIB
|
# USE_SH_LIB
|
||||||
# Determine whether to use the `sh` library
|
# Determine whether to use the `sh` library
|
||||||
|
@ -71,41 +60,3 @@ def getpreferredencoding():
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_ENCODING = getpreferredencoding()
|
DEFAULT_ENCODING = getpreferredencoding()
|
||||||
|
|
||||||
########################################################################################################################
|
|
||||||
# Unicode utility functions
|
|
||||||
|
|
||||||
|
|
||||||
def ustr(obj):
|
|
||||||
""" Python 2 and 3 utility method that converts an obj to unicode in python 2 and to a str object in python 3"""
|
|
||||||
if IS_PY2:
|
|
||||||
# If we are getting a string, then do an explicit decode
|
|
||||||
# else, just call the unicode method of the object
|
|
||||||
if type(obj) in [str, basestring]: # pragma: no cover # noqa
|
|
||||||
return unicode(obj, DEFAULT_ENCODING) # pragma: no cover # noqa
|
|
||||||
else:
|
|
||||||
return unicode(obj) # pragma: no cover # noqa
|
|
||||||
else:
|
|
||||||
if type(obj) in [bytes]:
|
|
||||||
return obj.decode(DEFAULT_ENCODING)
|
|
||||||
else:
|
|
||||||
return str(obj)
|
|
||||||
|
|
||||||
|
|
||||||
def sstr(obj):
|
|
||||||
""" Python 2 and 3 utility method that converts an obj to a DEFAULT_ENCODING encoded string in python 2
|
|
||||||
and to unicode in python 3.
|
|
||||||
Especially useful for implementing __str__ methods in python 2: http://stackoverflow.com/a/1307210/381010"""
|
|
||||||
if IS_PY2:
|
|
||||||
# For lists and tuples in python2, remove unicode string representation characters.
|
|
||||||
# i.e. ensure lists are printed as ['a', 'b'] and not [u'a', u'b']
|
|
||||||
if type(obj) in [list]:
|
|
||||||
return [sstr(item) for item in obj] # pragma: no cover # noqa
|
|
||||||
elif type(obj) in [tuple]:
|
|
||||||
return tuple(sstr(item) for item in obj) # pragma: no cover # noqa
|
|
||||||
|
|
||||||
return unicode(obj).encode(DEFAULT_ENCODING) # pragma: no cover # noqa
|
|
||||||
else:
|
|
||||||
return obj # pragma: no cover
|
|
||||||
|
|
||||||
########################################################################################################################
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
setuptools
|
setuptools
|
||||||
wheel==0.33.4
|
wheel==0.35.1
|
||||||
Click==7.0
|
Click==7.1.2
|
||||||
sh==1.12.14; sys_platform != 'win32' # sh is not supported on windows
|
sh==1.14.1; sys_platform != 'win32' # sh is not supported on windows
|
||||||
arrow==0.15.5;
|
arrow==0.17.0
|
||||||
|
|
10
run_tests.sh
10
run_tests.sh
|
@ -13,7 +13,7 @@ help(){
|
||||||
echo " -b, --build Run build tests"
|
echo " -b, --build Run build tests"
|
||||||
echo " -a, --all Run all tests and checks (unit, integration, pep8, git)"
|
echo " -a, --all Run all tests and checks (unit, integration, pep8, git)"
|
||||||
echo " -e, --envs [ENV1],[ENV2] Run tests against specified python environments"
|
echo " -e, --envs [ENV1],[ENV2] Run tests against specified python environments"
|
||||||
echo " (envs: 27,35,36,37,pypy2,pypy35)."
|
echo " (envs: 36,37,38,39,pypy37)."
|
||||||
echo " Also works for integration, pep8 and lint tests."
|
echo " Also works for integration, pep8 and lint tests."
|
||||||
echo " -C, --container Run the specified command in the container for the --envs specified"
|
echo " -C, --container Run the specified command in the container for the --envs specified"
|
||||||
echo " --all-env Run all tests against all python environments"
|
echo " --all-env Run all tests against all python environments"
|
||||||
|
@ -261,9 +261,7 @@ install_virtualenv(){
|
||||||
# For pypy: custom path + fetch from the web if not installed (=distro agnostic)
|
# For pypy: custom path + fetch from the web if not installed (=distro agnostic)
|
||||||
if [[ $version == *"pypy"* ]]; then
|
if [[ $version == *"pypy"* ]]; then
|
||||||
pypy_download_mirror="https://downloads.python.org/pypy"
|
pypy_download_mirror="https://downloads.python.org/pypy"
|
||||||
if [[ $version == *"pypy2"* ]]; then
|
if [[ $version == *"pypy36"* ]]; then
|
||||||
pypy_full_version="pypy2.7-v7.3.2-linux64"
|
|
||||||
elif [[ $version == *"pypy36"* ]]; then
|
|
||||||
pypy_full_version="pypy3.6-v7.3.2-linux64"
|
pypy_full_version="pypy3.6-v7.3.2-linux64"
|
||||||
elif [[ $version == *"pypy37"* ]]; then
|
elif [[ $version == *"pypy37"* ]]; then
|
||||||
pypy_full_version="pypy3.7-v7.3.2-linux64"
|
pypy_full_version="pypy3.7-v7.3.2-linux64"
|
||||||
|
@ -365,7 +363,7 @@ uninstall_container(){
|
||||||
|
|
||||||
assert_specific_env(){
|
assert_specific_env(){
|
||||||
if [ -z "$1" ] || [ "$1" == "default" ]; then
|
if [ -z "$1" ] || [ "$1" == "default" ]; then
|
||||||
fatal "ERROR: Please specify one or more valid python environments using --envs: 27,35,36,37,pypy2,pypy35"
|
fatal "ERROR: Please specify one or more valid python environments using --envs: 36,37,38,39,pypy37"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
@ -461,7 +459,7 @@ exit_code=0
|
||||||
|
|
||||||
# If the users specified 'all', then just replace $envs with the list of all envs
|
# If the users specified 'all', then just replace $envs with the list of all envs
|
||||||
if [ "$envs" == "all" ]; then
|
if [ "$envs" == "all" ]; then
|
||||||
envs="27,35,36,37,38,39,pypy2,pypy35"
|
envs="36,37,38,39,pypy37"
|
||||||
fi
|
fi
|
||||||
original_envs="$envs"
|
original_envs="$envs"
|
||||||
envs=$(echo "$envs" | tr ',' '\n') # Split the env list on comma so we can loop through it
|
envs=$(echo "$envs" | tr ',' '\n') # Split the env list on comma so we can loop through it
|
||||||
|
|
25
setup.py
25
setup.py
|
@ -7,13 +7,6 @@ import os
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# There is an issue with building python packages in a shared vagrant directory because of how setuptools works
|
|
||||||
# in python < 2.7.9. We solve this by deleting the filesystem hardlinking capability during build.
|
|
||||||
# See: http://stackoverflow.com/a/22147112/381010
|
|
||||||
try:
|
|
||||||
del os.link
|
|
||||||
except:
|
|
||||||
pass # Not all OSes (e.g. windows) support os.link
|
|
||||||
|
|
||||||
description = "Git commit message linter written in python, checks your commit messages for style."
|
description = "Git commit message linter written in python, checks your commit messages for style."
|
||||||
long_description = """
|
long_description = """
|
||||||
|
@ -52,8 +45,6 @@ setup(
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 2.7",
|
|
||||||
"Programming Language :: Python :: 3.5",
|
|
||||||
"Programming Language :: Python :: 3.6",
|
"Programming Language :: Python :: 3.6",
|
||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.7",
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
|
@ -66,18 +57,23 @@ setup(
|
||||||
"Topic :: Software Development :: Testing",
|
"Topic :: Software Development :: Testing",
|
||||||
"License :: OSI Approved :: MIT License"
|
"License :: OSI Approved :: MIT License"
|
||||||
],
|
],
|
||||||
|
python_requires=">=3.6",
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'Click==7.0',
|
'Click==7.1.2',
|
||||||
'arrow==0.15.5',
|
'arrow==0.17.0',
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
':sys_platform != "win32"': [
|
':sys_platform != "win32"': [
|
||||||
'sh==1.12.14',
|
'sh==1.14.1',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
keywords='gitlint git lint',
|
keywords='gitlint git lint',
|
||||||
author='Joris Roovers',
|
author='Joris Roovers',
|
||||||
url='https://github.com/jorisroovers/gitlint',
|
url='https://jorisroovers.github.io/gitlint',
|
||||||
|
project_urls={
|
||||||
|
'Documentation': 'https://jorisroovers.github.io/gitlint',
|
||||||
|
'Source': 'https://github.com/jorisroovers/gitlint',
|
||||||
|
},
|
||||||
license='MIT',
|
license='MIT',
|
||||||
package_data={
|
package_data={
|
||||||
'gitlint': ['files/*']
|
'gitlint': ['files/*']
|
||||||
|
@ -93,8 +89,7 @@ setup(
|
||||||
# Print a red deprecation warning for python < 3.6 users
|
# Print a red deprecation warning for python < 3.6 users
|
||||||
if sys.version_info[:2] < (3, 6):
|
if sys.version_info[:2] < (3, 6):
|
||||||
msg = "\033[31mDEPRECATION: You're using a python version that has reached end-of-life. " + \
|
msg = "\033[31mDEPRECATION: You're using a python version that has reached end-of-life. " + \
|
||||||
"Gitlint does not support Python < 3.5 or < 2.7, and will be dropping support for " + \
|
"Gitlint does not support Python < 3.6" + \
|
||||||
"Python 2.7 and 3.5 in the next release. " + \
|
|
||||||
"Please upgrade your Python to 3.6 or above.\033[0m"
|
"Please upgrade your Python to 3.6 or above.\033[0m"
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
unittest2==1.1.0; python_version <= '2.7'
|
flake8==3.8.4
|
||||||
flake8==3.7.9
|
coverage==5.3
|
||||||
coverage==4.5.3
|
|
||||||
python-coveralls==2.9.2
|
python-coveralls==2.9.2
|
||||||
radon==4.1.0
|
radon==4.3.2
|
||||||
mock==3.0.5 # mock 4.x no longer supports Python 2.7
|
flake8-polyfill==1.0.2 # Required when installing both flake8 and radon>=4.3.1
|
||||||
pytest==4.6.3; # pytest 5.x no longer supports Python 2.7
|
pytest==6.1.2;
|
||||||
pylint==1.9.4; python_version == '2.7'
|
pylint==2.6.0;
|
||||||
pylint==2.3.1; python_version >= '3.4'
|
|
||||||
-e .
|
-e .
|
||||||
|
|
Loading…
Add table
Reference in a new issue