- What are pre-commits?
- Setup pre-commit
- TwinCAT relevant pre-commits
- Developing your own hooks
- Further ideas
Use the GitHub TwinCAT template repo to set up a TwinCAT repo, including the pre-commits.
What are pre-commits?
Pre-commits are part of a class of so-called git hooks. Hooks enable you to run a script during a git command. Most of these hooks are pre-commit, meaning they do something before a commit, merge, or rebase. Pre-commit hooks can be beneficial; for instance, they can guarantee a uniform code formatting style or verify a file’s JSON or XML format.
You can see some examples of git hooks if you have a project which uses git. Navigate to the
.git/hooks folder and you should see a list of example hooks there. If you can’t see the
.git folder, make sure you have enabled “Hidden items” under the View tab in your Windows Explorer.
If you open one of the files, you see some bash scripts. It’s not necessary to use bash. You can use any programming or scripting language which is available on your system.
You can write your own hooks from scratch, but for most files, there is already a large variety available. Let me show you how you can use these and a structured text hook for your TwinCAT projects.
A popular framework to manage pre-commits is pre-commit. It’s a Python based framework and thus you need a working Python installation on your system before you can use it.
(In case you do not have Python) Download and install conda via one of the methods below. You can either install Miniconda which is a minimal installation. It comes with the bare necessities to get started. Miniconda should be enough for this tutorial. Or, if you would like to have a bit more tools/modules installed (mainly for data analyses), choose Anaconda.
Install pre-commit with either:
pip install pre-commit
conda install -c conda-forge pre-commit
Check if the installation went OK by running the command below in the terminal. If you see a version number then pre-commit works.
$ pre-commit --version
- Next, create a new file called
.pre-commit-config.yamlin the git project folder (same folder as where your
.gitfolder resides) where you want to start using pre-commits. Now you’re all set up to start using pre-commits.
TwinCAT relevant pre-commits
The kind people of the Photon Controls and Data Systems at SLAC have open-sourced their TwinCAT pre-commits. To use the SLAC pre-commits, add the following lines to your
- repo: https://github.com/pcdshub/pre-commit-hooks
# Replaces all leading tabs with spaces
- id: twincat-leading-tabs-remover
# Removes line ids. See point 4 of the link for why you don't need them
- id: twincat-lineids-remover
# Formats .tmc and .tpy files
- id: twincat-xml-format
# Check if there are any libraries whose versions are not fixed
- id: check-fixed-library-versions
For completeness, I’ll also add the following standard pre-commits. You can first run these pre-commits. Later I go into details about what they do and why they are useful.
- repo: https://github.com/pre-commit/pre-commit-hooks
# Removes trailing white spaces
- id: trailing-whitespace
# Checks yaml files for parseable syntax
- id: check-yaml
# Prevents git from committing large files
- id: check-added-large-files
Install the pre-commit hooks with
pre-commit install. This needs to be done once per git repository. Afterwards, you can run
pre-commit run --all-files to let the pre-commits do their job. Note: you only run this command if you have added new pre-commits. In other cases, all pre-commits are automatically executed when you use
git commit .... In this case, the automatic execution limits to the changed files.
Depending on the project you see no, some, or a lot of changed files. For example, when I ran it on my TwinCAT Tutorial repo I saw the following:
For each git hook, you see if it had files to check. If that was the case, you see if any files were changed.
Below I show an example where two hooks were triggered. Here both the leading tabs remover failed (as shown in the screenshot), but also the trailing white space one failed (not shown). Below are the differences I saw afterward in SourceTree:
You see that it removed a trailing space after the
CASE _state OF. Additionally, the file had a mix of tabs and spaces. The tabs were replaced by spaces. These changes were made by the
twincat-leading-tabs-remover respectively. For a full list of all the changes, you can see the differences of this commit.
Hooks what and why
Here I go a little deeper into what the hooks do and why you would want to use them.
What: replaces all leading tabs with four spaces.
Why: consistency. Also in some editors, the length of a tab can differ from the length of four spaces.
What: removes LineIDs from a POU file.
Why: they are only useful locally. When uploaded to source control they only cause visual clutter. For more information see point 4
What: formats the
.tcp files with newlines and indentation.
Why: makes these files readable for humans. Normally TwinCAT doesn’t put any newlines or indentations in these files. Useful if you would like to have these files in source control and see clear differences.
What: checks if there are TwinCAT libraries whose versions are not fixed or explicitly set to the latest version.
Why: ensures that your software behaves the same, even if you install newer library version on your system.
What: removes spaces and tabs at the end of lines.
Why: whitespace at the end of a line does not influence code execution; you can add or remove as many as you’d like. But, they show up as (useless) changes if someone adds some or removes them.
What: checks that programs can read your YAML file. For example, the
Why: ensures that your YAML files do not break.
What: prevents git from adding large files to its history.
Why: to prevent your git tree from becoming huge. Saves time for new users when they download the repo for the first time. Large, non-text files, such as images or binary files usually contain no useful diffs. You want to commit these files using Git LFS.
Developing your own hooks
If you would like to develop your own hooks there are two options: local and remote repo-based hooks. Local hooks are quite easy to set up, but they can only be used in the repo where they are saved. On the other hand, remote-based hooks can be shared across many projects. The TwinCAT hooks you saw earlier, is an example of a remote hook.
You can use many languages to develop your hooks. Most languages need a working installation of that specific language on the system where the hooks are executed. The exceptions are node, python, and ruby. For these languages, no existing installation is needed. Hooks developed in these three languages set up their own (node, python, ruby) environment when first executed. On subsequent runs, the environment is reused.
Remote hooks also need to be a valid git repo. That is because pre-commit tries to do a
git clone ... of the repo URL you supplied in the
.pre-commit.yaml file. To make a remote hook, see the TwinCAT hooks repo as an example.
Local hook example
Developing a local hook is quite straightforward. I’ll explain how to do it by making a hook that checks if all links on this blog start with
https. If they don’t, it replaces
First I added a file called
.pre-commit-config.yaml with the following content. See the comments for their meaning.
- repo: local
# Name of the hook
- id: check-https
# Hook name shown during hook execution
name: check if all links are https
# Where pre-commit can find the script it should call and how it should call it
entry: python _hooks/check_https.py
# The language to use, in this case we're using a language present on the system.
# Using python as an argument would work as well
# A valid regular expression pattern to define which files should be passed to `check_https.py`
When pre-commit runs this local hook, it first searches for files that conform to the regular expression pattern mentioned in
files:. Then it calls the command mentioned under
entry with each filename. For example, it finds the files
about.md. Then it calls
python _hooks/check_https.py README.md about.md.
Next, I created a new file called
_hooks/check_https.py with the following content. See comments for the meaning.
# Open the file
with open(filename, 'r') as fd:
original_lines = fd.readlines()
new_lines = 
changed = False
# For each line in the file check if a https:// is found.
# If so, replace it with https://
for line in original_lines:
if "https://" in line:
line = line.replace("https://", "https://")
changed = True
# If a line was changed, print a message in the console and overwrite the
# original file with the fixed one.
with open(filename, 'w') as fd:
# Parse the filename arguments:
# e.g. Namespace(filenames=['README.md', 'about.md'])
if args is None:
parser = argparse.ArgumentParser()
args = parser.parse_args()
# For each filename execute the file fixer on it
for filename in args.filenames:
# Return 0, which means the hook executed successfully and pre-commit is happy
except Exception as exc:
# If something went wrong return an error code other than 0 and pre-commit
# then knows something went wrong
if __name__ == "__main__":
If you now run
pre-commit run --all-files you should see all the files it changed.
> pre-commit run --all-files
check if all links are https.................................................Failed
- hook id: check-https
- files were modified by this hook
.view files. These files are not recognized as HTML files by prettier, but you could probably make it work by temporarily renaming these files to
.html and then running prettier.
Pre-commits are automatically executed locally whenever you commit something. But you can also add pre-commit to your CI workflow. For example, use prettier.ci to automatically format markdown files of a pull request.
Have you already used pre-commits for your (TwinCAT) projects? Or do you have other ideas? Let me know in the comments below.