Show HN: 'git inject' – amend commits other than HEAD
If you're as pedantic as I am about the git history you're about to push into master (as far as I can control it, I strive to keep each commit conceptually coherent), you'll often come to a situation where you have a modification that really belongs in an older commit. It takes a few git commands to stash other changes away, commit the modification, then interactively rebase over some previous commit, move your commit into place, change to 'fixup', save, exit, and then unstash whatever else you had lying around. Not fun.
Here's how 'git inject' makes it fun:
>> git inject <commit-ref> <patch-ref>
>> git inject HEAD^^ -a # inject all work-dir modifications
>> git inject a28kd8 -p # interactively select patches to inject
>> git inject HEAD~4 file1 # inject the modifications in file1
Just put this into your .gitconfig under 'aliases':
inject = "!f() { set -e; HASH=`git show $1 --pretty=format:\"%H\" -q`; shift; git commit -m \"fixup! $HASH\" $*; [ -n \"$(git diff-files)\" ] && git stash && DIRTY=1; git rebase $HASH^^ -i --autosquash; [ -n \"$DIRTY\" ] && git stash pop;}; f"
37 comments
[ 3.8 ms ] story [ 96.9 ms ] threadAlso, why mix the old style
and expansion.The other comments are 100% correct.
The variables can already exist in the environment. One of them is tested by a statement that is reachable without that variable being initialized in that script.
A fix that keeps the script POSIX would be to initialize the variable.
It wasn't immediately obvious to me that you can also re-order patches using `rebase -i`. I've used for squashing and dropping temporary code before but realizing it can also reorder commits was really useful.
In fact, you can add arbitrary commits to the list, not only re-order the existing ones!
I sometimes do this:
Then we edit the arbitrary-picks file so that it has the interactive rebase syntax: i.e. putting the word "pick" in front of every line. Then: In the editor, we delete the "noop" line and read in the arbitrary-picks: Wee, now we are picking all those commits into this branch, with the interactive rebase workflow.It was a neat trick and when it was done all the commits were nicely sorted into feature branches, as if we'd been doing that all along.
The point of the alias is to make it all more user-friendly and less error-prone.
But, be aware that this doesn't work to inject into the initial commit.
I hope you don't mind that I streamlined your original code a bit to make it more readable. This can be put directly into the .bashrc, .zshrc, etc:
If a commit is appended to a tree and nobody has checked it out yet, does it really exist?
I introduced the convention of preserving the previous version of the branch as "<branch-name>.1", and the previous-previous as "<branch-name>.2" similar to rotating logs.
You obviously don't want to be doing this on a repo with thousands of downstreams. The point is that "private" can have a somewhat larger scope than "just my single local repo".
https://www.youtube.com/watch?v=4OlDm3akbqg
When the code is tested and ready I just reset the index but leave the working directory, then commit different hunks to make coherent commits.
During reviews we use fixup commits so it doesn't blast the comment history on github, then the final merge does a rebase -i --autosquash.
hg commit # "stash" away whatever you had lying around
hg update <rev of older commit>
# make your changes
hg amend
hg evolve -a
http://procode.org/stgit/
(That page is actually out of date; it's actually somewhat better maintained than it appears there.) It seems to have fallen out of favour somewhat as Git's own native porcelain has improved over the years, but I know there are still some kernel developers using (and maintaining) it.
Personally, I find it allows me to do things trivially and routinely that others find difficult or impossible. I still prefer it even though I know how to accomplish the same thing using native Git commands (like rebase -i).
One of the things I like most is the lack of global (as opposed to per-branch) state of the kind that Git rebase relies on. So if I want to go switch to another branch in the middle of a rebase, I can. If I have unapplied patches that I haven't deleted, they'll still be there in a month or a year. I never have to worry about whether I am in Git's weird rebase mode or not, or decide whether I have to use "git commit --amend" or "git rebase --continue" (it depends on whether you specifically asked to edit the patch or there was an error applying it, and if you forget and guess badly Git does the Wrong Thing) because the commands are always the same, and the list of applied and unapplied patches is always right there to see.