Welcome to gitflow-linter’s documentation!

About

gitflow-linter is command line tool written in Python. It checks given repository against provided rules to ensure that Gitflow is respected.

What is Gitflow? Based on Atlassian:

The Gitflow Workflow defines a strict branching model designed around the project release.

[…]

It assigns very specific roles to different branches and defines how and when they should interact. In addition to feature branches, it uses individual branches for preparing, maintaining, and recording releases.

As they wrote: Gitflow is ideally suited for projects that have a scheduled release cycle. It means that Gitflow is not always recommended, but when it is, you’d better stick to the rules!

And this is when gitflow-linter can help ;-)

Quick Start

Installation

You can install the linter from

  • pip

pip install gitflow-linter
  • or the source code

git clone https://github.com/fighterpoul/gitflow_linter.git
cd gitflow_linter
git checkout 0.1.0
python setup.py install

Usages

Usage: gitflow-linter [OPTIONS] GIT_DIRECTORY

  Evaluate given repository and check if gitflow is respected

Options:
  -s, --settings FILENAME
  -o, --output [console|json]
  -p, --fetch-prune            Linter will refresh the repo before checking
  -d, --allow-dirty            Linter will ignore the fact that the given repo
                               is considered dirty

  -w, --fatal-warnings         Returned code will be 1 anyway, even if there
                               are warnings but no errors

  -F, --date-from [%Y-%m-%d]   Issues introduced before this date will be
                               ignored.

  -T, --date-to [%Y-%m-%d]     Issues introduced after this date will be
                               ignored.

  --help                       Show this message and exit.

Standard use case looks pretty simple:

gitflow-linter /path/to/git/repository

Warning

URL to a remote is not supported. Passing https://github.com/fighterpoul/gitflow_linter.git as the argument will fail.

Hint

Run git fetch --prune before to make the repo clean and clear

Settings

You can manipulate how linter works by using yaml file with settings.

  • place the settings file in a root folder of your repo as gitflow_linter.yaml

repo_dir/
|__ .git/
|__ gitflow_linter.yaml
|__ other files and folders

or

  • wherever you want and pass as an option.

gitflow-linter /path/to/git/repository --settings=my_settings.yaml

Example file:

branches: # optional, defines how your gitflow is configured
  master: master
  develop: develop
  features: feature
  fixes: bugfix
  releases: release
  hotfixes: hotfix
  others:
    - spike

rules: # mandatory, defines what needs to be validated and how
  single_master_and_develop:
  no_old_development_branches:
    max_days_features: 50
  no_orphan_branches:
  master_must_have_tags:
  no_direct_commits_to_protected_branches:
  version_names_follow_convention:
    version_regex: ^\d+\.\d+(\.\d+)?$ # standard major.minor(.optional patch) convention
  dev_branch_names_follow_convention:
    name_regex: ^\d+-[\.a-zA-Z0-9_-]+?$ # you may wish to require starting dev branches with eg. ticket numbers
  no_dead_releases:
    deadline_to_close_release: 30
  no_dependant_features:
    max_dependant_branches: 0

Rules

Available rules

Rule

Description

single_master_and_develop

gitflow strongly relies on the fact that there is (1) only one branch for keeping the release history and (2) only one integration branch

no_old_development_branches

having old feature or bugfix branches may create a mess in the repository

use max_days_features option to configure what ‘old’ means for you

no_orphan_branches

having branches that are out of configured folders (eg. created out of feature/, bugfix/) may be an indicator that you do something wrong and create a mess in the repo

master_must_have_tags

if your master branch contains commits that are not tagged, it probably means that you don’t use master as your releases history keeper

no_direct_commits_to_protected_branches

the purposes behind develop and master are different but there is an assumption that at least those two are protected

the rule is here to check if it is really the case and both branches does not contain direct commits (commits that were pushed directly)

version_names_follow_convention

checks if release branches and tags follow version naming convention

the convention must be specified in version_regex argument as a regular expression string

dev_branch_names_follow_convention

sometimes you may wish to have feature and bugfix branch names containing eg. ticket numbers

the given convention is checked by providing name_regex as an argument

if you want to provide different conventions for features and bugfixes, use feature_name_regex and bugfix_name_regex respectively

no_dead_releases

release branches that are not closed may create a mess in the repository and breaks the master/main branch - releases must be closed as soon as they are deployed to production environment (or just before, depending on your case)

since hotfixes are in fact releases started from master instead of develop, the rule will be checked against them as well

configure how long releases are supposed to be maintained by using deadline_to_close_release (number of days)

no_dependant_features

creating feature/bugfix branches one from another or merging them together before merging to develop may result in ugly issues during code review and merge mistakes

creating such a feature/merge is sometimes inevitable, you must configure the limit of such branches by using max_dependant_branches option

Severity

Each rule (even a custom one handled by a plugin) might be optionally configured by using severity option:

rules:
  a_rule:
    severity: info

If provided, level of all detected issues for a rule will be changed, according to provided value.

Provided severity value must correspond to one of Level enum cases:

class gitflow_linter.report.Level(value)

An enumeration.

ERROR = 'error'
INFO = 'info'
WARNING = 'warning'

Results

By default once job is done a report is printed as a text in a human readable form:

============================================================
Report for git repository: GIT_DIRECTORY
============================================================
Statistics:
    main: 1 branch(es)
    dev: 1 branch(es)
    features: 154 branch(es)
    fixes: 77 branch(es)
    releases: 5 branch(es)
    hotfixes: 3 branch(es)
============================================================

Results:

✅   /* rule from yaml that has been checked */
    Quick info on what was checked

❌   /* next rule from yaml */
    Quick info
    Issues detected:
        - Details of an issue, usually with the name of a problematic branch

❌   /* rule with error, eg. bad yaml config */
    ERROR!
    Issues detected:
        - 💀 Cannot be checked because of error: /* reason, eg. missing argument */

You can change it by providing a desired output

gitflow-linter /path/to/git/repository --output=json >> results.json

Then you should see in the console something that contains the same data as above but might be further processed.

Either way, in case of any issues with error severity the exit code will be 1. If repo is all good then 0 is returned. You can change that by providing -w (or --fatal-warnings) flag to return 1 if there are warnings but no errors. See severity section for more info.

Plugins

gitflow-linter is by design open for extensions, you can write a plugin and define your own rules as well as override implementation of existing ones.

Hint

The module that is installed and has name gitflow_{name}_linter will be considered as a plugin.

General principles

gitflow-linter is written by using Visitor pattern. Plugin is just a list of visitors that are subscribed to check corresponding rules.

# gitflow_my_awesome_plugin_linter/__init__.py

class MyAwesomeRuleVisitor(gitflow_linter.visitor.BaseVisitor):

    @property
    def rule(self):
        return 'my_awesome_rule_that_can_be_added_into_yaml_file'

    @gitflow_linter.visitor.arguments_checker('my_awesome_rule_argument')
    def visit(self, repo: gitflow_linter.repository.Repository, args, **kwargs) -> gitflow_linter.report.Section:
        # visit repository and return a ``Section`` with results of inspection
        # you can read ``Gitflow`` options by using self.gitflow (eg. name of branches): self.gitflow.develop
        # you can read your rule settings by using kwargs['my_awesome_rule_argument']

def visitors(gitflow: Gitflow) -> List[gitflow_linter.visitor.BaseVisitor]:
    return [MyAwesomeRuleVisitor(gitflow=gitflow)]

Example visitor will be ran if yaml settings file contains the rule:

rules:
    my_awesome_rule_that_can_be_added_into_yaml_file:
        my_awesome_rule_argument: 20

Hint

If your plugin’s visitor returns an existing, pre-configured rule, it will be ran instead of default visitor. This is how you can override default behaviour.

To verify if a plugin is properly installed and recognized you can run gitflow-linter-plugins

Available gitflow-linter plugins:
- gitflow_authors_linter handles following rules: 
	* no_multiple_open_features_per_author

Example

Exploring official plugin gitflow_authors_linter seems like a good starting point.

API Reference

From a plugin perspective, those are the most important classes:

class gitflow_linter.repository.Repository(repo: git.repo.base.Repo, gitflow: gitflow_linter.rules.Gitflow, should_fetch=False, allow_dirty=False)
raw_query(query: callable, predicate: Optional[callable] = None, map_line: Optional[callable] = None)

Let you run raw queries on GitPython’s git object

Parameters
  • query – callable where you can run raw query, eg. lambda git: git.log('master')

  • predicate – optional callable where you can decide if given line should be included, eg. lambda commit: commit.startwith('Merged'). All lines are included if predicate is not given.

  • map_line – optional callable where you can map line to other object, eg. when query returns list of name of branches, you can map them to branch objects: lambda line: repo.branch(line)

Returns

list of lines returned by query that matches optional predicate, eg. ["sha-of-commit1", "sha-of-commit2", ...]

unique_commits_for_branch(branch: git.refs.remote.RemoteReference, force_including_head=True)set

Returns set of unique commits for the given branch. Only commits that appear specifically on the branch will be returned. If a commit is included in the given branch and any other branch, it won’t be taken into consideration.

Parameters
  • branch – the commits specific to the branch will be returned

  • force_including_head – the flag will force including head commit of the branch. If a second branch has been started from branch passed as param, the flag set to True will ensure, that at least the one head commit will be returned, otherwise second/child branch will “consume” all commits and none will be considered as unique for the given branch.

Returns

set of unique commits for branch passed as the

class gitflow_linter.rules.Gitflow(settings: dict)

Contains all settings related to branches from YAML file. It extends AttributeDict, so settings may be accessed like properties: gitflow.features

It will contain custom settings if you add them in YAML file as a child of branches node.

class gitflow_linter.visitor.BaseVisitor(gitflow: gitflow_linter.rules.Gitflow)

Abstract class describing how gitflow-linter works. A visitor must provide a rule that it is supposed to verify. The linter will let the visitor visit a repository only if user wants to check the repository against the rule. Plugins can override default visitors by returning the same rule as a visitor they wish override.

abstract property rule: str
Returns

Rule from YAML file that is checked by the visitor

abstract visit(repo: gitflow_linter.repository.Repository, *args, **kwargs)gitflow_linter.report.Section

Verifies the repository - checks if self.rule is respected

Parameters
  • repo – Tiny wrapper for GitPython’s repository

  • args

  • kwargs – arguments from YAML file

Returns

Section with results

class gitflow_linter.report.Section(rule: str, title: str, issues=None)

Represents repository verification done for a single rule. Results are represented by list of Issues.

append(issue: gitflow_linter.report.Issue)

Adds new issue detected

Parameters

issue – New issue detected

Returns

class gitflow_linter.report.Issue(level: gitflow_linter.report.Level, description: str, obj: Optional[git.refs.reference.Reference] = None)
classmethod error(description: str, obj: Optional[git.refs.reference.Reference] = None)

Creates an Issue with ERROR severity for related git object

classmethod info(description: str, obj: Optional[git.refs.reference.Reference] = None)

Creates an Issue with INFO severity for related git object

classmethod warning(description: str, obj: Optional[git.refs.reference.Reference] = None)

Creates an Issue with WARNING severity for related git object