
Show HN: 'git inject' – amend commits other than HEAD - koreno
&#x27;git inject&#x27; is a git alias (see code at the bottom). It is similar to &#x27;git
commit --amend&#x27;, but it allows you to &#x27;inject&#x27; your changes into commits further back in history (using &#x27;rebase&#x27;).<p>If you&#x27;re as pedantic as I am about the git history you&#x27;re about to push into master (as far as I can control it, I strive to keep each commit conceptually coherent), you&#x27;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 &#x27;fixup&#x27;, save, exit, and then unstash whatever else you had lying around. Not fun.<p>Here&#x27;s how &#x27;git inject&#x27; makes it fun:<p>&gt;&gt; git inject &lt;commit-ref&gt; &lt;patch-ref&gt;<p>&gt;&gt; git inject HEAD^^ -a      # inject all work-dir modifications<p>&gt;&gt; git inject a28kd8 -p      # interactively select patches to inject<p>&gt;&gt; git inject HEAD~4 file1   # inject the modifications in file1<p>Just put this into your .gitconfig under &#x27;aliases&#x27;:<p>inject = &quot;!f() { set -e; HASH=`git show $1 --pretty=format:\&quot;%H\&quot; -q`; shift; git commit -m \&quot;fixup! $HASH\&quot; $*; [ -n \&quot;$(git diff-files)\&quot; ] &amp;&amp; git stash &amp;&amp; DIRTY=1; git rebase $HASH^^ -i --autosquash; [ -n \&quot;$DIRTY\&quot; ] &amp;&amp; git stash pop;}; f&quot;
======
exDM69
This can be easily solved without aliases by adding a new commit on top, then
using `git rebase -i` to re-order and squash the commit on top of another one.

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.

~~~
kazinator
git rebase is just a mechanized cherry picker. (Agricultural implement,
really, when you think about it.)

In fact, you can add arbitrary commits to the list, not only re-order the
existing ones!

I sometimes do this:

    
    
        $ git log --reverse --oneline here..there > arbitrary-picks
    

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:

    
    
        $ git rebase -i HEAD   # noop!
    

In the editor, we delete the "noop" line and read in the arbitrary-picks:

    
    
        :r arbitrary-picks
    

Wee, now we are picking all those commits into this branch, with the
interactive rebase workflow.

~~~
__david__
Even cooler is the "exec" directive. I converted a darcs repo to git and then
used rebase to separate the huge mass of commits in the dev repo into their
own branches. It went something like this:

    
    
        exec git branch new_master
        exec git checkout -b branch-a
        pick a
        pick b
        pick c
        exec git checkout new_master
        pick d
        exec git checkout -b branch-b
        pick e
        exec git checkout new_master
        exec git checkout -b branch-c
        pick f
        ...
        exec git checkout new_master
    

The only trick was that you're doing stuff behind rebase's back, so whatever
branch you end the script on gets `git reset` to the last pick. So you have to
remember to switch back to master or end on a dummy branch that you can
delete. Also, since the branch you are rebasing is not changed until the end,
you can't rely on its name (since at the start it's pointing to the current
state of things, not the new state), so you have to create your own branch if
you want to keep popping back and forth.

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.

------
zb
If you find that this is a regular part of your workflow, then you should
consider trying Stacked Git:

[http://procode.org/stgit/](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.

------
xenadu02
Eh most of the time I just commit blindly as I'm working (WIP #1, #2, etc) so
I can see how the code has evolved or revert to an earlier approach if I end
up in a blind alley.

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.

------
dr4g0n
It's not exactly hard to do by hand:

    
    
        git add ...
        git commit --fixup $SHA
        git stash
        git rebase -i --autosquash $SHA~
        git stash pop

~~~
johnkeeping
Or simply:

    
    
        git add ...
        git commit --fixup $SHA
        git rebase -i --autosquash --autostash $SHA~

~~~
koreno
It gets more complicated if you have some other modified content that you
wanna leave untouched. You then need to stash it or commit it separately.

The point of the alias is to make it all more user-friendly and less error-
prone.

~~~
johnkeeping
That's what the `--autostash` is for. It also has the advantage that the stash
will be restored at the end of the rebase even if it hits problems and needs
to be continued or aborted.

------
kazinator
Do not expand arguments with unquoted

    
    
      $*
    

Use:

    
    
      "$@"
    

You probably want

    
    
      local HASH=...
    

and

    
    
      local DIRTY=
    

in there.

Also, why mix the old style

    
    
       `command ...`
    

and

    
    
       $(command ...)
    

expansion.

~~~
johnkeeping
`local` is a bash-ism so it's best avoided in this context, and the command is
running in its own shell anyway so the variables can't pollute anything else.

The other comments are 100% correct.

~~~
kazinator
> _so the variables can 't pollute anything else_

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.

------
piinbinary
I have a similar function that's a bit simpler:

    
    
        gfixup () {
            REVISION=$(git rev-parse $1) 
            git commit --fixup=$REVISION && git rebase -i --autosquash $REVISION~
        }
    

It doesn't handle stashing anything, but other than that it's basically the
same.

~~~
michaelmior
Wouldn't the stash be easily handled with --autostash?

------
pstadler
This is neat. Let me add that I use `git commit --amend` relatively often.
Useful not only for adding "forgotten" files and changes, but also for editing
the commit message.

~~~
exDM69
Add --reset-author to update the timestamp to avoid getting confused with two
versions of the "same" commit. It also updates the author information, which
may or may not be what you want.

------
krupan
With the mercurial evolve extension this is simply:

hg commit # "stash" away whatever you had lying around

hg update <rev of older commit>

# make your changes

hg amend

hg evolve -a

------
jasonlotito
Thanks! I'd use this. Usually for updating previous commits that are up in
Gerrit.

------
mdemare
Does this work when using the fish shell, which is not bash compatible?

~~~
davidcelis
git aliases aren't run in fish-shell, even if you've set that to be your
default shell.

------
evandrix
you mean under `[alias]`, rather than 'aliases'

------
milkworsethan
Imo if you were as pedantic about the history you wouldn't be tampering with
it.

~~~
artursapek
There's no issue if you haven't pushed to any remotes (origin) yet. I often
wish I could amend a few commits back before pushing.

~~~
bpicolo
Then do a commit and then use rebase interactive (-i) and squash them
together.

~~~
artursapek
That's what I would do, and that's what "git inject" seems to be after.

