Well, one thing is that I obviously mainly work on a relatively large
project, and one that has been carefully de-centralized over a long long
time, so the source code I work with - the kernel - may be more amenable
to my workflow than most.
For example, we have long long since tried to avoid having central files
that everybody changes - because it's such a pain to manage, even with
good automated merging (and even more with central people still using just
series of patches).
In other words, in well-maintained larger projects, you simply don't see
those kinds of conflicts very often: people don't work on the same files
very much. I regularly go for days, and easily merging hundreds of
thousands of lines of changes, with a dirty tree, and the merges don't
affect it at all.
And if I happen to hit a dirty file, the pull will just say "cannot
merge", and I can stash away my changes, and just re-do. So the cost of a
conflict in a dirty tree is very low when it *does* happen.
Many people seem to enjoy per-hunk commits, but I seldom do that. Maybe
it's just because I'm *so* comfortable with diffs, that when I clean up an
ugly sequence of commits, what I do is literally:
- I make sure that my ugly sequence of commits is on some temporary
branch, but that the _end_result_ is good and clean (ie I will have
tested the end result fairly well, and made sure that there are no
debug statements etc crud left).
I would call this branch something like "target", because the end
result of that branch is what I'm looking for - even if the commits in
the sequence that gets me there are individually ugly!
- I just switch back to my starting point (and now I'm usually on
"master"), and do
git diff -R target > diff
to create a diff of my current tree (which is initially the starting
point) to the good result.
- I actually edit the "diff" file by hand, and edit it down to the part I
actually want to commit as the first in the series. And then I just do
a "git-apply diff" to actually apply that part to my working tree.
- I then edit any missing parts in the actual working tree (for example,
if there were mixed hunks that I want to get to in later commits, and I
edited out above, or that I need to partially undo), to do any
finishing touches.
- I now have a tree I can compile and test, and has the "first part" of
the journey towards the final "target" state. If compiling/testing
shows that I missed something, I can still fix things, and/or go back
to doing another "git diff -R target" to see if I missed something).
- I commit that first case, and repeat the sequence from step 2 (and
at every step, the "diff" file ends up shrinking and shrinking).
The above sounds like it's a complicated sequence, but it really isn't.
Partly because I just am very comfortable with diffs indeed (probably more
than most people), but partly because at all times "git diff" works fine
to see what I've done, and what the diff to "target" is.
And unlike the "simpler" model of committing individual hunks with "git
add -i" or something like that, my model is actually much superior! It
means that I can actually test each stage individually, and make sure that
the intermediate commits are good. It also allows me to edit up places
where the diff mixes up two different things, and the intermediate result
needs to be different from the final one.
Do I do this very often? No. Most of the time, the changes are separate
enough that I can just commit one file at a time, and in fact, I can mix
and match (ie I can do the above thing in the "big picture", but actually
end up doing one substep where I do just one "diff and edit" phase, but
then actually commit that as two things by just committing individual
files separately when they are obviously independent changes).
But the above is literally what I did for the superproject support and for
some other things where I want to send out the end result in a nice
sequence of 5-6 patches, but when I was actually *developing* it I ended
up making more mistakes, and I started out with 10 patches with some total
braino's that I had to fix, or cleanups that I didn't do in the right
sequence.
And I actually mix-and-match other ways of working too. For example, if
some commit in my otherwise ugly "target" sequence was fine, I'll just
cherry-pick it instead, and re-order things that way.
The point of this all is that the "git way" is actually very flexible. You
can keep the tree dirty and not worry about it, and if you always think
twice before you do "git commit -a" you won't be committing dirty state
that you didn't intend to commit by mistake.
Of course, if you get so used to doing "git commit -a" that you just do it
in your sleep, then the dirty tree model won't work for you, because
you'll simply start committing stuff you didn't intend to commit when
you're on auto-pilot. But the way I work, I basically always do
git diff
to see what's in my tree, and I will only use the "-a" flag when I
*consciously* think "ok, that's all one thing".
Btw, what goes hand-in-hand with this workflow is the nice ability to
specify a subtree. So I'll have a dirty tree with two different
test-things, but since one of them was a filesystem fix, and the other one
was in the kernel, rather than give all the paths explicitly, I'd do
git commit fs/
and it will automatically do the right thing (actually, I often end up
using the two-stage "git add" + "git commit" thing, because one of the
more common cases for me is that I'm going to commit a merge that I fixed
up a conflict in, and then you have to do it that way).
Linus
-
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html