124 karma · joined November 10, 2009
I didn't mention any of this in the talk (it would've killed the flow ;). Instead, I glossed over it and interpreted the syntax as any sane programmer would.
But, most of us don't bother copying before rebasing. The reflog makes that pointless from a safety perspective and, without the safety need, there's not much reason to copy before rebasing.
An early draft of this post talked about the relationship between rebase and bisect, but I cut it out to focus on one topic. The tl;dr is that heavy merging makes it harder to reason about bisect (and, of course the history in general).
I think that it would be pretty crazy to import simplejson on the Python side, write extra code to dump the data to JSON, look up how to do it in Ruby (because I don't know off the top of my head), and add the code to load it from JSON. That effort would not change the outcome by a single bit. For other theoretical data sets, it would, but this is not other data sets.
If this code were ever going to be used again, sure, I'd transfer it in a safe way. But this is the equivalent of `grep`ing and `cut`ing at the Unix shell – the goal is to get data from point A to point B, never to be revisited. I reject the idea that, in this particular case, extra effort to serialize and deserialize JSON data is superior to adding five characters of str().
With respect to history modification:
First, I rebase a couple dozen times per day. I'm on a team that doesn't use merges unless we have a reason to (this makes it easier to bisect and think about history). I also create, destroy, and rebase many of my own branches every day.
Second, I amend commits a lot. I'll often spike some little piece of code I don't understand, then start amending the commit as I rewrite it with TDD, until the commit no longer contains any traces of the original spiked version. For more complex spikes and TDD rewrites, I'll do it over many commits, rebasing the spike over the rewritten version until the spike commit is empty and gets skipped by the rebase. Doing that in Mercurial would be... arduous. I can easily do multiple history rewrites per minute while doing this.
Third, I amend commit messages a lot, usually with "git rebase -i". Maybe I forgot the ticket number, or maybe the meaning of the commit changed (see the next point).
Fourth, I sometimes do drastic commit rearranging. This is harder to explain, but it usually involves splitting commits (in the simple case) or moving sets of related changes from one commit to another (in the complex case). These are sometimes at the file level, sometimes at the hunk level, and sometimes within a hunk. This is rarer than the others; I probably do it once or twice per week.
Fifth, I "git reset <ref>" a lot. It took me longer to start doing this, but it's useful in a lot of situations. For example, "oops, I accidentally created a merge bubble."
""" I'm sorry for recommending software with a confusing interface. But you'll be spending a lot of time with it; it's worth getting over the initial hurdle of confusion. """
Note that I call it the "initial hurdle". I know both systems very well, and Mercurial gets in my way far, far more than Git.
The fact that so many people consider "hg strip" an advanced command is part of the problem. Modifying history should not be considered advanced. Being able to recover from ANY command, including destructive ones, should not be considered optional.
A DVCS that is incapable of rewriting history is, for me, a nonstarter, so I'm not interested in talking about "well, if you turn off the interesting parts of Mercurial, it's perfectly safe!"
You can either use Mercurial in a mode where it's not at all equivalent to Git, but safe (extensions off), or you can use it in a mode where it's somewhat equivalent to Git, but might lose your data.
With Git, I can mutate history without the slightest bit of fear, often just to see what will happen. Does this giant branch I discarded two months ago rebase cleanly over master? Try it. If the rebase completes but I don't like it, I "git reset mybranch@{1}" and everything is good. And that safety net is always there.
In Mercurial, I have to stop and think every time. Even if the command does dump a bundle, restoring from it is a special case, and I'm going to have to go dig up the bundle file, and in some cases it might not be there and I'll be screwed. In Git, there are no special cases, I always know exactly how to recover, and the recovery mechanism is always there.
Once more, to be clear: In Git, it is always there. Always there!
To respond to your example, I'd almost certainly go with something like your Count_Xs_in_Row path, or some similar subset of the problem. I'd probably first drive out the "win" concept by saying "if there's a row full of Xs, X wins; if a row full of Os, O wins." I don't actually need to be able to count rows for that; I can pass something fake in (probably a stub). Once I'd done that, I'd drive out the row counting, possibly backporting that into the "win" tests, but probably leaving them totally isolated.
In this example, the key insight is that the concept of winning (a full row) can be independent of the process of counting rows. The tests would push you toward that separation naturally, because it would be too hard to think about testing the "X wins" directly based upon a full board.
This is still a bit overwrought, as it's still a toy example. Toy examples make TDD sound silly, which is a big part of its perception problem. I still don't know how to fix that other than pairing with a person and/or talking directly to them in person for a couple hours. :(
[1] http://www.reddit.com/r/programming/comments/a2yhe/im_not_su...
I've said this several times across several forums since publishing this post, but my goal was to write a very brief example of "doing the simplest thing". It's not about doing the entire TDD process on a real-life software problem, because that post would be so long that no one would read it! :) I don't think TDD can't be adequately demystified in any short example.