This one is what I call a “double-disclaimer”; a post so iffy that I need to stick not one but two disclaimers in front of it.
First of all, I’m glad people are making FOSS version control for the non-technical audience. Thank you for that. And if that, through your usability studies and such means that for that crowd, the “staging area” a.k.a. the “index” is just not worth its weight in confusion for them, that’s fine. Get rid of it.
Staging is a client side UI affordance, not something that really needs too be part of the wire format or the remote repo format. If I’m collaborating with Alice and Bob on the other side of the world and one of them is not using staging at all and the other is carefully staging everything, I wouldn’t be able to tell the difference. It’s one of those things that doesn’t hurt anyone else. Even though I sometimes think vim users should be required to wear bells to warn others of their presence, when all is said and done, they can receive & read text files and they can write & send text files, so they’re only hurting themselves. Or, if these UI studies are right, it’s us who are using staging who are only hurting ourselves.
Second disclaimer: I know that some of the non-staging tools like
jujutsu and gitless do have ways to do some of the cool things that
staging can do, like the jj split
command in jujutsu, and got even
has an optional index; if you run got stage you can stage things,
otherwise commits commit the entire thing.
So awesome is Magit and its staging area that I sometimes put a text file under git that I normally would never use VC for, just so I can revert hunks from commits and revert hunks from the staging area, too.
It sometimes feels like this wonderful time machine; I still use the weird default Emacs “undo chain” but that hardly matters with all the glorious powers of Magic available!
Jujutsu describes git’s staging area this way:
The index is very similar to an intermediate commit between HEAD and the working copy
That’s right, and that’s why jujutsu doesn’t need it, and that’s awesome. For them. But Magit leverages this feature of being almost-a-commit wonderfully because then I can revert hunks without ever having to have committed them! I can put all kinds of junk in my files and only commit the good things.
Now, in the grim darkness of the far future someone might come up with an Emacs interface for Pijul or for Jujutsu that is as awesome as Magit is, and when that happens, this essay is gonna look pretty wack. But it hasn’t happened yet.
Jacek writes in, saying:
Staging is great.
Magit is absolutely awesome.
Staging in magit is the greatest!
Magit made git tolerable for me, and then it made all other VCSes intolerable, because none have an interface anywhere near as good as magit.
We seem to agree. However, I can’t help feeling that your staging-is-great post misses a very important point. Magit prevented me from using jj for far too long, primarily because I hadn’t realized that I could still stage in Magit even when using jj. It would be a shame if magit did you the same disservice, because interacting with git via jj is as much of a boon as interacting with git via magit, but many of the benefits are orthogonal, so adding jj to your git-taming toolbox is very useful even if you already use magit. Without actively using them for a while, it is hard to imagine how much each of these tools improves git and how much they change your perception of what is possible. The testimonials sound too good to be true. They are not. You probably understand this in the case of magit. The same is true for jj.
In your post you state
Jujutsu describes git’s staging area this way:
The index is very similar to an intermediate commit between HEAD and the working copy
That’s right, and that’s why jujutsu doesn’t need it, and that’s awesome. For them. But Magit leverages this feature of being almost-a-commit wonderfully because then I can revert hunks without ever having to have committed them! I can put all kinds of junk in my files and only commit the good things.
which suggests to me that you have missed the point, and formed an unfounded negative opinion about jj.
What git calls staging is the redistribution of differences between three trees. Let’s call them 1, 2 and 3. Staging is the process of partitioning the set of differences between trees 1 and 3 into the sets of differences between 1-and-2 and 2-and-3. To keep it general, let’s call it partitioning.
How those three trees are represented is an implementation detail of any given tool used for partitioning.
In git, these three trees are called HEAD, Index and Working Tree. What they are called in jj depends on which workflow you are using and which exact three commits are involved. For the sake of brevity let’s make one choice: @–, @- and @.
In git, HEAD is stored as a commit; Index is stored as, well, I’m not entirely sure and I don’t really care and I shouldn’t need to care; the working tree is not stored in git at all, but is simply whatever happens to be lying around in the filesystem.
In jj, all three trees are stored as commits. This regularity offers many advantages. One of them being that any staging tool capable of partitioning the diffs between @–, @- and @, could be used just as easily to partition the diffs between any three trees known to jj. Representing tree 3 as a commit rather than whatever is lying around in the filesystem, allows jj to operate on it much more efficiently than git does, by being able to do so entirely via data stored inside its internal database.
The last few of your words I quoted above, “only commit the good things”, suggest that you see tree 3 being represented as a commit, as something permanent and therefore bad. This is a shame, because it prevents you from seeing that jj’s consistent representation is an excellent feature. Yes, jj includes snapshots of the working tree in its internal database. But none of the junk you put in your files ever makes it into a commit graph that you would share with anyone else, unless and until you choose to copy junk from @ into @-, a.k.a. stage it.
Don’t want junk in your jj history? Then don’t copy it from @ to @-, just like you wouldn’t copy it from the working tree to the index in git. To frame it in your words: a jj snapshot commit is almost-a-commit.
Jj’s choice to represent all three trees consistently is a strength, while magit’s tight coupling to git’s ad-hoc and inconsistent representation of the three trees is a shortcoming.
We shouldn’t complain about jj’s consistent representation of the three trees involved in partitioning; we should lament that magit’s peerless staging tool is tied to git’s idiosyncratic and suboptimal representation of those trees, and therefore only applicable to staging-in-git and not partitioning in general.
Nevertheless, I can use magit to stage in jj!
Jj provides extra conveniences for manipulating a perfectly normal git commit graph, which doesn’t prevent anyone from also manipulating the same graph in git, wherever convenient. When it comes to staging, Magit is still the most convenient. I haven’t yet found a way to use magit directly as the diff-partitioning tool in all situations where it is called for in jj. But that is magit’s shortcoming (inspired by git’s inconsistency), not jj’s.
I look forward to diving in to using magit in combination with jj!
Me too, it’s a very recent realization :-)
It’s very obvious with hindsight, but less so before the epiphany, because all the tools jj proposes only really show you one of the two sub-diffs, so it doesn’t seem to fit.
Also, my claim that the three trees in jj are @–, @- and @ is not quite right, but the full truth is much more verbose without adding much to the conversation. I hope the un-truth doesn’t cause too much distraction.
When using magit to stage in jj, the most obvious glitch is that, if jj snapshots the working tree before you stage in git, you’re left with a non-empty branched-off snapshot commit that you’ll need to abandon manually. A price I’m happy to pay for this service.