Pre-Commit Hooks and How To Use Them To Automate Your Obsidian Vault

4/13/20236 views • 4 min read • 0 likes

Pre-Commit Hooks and How To Use Them To Automate Your Obsidian Vault

What are pre-commit hooks?

Pre-commit hooks are scripts that run before code is committed to a repository. They can be written in any language and can be used to automate linting, formatting, testing, and security scanning. You can configure pre-commit hooks to run automatically when You run the git commit command.

The primary purpose of pre-commit hooks is to catch issues early in the development process. By running automated tests before committing code, You can catch issues before they become problems. This can help prevent bugs and security vulnerabilities from making their way into production.

Why use pre-commit hooks?

Pre-commit hooks provide several benefits to You and Your project team:

  1. Catch errors early: Pre-commit hooks catch errors before they are committed to the repository. This helps prevent errors from being propagated throughout the codebase and causing problems later on.

  2. Enforce coding standards: Pre-commit hooks can enforce coding standards and style guidelines, ensuring that all code in the repository is consistent and easy to read.

  3. Ensure code quality: Pre-commit hooks can run tests, lint code, and perform other checks to ensure that code meets quality standards.

  4. Improve security: Pre-commit hooks can scan code for security vulnerabilities, ensuring that sensitive information is protected and that the codebase is secure.

How to set up pre-commit hooks?

You can set up pre-commit hooks by following these simple steps:

  1. Install a pre-commit hook manager: There are several pre-commit hook managers available, such as pre-commit and husky. These tools make it easy to manage and configure pre-commit hooks. (You can install pre-commit using asdf-vm. Check out my Medium story on the WSL, where I also talked about asdf-vm.)

  2. Create a pre-commit hook script: Create a script that performs the actions You want to run before committing code. You can use most programming languages like Python or JavaScript for this.

  3. Configure the pre-commit hook manager: Configure the pre-commit hook manager to run Your pre-commit hook script before committing code. If You use pre-commit you only have to add one yaml file for this.

How to use pre-commit hooks to automate Your Obsidian vault?

As I told You in my last Medium story, I am using Obsidian. If You haven’t read this story or haven’t ever heard of Obsidian, make sure to check it out:

https://krimphove.site/blog/ive-switched-from-bullet-journaling-to-using-obsidian-my-conclusion-after-six-months

I also use Obsidian to create and maintain my personal knowledge repository on GitHub. However, while the new type of links provided by Obsidian comes in handy, it is not supported by GitHub.

So I wrote a pre-commit hook, that checks if a document contains an Obsidian link and parses it if necessary:

from __future__ import annotations

import argparse
import fileinput
import os
import re
from typing import Sequence

PASS = 0
FAIL = 1

REGEX_OBSIDIAN_LINK = re.compile('\[\[.*\]\]')
REGEX_FILE = re.compile('.*\..*')


def find_file(name, path):
    # finds the first file with a matching name in the given directory
    for root, dirs, files in os.walk(path):
        for file in files:
            if file.startswith(name) and REGEX_FILE.match(file):
                return os.path.join(root, file)


def parse_link(link, root_file):
    # parses a link from Obsidian link format to standard Markdown link format
    link = link.strip('[]')
    link = link.split('|')
    link_name = link[0]
    file_name = link[-1]
    root_dir = os.getcwd()
    file_path = find_file(file_name, root_dir)
    if file_path:
        file_path = os.path.relpath(file_path, root_file)
        return f'[{link_name}]({file_path})'
    else:
        return None

def main(argv: Sequence[str] | None = None) -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument('filenames', nargs='*', help='Filenames to check')
    args = parser.parse_args(argv)

    for filename in args.filenames:
        if filename.endswith('.md'):
            with fileinput.input(filename, inplace=True) as f:
                for line in f:
                    for link in REGEX_OBSIDIAN_LINK.findall(line):
                        new_link = parse_link(link, filename)
                        if new_link:
                            line = line.replace(link, new_link)
                    print(line, end='')

    return PASS


if __name__ == '__main__':
    raise SystemExit(main())

Run pre­-commit run --all-files to run Your hook for the first time on all of the files in your repo. If there are no Obsidian links, all tests pass, and the commit gets done:

If there are Obsidian links, the tests fail, but pre-commit parses all commits and You can add the changes and try to commit again:

Other pre-commit hooks could check whether the links are actually pointing to an existing file, or You could use them to insert files into others.

You can get more information on how to deploy this to Your project in my GitHub repo, where I will also add more hooks ready to use for You in the future:

https://github.com/lkrimphove/pre-commit-hooks