I think the root of the problem is that git (and version control generally) forces you to think in terms of sequences of changes, but a changeset or feature-branch or whatever you call a cohesive group of related changes is in general not logically sequential. The order in which the code was written is incidental and not necessarily related to the order in which it should be read.
I don't have a solution to that, but I have written a Perl script to help automate the process of rewriting history. It's similar to the Python script of @MikaEloranta which I hadn't seen when I wrote it.
commit --fixup
and rebase --autosquash
are great, but they don't do enough. When I have a sequence of commits A-B-C
and I write some more changes in my working tree which belong in one or more of those existing commits, I have to manually look at the history, decide which changes belong in which commits, stage them and create the fixup!
commits. But git already has access to enough information to be able to do all that for me.
For each hunk in git diff
the script uses git blame
to find the commit that last touched the relevant lines, and calls git commit --fixup
to write the appropriate fixup!
commits, essentially doing the same thing I was doing manually before.
If the script can't resolve a hunk to a single, unambiguous commit, it will report it as a failed hunk and you'll have to fall back to the manual approach for that one. If you changed a line twice in two separate commits, the script will resolve a change on that line to the most recent of those commits, which might not always be the correct resolution. IMHO in a "normal form" feature branch you shouldn't be changing a line twice in two different commits, each commit should be presenting the final version of the lines that it touches, to help the reviewer(s). However, it can happen in a bugfix branch, to contrive an example the line foo(bar());
could be touched by commit A (rename foo
to fox
) and commit B (rename bar
to baz
).
If you find the script useful, please feel free to improve and iterate on it and maybe one day we'll get such a feature in git
proper. I'd love to see a tool that can understand how a merge conflict should be resolved when it has been introduced by an interactive rebase.