Jay
banner
jaygooby.mastodon.social.ap.brid.gy
Jay
@jaygooby.mastodon.social.ap.brid.gy
internetworker https://jay.gooby.org he/him

[bridged from https://mastodon.social/@jaygooby on the fediverse by https://fed.brid.gy/ ]
Tree prep done - decorations to follow…

Past me & my daughter’s advice always welcome 😂

Cc: @law_geek
November 30, 2025 at 6:00 PM
Reposted by Jay
Been researching extending git in various ways for a little side project, didn't find a good guide that covers everything so I put one together on my blog: https://nesbitt.io/2025/11/26/extending-git-functionality.html
Extending Git Functionality
I’ve been researching how to extend git for a project I’m working on. There are six distinct patterns that I’ve seen people use to add functionality to git without modifying git itself: * **Subcommands** for adding new commands * **Clean/smudge filters** for transforming file content * **Hooks** for enforcing workflows * **Merge/diff drivers** for custom merging of specific file types * **Remote helpers** for non-git backends * **Credential helpers** for custom auth ## Subcommands Put an executable called `git-foo` anywhere in your `$PATH` and git will run it when you type `git foo`. That’s it. No registration, no configuration. Git literally just looks for executables matching the pattern. This is how most git extensions work: git-lfs, git-flow, git-extras, hub, gh. The awesome-git-addons list has hundreds of examples. #!/bin/bash # Usage: git hierarchize # Install: brew install git-hierarchize (or put this script in $PATH) git log --graph --oneline --all The pattern is good for: * New workflows (git-flow’s branching model) * Integrations with external services (hub/gh for GitHub) * Convenience wrappers (git-extras’ grab-bag of utilities) * Repository inspection tools (git-stats, git-standup) Limitations: You’re just adding commands. You can’t intercept existing git operations, transform content, or change how git talks to remotes. See the git docs for more on how git finds commands. ## Clean/Smudge Filters Filters transform file content on checkout (smudge) and commit (clean). Git pipes the file through your program and stores whatever comes out. # .gitattributes *.secret filter=vault # .git/config or ~/.gitconfig [filter "vault"] clean = gpg --encrypt --recipient [email protected] smudge = gpg --decrypt The clean filter runs when you `git add`, the smudge filter runs when you `git checkout`, and the repository stores whatever the clean filter outputs. **git-crypt** uses this pattern. Your working directory has plaintext files; the repository stores encrypted blobs. Anyone without the key sees garbage, anyone with the key sees the files transparently. **git-lfs** also uses filters. The clean filter uploads the real file to an LFS server and outputs a small pointer file, the smudge filter downloads the real content, and the repository only stores pointers. # What git-lfs stores in the repo (the pointer file) version https://git-lfs.github.com/spec/v1 oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393 size 12345 Filters are good for: * Transparent encryption (git-crypt) * Large file handling (git-lfs) * Normalizing content (converting line endings, stripping trailing whitespace) * Expanding/collapsing keywords The constraint: filters must be idempotent. Running clean twice should produce the same output as running it once. And smudge(clean(x)) should equal x for anything you want to round-trip. One thing to note: filters don’t run until checkout. If someone clones a repo using git-lfs without having git-lfs installed, they get the pointer files, not the actual content. Same with git-crypt: without the key, you get encrypted garbage. There’s no way for the filter to bootstrap itself. See the gitattributes docs for the full filter specification. ## Hooks Hooks are scripts that git runs at specific points: before commit, after merge, before push, on the server when receiving a push. There are about 25 different hook points. # .git/hooks/pre-commit #!/bin/bash npm test || exit 1 The hooks most people use: * **pre-commit** : Run linters, formatters, tests before allowing a commit * **prepare-commit-msg** : Modify the commit message template * **commit-msg** : Validate commit message format * **pre-push** : Run tests before pushing * **post-checkout** : Update dependencies after switching branches * **pre-receive** (server): Enforce policies on what can be pushed Hooks are good for enforcing local workflow (pre-commit linting) and server-side policies (pre-receive rejecting force pushes to main). One limitation: hooks aren’t versioned with the repository. Each developer has to install them locally. Tools like husky, lefthook, and pre-commit exist specifically to solve this by providing a way to declare hooks in config files that do get committed. `core.hooksPath` or `init.templateDir` can configure global hooks that apply to every repo. And `post-checkout` fires after clone completes, so a global post-checkout hook can bootstrap dependencies automatically. **git-branchless** uses hooks heavily. It installs a post-commit hook that records every commit you make, enabling features like undo and automatic rebasing. The hook-based approach means it can observe git operations without replacing git commands. The githooks docs list all available hooks and when they fire. ## Merge/diff drivers You can tell git how to merge or diff specific file types. # .gitattributes *.json merge=json-merge *.png diff=exif # .git/config [merge "json-merge"] driver = json-merge %O %A %B [diff "exif"] textconv = exif Merge drivers receive three files (ancestor, ours, theirs) and produce the merged result. Diff drivers can convert binary files to text for diffing. This is useful for: * Smarter merging of structured formats (JSON, XML, config files) * Making binary files diffable (images via exif data, PDFs via text extraction) * Lock files that shouldn’t merge (use the `binary` merge driver) Most people don’t need custom merge drivers. The built-in 3-way merge handles code well. But if you’re constantly resolving the same conflicts in generated files, a custom driver might help. See the gitattributes docs for custom merge drivers and custom diff drivers. ## Remote Helpers If you want git to talk to something that isn’t a git server, you write a remote helper. Name it `git-remote-foo` and git will invoke it for URLs like `foo::some-address`. # git clone foo::some-address invokes: git-remote-foo origin some-address The helper communicates with git over stdin/stdout using a line-based protocol. It declares capabilities (fetch, push, import, export) and handles the corresponding operations. This is how git talks to non-git systems: * `git-remote-hg` for Mercurial repos * `git-remote-svn` wraps subversion * Various cloud storage backends (S3, GCS) Remote helpers are the most complex extension point. You’re implementing a protocol, handling refs, transferring objects. The gitremote-helpers docs describe the protocol, but it’s dense. Most people end up reading existing helpers as the de facto spec. Still, they’re the only way to make git work with foreign systems transparently. ## Credential Helpers When git needs authentication, it asks a credential helper. Helpers are simpler than remote helpers, they just store and retrieve usernames and passwords. # .gitconfig [credential] helper = osxkeychain Git ships with helpers for OS keychains. The protocol is straightforward: git sends key-value pairs describing what it needs, the helper responds with credentials. You’d write a custom helper to integrate with a secrets manager (Vault, 1Password) or custom authentication system. The gitcredentials docs cover the protocol and available helpers. ## What Language? Git doesn’t care. Here’s what existing projects use: * **Shell** : git-extras, git-flow * **Go** : git-lfs, gh, hub * **Rust** : git-branchless * **C++** : git-crypt * **Python** : pre-commit * **Ruby** : overcommit For filters specifically, startup time matters because they run once per file. Git does support long-running filter processes that stay alive across multiple files (git-lfs uses this), but you have to implement the protocol. ## Configuration Extensions need somewhere to store their settings. A few patterns: **Git config** is the natural choice. Your extension can use `git config` to read/write values under its own namespace: git config --global lfs.fetchrecentalways true git config myextension.somesetting value Config lives in `~/.gitconfig` (global) or `.git/config` (per-repo). Users already know how to edit these. **Dedicated dotfiles** work when you need more structure. git-lfs uses `.lfsconfig`, git-crypt stores keys in `.git-crypt/`. These can be committed to the repo so settings travel with it. **`.gitattributes`** declares which files use filters or drivers: *.psd filter=lfs diff=lfs merge=lfs *.secret filter=git-crypt diff=git-crypt This file should be committed, it’s how the repo tells git which extensions to invoke for which paths. ## Interesting Examples **git-lfs** : Combines multiple patterns. It’s a subcommand (`git lfs track`), uses clean/smudge filters for the actual file handling, and hooks into pre-push to upload files. **git-crypt** : Clean/smudge filters with a subcommand for key management. The C++ implementation keeps the filter fast. **git-branchless** : Hook-based observation combined with subcommands for the UI. Shows how to build features on top of git without modifying git itself. The Rust implementation handles large repos well. **hub/gh**: Pure subcommand pattern. Wraps git commands and adds GitHub-specific features. Shows how far you can get with just new commands. **overcommit** : Hook manager in Ruby. Lets you configure hooks via YAML and provides a library of built-in checks for linting, security, and commit message formatting. ## Installation Required Filters and hooks work transparently, users run normal git commands and your extension does its work, but your extension has to be installed first. If someone clones a repo that uses git-lfs without having git-lfs installed, they don’t get an error, they get pointer files instead of content. Git has no mechanism to say “this repo requires extension X”. The best you can do is document requirements, fail loudly when things are wrong, and make installation easy. git-lfs handles this reasonably well, if you try to push without it installed you get an error pointing you to the fix: $ brew install git-lfs && git lfs install # macOS $ apt install git-lfs && git lfs install # Debian/Ubuntu $ dnf install git-lfs && git lfs install # Fedora * * * If you know of other git extension techniques or projects worth mentioning or have corrections, reach out on Mastodon or submit a pull request on GitHub.
nesbitt.io
November 27, 2025 at 5:01 PM
Reposted by Jay
Unicode normalization.
November 26, 2025 at 10:03 PM
For the next 4 weeks I’m only working Monday & Tuesday, then off until Jan 6. Bliss!

I am popping into the office today to help with next week’s office move though, so another day TOIL due
November 26, 2025 at 8:09 AM
New 21st century chores; cleaning my cleaning robot’s hutch
November 24, 2025 at 8:14 AM
Reposted by Jay
A Brief, Incomplete, and Mostly Wrong History of Programming Languages
James Iry; Thursday, May 7, 2009

1801 - Joseph Marie Jacquard uses punch cards to instruct a loom to weave "hello, world" into a tapestry. Redditers of the time are not impressed due to the lack of tail call recursion […]
Original post on mathstodon.xyz
mathstodon.xyz
November 23, 2025 at 2:43 AM
Reposted by Jay
Announcing the Gem Fellowship, a grant program for improvements to Ruby-related open source projects. https://gem.coop/fellowship/ Want to improve your favorite gem? Submit a proposal, starting next month.
gem.coop
gem.coop
November 20, 2025 at 11:58 PM
Ah yeah! @orbific’s horror advent calendar has arrived 🙌

Here’s a little taste from last year..
November 21, 2025 at 8:32 AM
Been using the Community version of https://avohq.io - just upgraded to Pro. Very happy :D
Avo
Build self-hosted Internal Software in days not months
avohq.io
November 20, 2025 at 2:36 PM
Reposted by Jay
I’m not a great political analyst, but I do feel Labour have made a tactical error by opting to be cunts.
November 18, 2025 at 10:12 PM
Excited to announce our collaboration with the Royal Shakespeare Company - we are launching https://www.shakespearecurriculum.com/ today!

https://charanga.com/site/about-charanga/royal-shakespeare-company/
RSC Shakespeare Curriculum
Be part of the teaching Shakespeare revolution
www.shakespearecurriculum.com
November 12, 2025 at 8:43 AM
I have been stressed and v busy for the last couple of weeks, and so Andrew and the Sisters on repeat is good medicine
November 11, 2025 at 9:18 AM
Just been to see Coilguns - Swedish hardcore/noise in Brighton’s best gay club

Now awaiting Pigs Pigs Pigs Pigs Pigs Pigs Pigs and Lambrini Girls in a slightly bigger venue 😂
November 8, 2025 at 6:47 PM
I should pin this to my profile…
November 4, 2025 at 9:03 PM
I need to do some load testing later this week, and I think a simple post of the URL to fedi will be a good start 😓
November 4, 2025 at 9:23 AM
> Like an unattended turkey deep frying on the patio, truly global distributed consensus promises deliciousness while yielding only immolation.

https://fly.io/blog/corrosion/ 👨‍🍳🤌
Corrosion
Corrosion is distributed service discovery based on Rust, SQLite, and CRDTs.
fly.io
October 30, 2025 at 6:11 PM
I don't think it's possible to ssh into this container...

<remembers Tailscale>

YES... HA HA
HA... YES
October 28, 2025 at 4:46 PM
Two P1s fixed today:

Office out of freshly ground coffee.
Printer jammed.
October 28, 2025 at 9:55 AM
We moved a "miniature" weeping willow from its not-really-suitable home outside our back door to a better home in the garden, over the weekend.

10/10 outdoor action.

Much better than computer touching
October 27, 2025 at 2:41 PM
Hey @agius thanks for https://atevans.com/2012/12/12/nginx-config-try_files-for-s3.html - your 13! year old article got me out of a hole today 🙌
Nginx Config: try_files for S3
blog from andrew evans
atevans.com
October 25, 2025 at 4:26 PM
Just accidentally typed `git stash poop` and now I *have* to make it an alias...
October 24, 2025 at 11:01 AM
Oh, it's Thursday...

I've got a few tasks to finish. Let me just check this email...

Surely this other thing I need to deal with won't take that long...

How about this quick task before I can get on with what I really need to do today...
October 23, 2025 at 12:02 PM
#ukrain 11/10 BN1

Picked the absolute worst time to pop out for lunch ☔️
October 20, 2025 at 12:22 PM
TIL about Linux Capabilities 🤯

Need a process to bind to privileged ports, but not require any other elevated permissions? Use Capabilities.

https://dfir.ch/posts/linux_capabilities/
October 19, 2025 at 8:19 PM