Skip to content

Instantly share code, notes, and snippets.

@heftig
Last active August 7, 2025 13:24
Show Gist options
  • Save heftig/a53fd4a72382bce2502c353d4c42b397 to your computer and use it in GitHub Desktop.
Save heftig/a53fd4a72382bce2502c353d4c42b397 to your computer and use it in GitHub Desktop.
"lint-wrap" helper for Git pre-commit scripts
#!/bin/bash
# Usage: lint-wrap <command> [<arguments>...]
# Example: lint-wrap cargo fmt
#
# The provided command is run with the working tree cleaned of unstaged changes
# and untracked files.
#
# If the command changes any tracked files, the changes are staged.
#
# This script is intended to wrap a linter or code formatter to be run on
# pre-commit. When committing part of the changes in the tree, this ensures the
# changes that actually get committed are linted.
#
# SPDX-License-Identifier: 0BSD
# Copyright (c) 2025 Jan Alexander Steffens (heftig) <[email protected]>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# Enable exit-on-error and disable globbing
set -ef
# Make sure we're in the working tree root
cd -- "$(git rev-parse --show-toplevel)"
msg() {
local attr="$1"; shift
echo -e "\e[${attr}m$*\e[0m" >&2
}
# Runs on any exit from this script
restore() {
if (( ${#untracked_files[@]} > 0 )); then
msg 90 "Restoring untracked files from $untracked"
git read-tree --reset -u $untracked
git rm -q --cached -- "${untracked_files[@]}"
fi
if [[ -n $unstaged ]]; then
msg 90 "Restoring unstaged files from $unstaged"
git read-tree --reset -u $unstaged
fi
if [[ -n $staged ]]; then
msg 90 "Restoring staged files from $staged"
git read-tree --reset $staged
fi
}
untracked_files=()
untracked=
unstaged=
staged=
trap restore EXIT
# Save staged and unstaged changes
staged=$(git write-tree)
msg 90 "Saved staged files to $staged"
git add --update
unstaged=$(git write-tree)
msg 90 "Saved unstaged files to $unstaged"
# Save untracked files, if any
mapfile -t -d '' untracked_files \
< <(git ls-files -z --others --exclude-standard)
if (( ${#untracked_files[@]} > 0 )); then
git add -- "${untracked_files[@]}"
untracked=$(git write-tree)
msg 90 "Saved untracked files to $untracked"
fi
# Remove unstaged changes and untracked files
git read-tree --reset -u $staged
# Our tree is clean, process it now
msg 1 "Running $*"
"$@"
# Success! Stage any new changes
git add --update
staged=$(git write-tree)
msg 90 "Saved staged files to $staged"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment