Jordan Bradford
Jordan Bradford

Reputation: 407

Git commit policy that isolates changes to specific files as separate commits

I want to require SVGs to be committed separately from everything else to keep the diff output of everything else cleaner. For example, I want to disallow commits like this one:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   src/css/main.css
        modified:   src/images/example.svg
        modified:   src/index.html

Can this be done with a pre-commit hook? How would that be written?

Edit: I think git ls-files -dmo will be useful here, but I don't know how to write the script to parse its output.

Upvotes: 3

Views: 240

Answers (1)

torek
torek

Reputation: 488453

Can this be done with a pre-commit hook?

Yes. (Note, however, that such hooks can be bypassed.)

How would that be written?

Depends on what language you want to use to write it.

Shell scripts tend to be the simplest since you can just run Git tools directly. Here, you might run git diff-index --name-status to compare the index (the proposed commit) to the current i.e. HEAD commit, then read through the files being added, modified, or deleted to see if any have names ending with .svg, and if any have names ending with anything else. This lets you tweak the rules to allow deleting .svg files while making other changes. Or, if file-status (added/deleted/modified) is not relevant, this is a bit simpler:

# straight from the sample pre-commit hook
if git rev-parse --verify HEAD >/dev/null 2>&1
then
        against=HEAD
else
        # Initial commit: diff against an empty tree object
        against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

# create a temp file to hold diff output
tmpfile=$(mktemp)
trap "rm -f $tmpfile; exit" 0 1 2 3 15
git diff-index --cached --name-only --diff-filter=ADM $against > $tmpfile

# read the status and names from the temp file.
# we use a temp file simply because "| while read ..." runs
# a sub-shell which means that variable changes don't make
# it back to the parent shell.  there are other workarounds
# but this one is simple.
num_svg=0
num_nonsvg=0
while read name; do
    case "$name" in
    *.svg) num_svg=$((num_svg + 1));;
    *) num_nonsvg=$((num_nonsvg + 1));;
    esac
done < $tmpfile

# now disallow commit if there are mixed svg and non-svg files
if [ $num_svg -gt 0 -a $num_nonsvg -gt 0 ]; then
    echo "this commit affects both .svg files and non-svg files" 1>&2
    exit 1
fi
# run any other checks here too
exit 0

(NB: this is entirely untested)

Upvotes: 2

Related Questions