Improve Code Quality and Git History with Automation tools

Al-Waleed Shihadeh
Feb 5 Β· 9 min read

Lets us follow a standard and a consistent way for integrating changes into the source code

Photo by Glenn Carstens-Peters on Unsplash

Problem

Code quality is always an important topic in software development because it dramatically influences the maintenance efforts needed to keep supporting the projects. That is why most of the software application projects are using the Pull Request & Code Review concepts to ensure a specific quality level. With code reviews engineers will review the changes introduced by their peers and try to verify the quality of the code being integrated, usually, they check for some of these items

  • Code style and conventions adopted by the team.
  • For interpreted languages such as ruby or python, they could verify the syntax of the source code.
  • Extra or unnecessary statements such as debugging comments, print statements, and debugging test code.
  • Missing Unit tests.
  • The implementation of the change and the algorithms used.

In my opinion, performing the code review process for each pull request to verify and check for all the points above (maybe more depends on the project nature) have a couple of drawbacks:

  • The process is time-consuming, engineers will spend a lot of time verifying that the PRs are compliant with the project standards.
  • Even with reviewing each of the PRs, it is not guaranteed that we will be able to follow the standards of the code style for each of the PRs. Engineers are human beings and they could miss somethings πŸ˜†.

Fortunately, some of these checks and verifications can be done automatically and performed without human interaction before even creating the PRs. In this post, I will present a tool that can be used to automate the verification of some of the items discussed above.

Git Hooks and Overcommit

Git Hooks is a technique that can be used to trigger the executions of custom scripts when specific events happen such as committing code or rebasing a branch. There are two types of git hooks: client-side such as adding a new commit locally and server-side such as receiving pushed commit hooks. We can use Git Hooks to execute our scripts to verify the code style before pushing the changes to the remote repository and create the Pull Request.

One further step that we can do here is to use the Overcommit gem for handling the git hooks and the verifications scripts instead of reinventing the wheel and writing these scripts from scratch.

Overcommit is a ruby gem that manage and configure Git hooks. The gem allows us to define the supported hooks in a yaml files and to customize the scripts to our needs. The Gem is providing a long list of verification scripts for all the supported Git hooks. In addition, The gem is also allows writing and integrating custom scripts.

Install and integrate Overcommit

Integrating overcommit with a git project can be simply done by running the below commands in the root directory of the project.

$ gem install overcommit
$ overcommit --install

In case that project is using bundle to install the gems you can add the overcommit gem to the project Gemfile and then install the gem. Below are the commands needed for doing the task.

$ echo "gem 'overcommit', '~> 0.52.0'" >> Gemfile
$ bundle install
$ overcommit --install

The command overcommit β€” install will install overcommit hooks in the respective repository, create a .overcommit.yml settings file in the current folder and backup project existing hooks.

Overcommit is supporting the following Built-In Hooks.

  • post-checkout hooks will be executed only after a successful git checkout command is executed.
  • post-merge hooks run after a git merge executes successfully with no merge conflicts.
  • post-rewrite hooks run after a commit is modified by a git commit --amend or git rebase
  • commit-msg hooks are run against every commit message you write before a commit is created
  • pre-commit hooks are run after git commit is executed, but before the commit message editor is displayed.
  • pre-push hooks are run during git push command, after remote refs have been updated but before any objects have been transferred.
  • pre-rebase hooks are run during the command git rebase and before any commits are rebased.

The default configuration file of all these hooks can be found here, you can use this file as a reference to fill the .overcommit.yml file for your project.

The first section that I configure usually is the PostCheckout section, In this section I define all the steps needed to be performed after checking out a new branch locally. For instance, for most ruby applications there is a need to install all dependencies of the project after the checkout, this step can be configured to be triggered automatically by including the below configuration the in .overcommit.yml

BundleInstall:
enabled: true
description: 'Install Bundler dependencies'
requires_files: true
required_executable: 'bundle'
install_command: 'gem install bundler'
flags: ['install']
include:
- 'Gemfile'
- 'Gemfile.lock'
- '*.gemspec'

The next section that I configure is the CommitMsg section to define and force the commit messages standards for the project. If one of these hooks fail, adding the commit to the git project will fail also.

β†’ EmptyMessage this hook will ensure that the git history of the project will not contain commits with empty messages.

EmptyMessage:
enabled: true
description: 'Check for empty commit message'

β†’ TextWidth Define a minimum and max length for the commit messages. This is also helpful to avoid unclear or short commit messages.

TextWidth:
enabled: true
description: 'Check text width'
max_subject_width: 60
min_subject_width: 4
max_body_width: 72

β†’ CapitalizedSubject: This is to ensure that each of the commit messages starts with a capital letter.

CapitalizedSubject:
enabled: true
description: 'Check subject capitalization'
on_warn: fail

β†’ SingleLineSubject: Ensure that the first line of a commit message ends with a new blank line.

SingleLineSubject:
enabled: true
description: 'Check subject line'

β†’ TrailingPeriod: Ensure that the commit message subject does not end with a period.

endTrailingPeriod:
enabled: true
description: 'Check for trailing periods in subject'
required: true
on_warn: fail

β†’ MessageFormat: Ensure a specific format for the git commit messages. The example below will only accept commits that are following the defined pattern.

MessageFormat:
enabled: true
description: 'Check commit message matches expected pattern'
pattern: 'Close #(.+)[|](.+)'
expected_pattern_message: 'Close #<Issue Id> | <Description>'
sample_message: 'Close #BUG-1234 | Fix syntax bug'
on_warn: fail

β†’ SpellCheck: Check for misspelled words in the git commit messages.

SpellCheck:
enabled: true
description: 'Check for misspelled words'
required_executable: 'hunspell'
flags: ['-a']

The next configuration section is the PreCommit section and in this section we can verify the commit data and validate the introduced source code changes. If any of the implemented hooks fails, the commit will fail too and the engineers will be forced to make changes to comply with the defined standards.

β†’ AuthorEmail: This hook ensures that the contributors configured their git clients with valid email addresses. For instance, with the below configs engineers will be able to commit changes only with Gmail emails. This is a good idea if you would like to ensure that only the company emails are used for the development and personal emails are forbidden.

AuthorEmail:
enabled: false
description: 'Check author email'
requires_files: false
pattern: '^[^@]+@gmail.com'

β†’ AuthorName: This hook ensures that the contributors configured their git client with a valid author name.

AuthorName:
enabled: false
description: 'Check for author name'
requires_files: false

β†’ BrokenSymlinks: This hook checks for broken symlinks within the project files.

BrokenSymlinks:
enabled: false
description: 'Check for broken symlinks'
quiet: true

β†’ BundleAudit: Checks for vulnerable versions of gems in Gemfile.lock using the bundler-audit gem.

BundleAudit:
enabled: false
description: 'Check for vulnerable versions of gems'
required_executable: 'bundle-audit'
install_command: 'gem install bundler-audit'

β†’ BundleOutdated: Check for outdated gems within the project, Only gems that have warning or deprecation notes will be displayed.

BundleOutdated:
enabled: false
description: 'List installed gems with newer versions available'
required_executable: 'bundle'
flags: ['outdated', '--strict', '--parseable']
install_command: 'gem install bundler'

β†’ CaseConflicts: This hook is Checking for files that would conflict in case-insensitive filesystems

CaseConflicts:
enabled: false
description: 'Check for case-insensitivity conflicts'

β†’ FixMe: This hook is checking for files that contain the any of defined keywords. This Hook can help in preventing commuting files that contain keywords such as TODO.

FixMe:
enabled: false
description: 'Check for "token" strings'
required_executable: 'grep'
flags: ['-IEHnw']
keywords: ['BROKEN', 'BUG', 'ERROR', 'FIXME', 'HACK', 'NOTE', 'OPTIMIZE', 'REVIEW', 'TODO', 'WTF', 'XXX']
exclude:
- '.overcommit.yml'

β†’ FileSize: This hooks is checking for oversized files before committing.

FileSize:
enabled: false
description: 'Check for oversized files'
size_limit_bytes: 1_000_000

β†’ ForbiddenBranches: This hook can be used to prevent commits to the defined branches. This is very helpful in case you would like to freeze code changes on specific branches.

ForbiddenBranches:
enabled: false
description: 'Check for commit to forbidden branch'
quiet: true
branch_patterns: ['master']

β†’ MergeConflicts: This hook checks for unresolved merge conflicts in the source code.

MergeConflicts:
enabled: false
description: 'Check for merge conflicts'
quiet: true
required_executable: 'grep'
flags: ['-IHn', "^<<<<<<<[ \t]"]

β†’ RailsBestPractices: Validate the source code using the style and syntax using the rails_best_paractices gem.

RailsBestPractices:
enabled: false
description: 'Analyze with RailsBestPractices'
required_executable: 'rails_best_practices'
flags: ['--without-color']
install_command: 'gem install rails_best_practices'

β†’ RailsSchemaUpToDate: This hook Validates if the schema file is up to date and includes all the defined database migrations or not.

RailsSchemaUpToDate:
enabled: false
description: 'Check if database schema is up to date'
include:
- 'db/migrate/*.rb'
- 'db/schema.rb'
- 'db/structure.sql'

β†’ Reek: Reek is Code smell detector for Ruby applications, This hook will execute reek against all modified files.

Reek:
enabled: false
description: 'Analyze with Reek'
required_executable: 'reek'
flags: ['--single-line', '--no-color', '--force-exclusion']
install_command: 'gem install reek'
include:
- '**/*.gemspec'
- '**/*.rake'
- '**/*.rb'
- '**/Gemfile'
- '**/Rakefile'

β†’ RuboCop is a Ruby static code analyzer (a.k.a. linter) and code formatter. This hook will run therubocop command against all the modified files in the commit.

RuboCop:
enabled: false
quiet: false
description: 'Analyze with RuboCop'
required_executable: 'rubocop'
flags: ['--format=emacs', '--force-exclusion', '--display-cop-names']
install_command: 'gem install rubocop'
include:
- '**/*.gemspec'
- '**/*.rake'
- '**/*.rb'
- '**/*.ru'
- '**/Gemfile'
- '**/Rakefile'

β†’ RubySyntax: This hook tries to execute the source code in a ruby shell to check for any syntax errors or bugs.

RubySyntax:
enabled: true
description: 'Check ruby syntax'
required_executable: 'ruby'
command: [
'ruby',
'-e',
'ARGV.each { |applicable_file| ruby_c_output = `ruby -c #{applicable_file}`; puts ruby_c_output unless $?.success? }'
]
include:
- '**/*.gemspec'
- '**/*.rb'

The presented list above is not the only hooks that Overcommit is supporting, actually, Overcommit is supporting many more hooks for validating and checking different source codes built using different programming languages such as PythonFlake8, PhpCsFixer, NginxTest, TravisLint, and many more.

The last configuration section that I would like to mention is the PrePush section, In this section, we can trigger scripts that need to be executed before pushing the changes to the remote repository and creating the Pull Request. For instance, Running unit tests RSpecs locally to ensure that the new changes did not break any feature in the application.

RSpec:
enabled: true
description: 'Run RSpec test suite'
required_executable: 'rspec'

The complete implementation of the .overcommit.yml with the hooks presented in this post is shown below.

Conclusion

Overcommit is a great tool that can be used to improve the code quality and the git logs of software development projects. This tool can help us in defining and ensuring a specific format for git messages, validate the source code syntax and style and prevent committing codes that do not comply with the project standards.

Follow us on Twitter 🐦 and Facebook πŸ‘₯ and Instagram πŸ“· and join our Facebook and Linkedin Groups πŸ’¬.

To join our community Slack team chat πŸ—£οΈ read our weekly Faun topics πŸ—žοΈ, and connect with the community πŸ“£ click here⬇

Faun

The Must-Read Publication for Aspiring Developers & DevOps Enthusiasts. Medium’s largest DevOps publication.

Al-Waleed Shihadeh

Written by

Team Lead & Product Owner

Faun

Faun

The Must-Read Publication for Aspiring Developers & DevOps Enthusiasts. Medium’s largest DevOps publication.

More From Medium

More from Faun

More from Faun

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium β€” and support writers while you’re at it. Just $5/month. Upgrade