Format Swift with a Git Commit Hook

How do you automatically format your Swift code every time you commit it to your Git repository?

Swift Format

There are several Swift formatting tools. Xcode 16 shipped with swift-format included in the toolchain:

$ xcrun --find swift-format
/Applications/Xcode.app/Contents/Developer/Toolchains/
XcodeDefault.xctoolchain/usr/bin/swift-format

To run the formatter:

$ swift format MyFile.swift

Note: This runs the swift-format bundled with the active Xcode development directory (beta or release).

Configuring swift-format

The Xcode settings let you configure various formatting options including indentation but I prefer to use an external configuration file for swift-format.

By default, swift-format looks for a configuration file named .swift-format in the same directory as the files you are formatting. If there isn’t one it looks in each successive parent directory. If it doesn’t find a configuration file it uses a default configuration. You can dump the default configuration to get started:

$ swift format dump-configuration > swift-format

Modify the configuration file to meet your own code style:

{
  "fileScopedDeclarationPrivacy" : {
    "accessLevel" : "private"
  },
  "indentConditionalCompilationBlocks" : true,
  "indentSwitchCaseLabels" : false,
  "indentation" : {
  ...

I keep my default configuration file in my base Developer directory:

$ mv swift-format ~\Developer\.swift-format

When needed, I can override the defaults by placing a modified configuration file in the project-specific root directory.

Note: Don’t forget to prefix the configuration file with a .

Git Commit Hooks

Git Hooks are a way to run custom scripts when different Git actions occur. A pre-commit hook runs when you commit changes to a repository. Here’s the pre-commit script I’m using to run swift-format:

#!/bin/bash

GIT=/usr/bin/git
GREP=/usr/bin/grep
SWIFT=/usr/bin/swift

# Staged (cached) added/copied/modified/renamed files
FILES=$($GIT diff --cached --name-only --diff-filter=ACMR | $GREP '\.swift$')

# Exit early if no staged files
if [ -z "$FILES" ]; then
  exit 0
fi

$SWIFT format --in-place --parallel $FILES
$GIT add $FILES

Notes:

  • The git diff command returns the names of any staged (cached) swift files (A)dded, (C)hanged, (M)oved, or (R)enamed compared to HEAD.
  • I run the swift format command on the staged files in one go, modifying the files in-place.
  • Since formatting the files can change them I need to add the modified files back to the staging area.
  • Exiting the script with a non-zero value cancels the commit.

Installing the Git pre-commit script

Git stores hook scripts in the hooks subdirectory of the Git directory in the project root. To install my pre-commit script I need to copy it to the hooks folder and make it executable:

$ cp pre-commit ~/Developer/Repositories/MyProject/.git/hooks
$ chmod +x ~/Developer/Repositories/MyProject/.git/hooks/pre-commit

Note:

  • This is a client-side hook so I have to install it each time I create or clone a new repository.

Bypass the pre-commit script with the --no-verify flag:

$ git commit --no-verify

Learn More