Jailbreaking Magit Commit
Apologies in advance that I couldn't think of a properly descriptive title, but at least it sounds catchy... Now that I have your attention ;)
Most of my development occurs within FreeBSD jails, as a security measure against you-know-what that's all the rage these days. The secured environment naturally must never have a git identity or SSH keys which would give any form of access to impersonate me or do something destructive.
My git frontend, Magit,
will open in the "same" git repository by default.
If I have a buffer open visiting file /rpc:devjail:~/code/project/crates/foo/src/file.rs,
then it will be acting through the host devjail.
(That /rpc:devjail: bit uses an Emacs feature called TRAMP
which transparently accesses the host devcube through the rpc protocol.
It's sort of like editing files over SSH, but WAY better.)
So, when Magit runs git, it will run inside the jail.
That's actually fine, most of the time.
Staging files works normally, and when I jump to a file,
it's still doing it within the jail context.
The latter is important because if I visited a file outside the jail,
tools like rust-analyzer will kick off with essentially the same directory tree,
but on the "host" side of the barrier, which may have slight environmental differences.
Suffice it to say, this is working as intended... but...
This creates an ergonomic problem when it comes time to commit. I have intentionally NOT given the jail environment a commit identity, and its SSH identity doesn't have push access. This means that options like commit and even stash will fail.
I currently work around this by manually opening up a new buffer pointed at the host.
For example, in the above scenario, I could open /rpc:devhost:~/code/project/crates/foo/src/file.rs,
where devhost is the hostname of the actual host running the jail,
which I assume has the file accessible at the exact same path (I set up my dev environment with this assumption).
Then I have to launch Magit from there again, and finally commit.
Besides being annoying,
it leaves a few buffers around for me to clean up later,
and introduces an easy path to opening the "same" file multiple times on "different" hosts,
re-triggering another rust-analyzer session etc...
it's not awful, but it's no Pit of Success either!
So last night I thought, why not fix this. Unlike a sealed GUI application, Magit (like all Emacs packages) is really a bunch of smaller functions. We can use the advice feature to effectively add to the existing definition of a function. This provides a clean way of hooking in to do what we want, which is basically "if the host is X, rewrite it to Y."
We can hook up advice to the specific functions which are responsible for commits, staging, etc. to remap the host. I created a simple mapping which can be configured with new entries as needed. Anything not in the mapping runs per normal. And now the workflow is completely transparent and free of extra buffers and manual steps. The coolest part is that none of the steps needed special casing like "oh, we're using the tramp-rpc package, so we need to set up a buffer with this function call and..."
It really "just works." The T in TRAMP is for transparent.
This would not be possible with 99.9% of editor setups. The granular scriptability and emphasis on small, composable units is one of many reasons I think Emacs is more relevant than ever!
Here's the commit in my dotfiles repo with the code in case you want to do something similar.
PS: The astute reader may notice that I'm making a security assumption here. Specifically, I need to trust the elisp in my environment. Putting a finer point on it, that means I'm assuming that things like the agent-shell (or any other package) doesn't contain a vulnerability which would result in commandeering Emacs. Elisp is a memory-safe language, removing the most common exploit vector, and most units of elisp are small and composable, mitigating the risks, but it's worth being mindful of that as you select dependencies for your Emacs environment!