How to check photos in CI.

A repository that accepts image contributions will eventually accept somebody's GPS coordinates. --check is the mode for catching that before the commit lands.

~3 minutes macOS · Linux ScrubPony
// at a glance
  1. Run --check by hand first
  2. Read the exit code
  3. Wire it into a pre-commit hook
  4. Add it to CI
Prerequisites
  • ScrubPony installed on the machine running the hook or the CI job
  • A repository with images in it
// step 01

Run it by hand

--check reports and changes nothing. It is quiet about files that are already clean — the useful output is the short list of the ones that are not.

$ scrubpony --check -r ~/Pictures/to-upload
~/Pictures/to-upload/IMG_4471.jpg: 3 segments of identifying metadata, including GPS
~/Pictures/to-upload/scan.jpg: 1 segment of identifying metadata
scrubpony: 2 of 118 carry identifying metadata (0 not JPEG, 0 skipped, 0 failed)
$ echo $?
2
// step 02

Learn the exit codes

0 means clean or nothing to do. 2 means identifying metadata was found. 1 means something went wrong — unreadable, malformed, write failed. 64 is a usage error. An error outranks a dirty verdict even though 2 is the larger number: a script that sees 2 concludes "found metadata, scrub it", and if a file was also unreadable that is the thing it needs to know first.

// step 03

Wire it into a pre-commit hook

Check only what is being committed, so the hook stays fast on a large repository.

#!/bin/sh
# .git/hooks/pre-commit
files=$(git diff --cached --name-only --diff-filter=ACM | grep -Ei '\.jpe?g$')
[ -z "$files" ] && exit 0
echo "$files" | xargs scrubpony --check
status=$?
[ $status -eq 2 ] && echo "run: scrubpony -i $files" >&2
exit $status
// step 04

Add it to CI

One line in whatever runs your checks. The whole repository is fast enough — 1,200 files in about 3.2 seconds — that scanning everything on every run is usually simpler than working out what changed.

$ scrubpony --check -r .

Verify it worked.

  • A repository with a geotagged JPEG in it exits 2 and names the file.
  • A clean repository exits 0 and prints only the summary line.
  • Re-running --check immediately after scrubpony -i exits 0 — a scrubbed file does not report dirty forever.

Common questions.

Why does --check not just count dropped segments?

Because a photo scrubbed once still carries the 36-byte orientation block, and policy still drops it. Counting dropped segments would report every rotated photo as dirty forever and the hook would get switched off within a week. --check counts identifying segments: a block that matches the bytes this tool emits, exactly, is not one of them.

Should the hook scrub automatically instead of failing?

That is a taste question. Failing loudly makes the author look at the file, which is often the right outcome — a photograph with GPS in it may also have a street sign in it, and only a person can decide about that.

Does it slow the hook down?

Barely. It reads the segments before the scan and stops; it never decodes an image.

Next steps.

Get ScrubPony

Strips identifying metadata out of JPEGs without touching a pixel. C99, no dependencies beyond libc, macOS and Linux, MIT-licensed.