6 ms·
> It says it abstracts the backend, but it's not clear how something so git-influenced will have abstractions that work with something like a centralized system
> It says it abstracts the backend, but it's not clear how something so git-influenced will have abstractions that work with something like a centralized system like Perforce or Piper that has auto-increment numeric commits.
Its Piper backend honestly works better than the Git backend. Which isn't a knock on the Git backend, but the impedance mismatch is worse there.
> Some of the design decisions are also not great. Working copy as commit means you have no quality control. The whole point of a commit is that... you commit it. So now you need a separate repo or filter to remove the worthless changes from the ones that you actually intend to commit. Bad commits polluting git histories is already a big problem.
Sorry, but saying this implies you haven't tried it. :-)
Jujutsu commits aren't equivalent to Git commits. They're implemented with a mixture of Git commits and the Git working tree, yes (if you use the Git backend!), but when you see 'commit', you should read 'named diff'.
Jujutsu also has a notion of immutable commits, by default meaning (roughly) commits which have been pushed upstream.
You can and should rewrite the un-pushed commits to clean up history prior to pushing changes upstream. jj makes that much MUCH easier than rebases and history edits could ever be with git. Most of my nontrivial jj work involves at least three or four commits at some point, everything from experiments to documentation branches, which with git I would have needed to awkwardly fit into stash or inconvenient throwaway branches.
Thanks, can you link me to their perforce backend code? I don't see the string "perforce" or "p4" anywhere in the code and it's not coming up on search.
> You can and should rewrite the un-pushed commits to clean up history prior to pushing changes upstream.
My concern isn't just about pushing upstream. What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready. The FAQ suggests this isn't possible and that you should work around it by using a separate branch and then merge into your target branch, which is a pretty ugly workflow.
Their GitHub branches are a mess of auto-generated strings, which suggests to me that this problem isn't just an abstract concern but is a form of technical debt that the jj devs are currently piling up.
> What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready.
Yes, it's "committed", but that doesn't mean all that much. I have the fsmonitor feature enabled that continually watches my repos as I edit files in them. This means that over the course of a coding session, the revision I'm working on has probably pointed at dozens or more ephemeral underlying git commits. Those are only kept around for the purpose of the evolution log, which lets me look back at my edit history throughout the day even without having interacted with the repo.
When you're ready to "finalize" your work, you have two common workflow options: you can `split` the out parts you want to keep as one consistent commit, which dumps the rest in a subsequent revision. Spiritually this is equivalent to `git add -p`. Alternatively, you can create an empty "staging" revision before your "working copy" revision and `jj squash -i` pieces you're happy with into there. In practice, many people use both. I generally do the former for new work, and the latter to make changes to earlier work.
Thinking that `git add -p` was absolutely a deal-breaker is the main reason I passed over jj for so long. I care deeply about maintaining a clean commit history with small, isolated, and individually-tested changes. I thought jj would make that harder due to not having a staging area. I was wrong. It is actually far easier to be principled about your commit history with the tools that jj gives you.
I don't mind the workflow of selecting what you want at a later time, but my concern is with having changes visible to the local repo database automatically.
If you like it that way, that's cool with me.
But to me personally I'd rather have the filesystem doing snapshots and not comingle fs snapshots with VCS. I can nuke an fs snapshot in a single line of bash if I accidentally leak something, and fs snapshots don't contribute to the slowness of a big repo. But those are problems in regular git and would be even bigger problems in a version of git that treated VCS as if it were an fs snapshot system.
But like I said if that works for other people that's great. It would probably work for me if I were a consultant working on a lot of small repos. I don't think it scales to a bigger repo unless the jujutsu repos are completely destroyed every few days, which is essentially what happens to CITC client working copies at Google.
> Jujutsu also has a notion of immutable commits, by default meaning (roughly) commits which have been pushed upstream.
This makes sense to me as a longtime Mercurial user. In short, by default, when you push a commit, it becomes public and therefore immutable [1].
Other awesome Mercurial features that appear in JJ are revsets [2], filesets [3] and templates [4]. Looking forward to giving JJ a try.
[1]: https://wiki.mercurial-scm.org/ChangesetEvolution https://wiki.mercurial-scm.org/ChangesetEvolution
[2]: https://jj-vcs.github.io/jj/latest/revsets/ https://jj-vcs.github.io/jj/latest/revsets/
[3]: https://jj-vcs.github.io/jj/latest/filesets/ https://jj-vcs.github.io/jj/latest/filesets/
[4]: https://jj-vcs.github.io/jj/latest/templates/ https://jj-vcs.github.io/jj/latest/templates/
> My concern isn't just about pushing upstream. What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready
Since you brought this up, I've noticed some people seem to work this way but I've never found anyone to ask why they do this.
I get the idea behind clean, readable git logs with nice consistent messages, but isn't that what rebase/amend is for?
To me, the status of my local git log is mostly irrelevant, the thing that really matters is the commit(s) I submit for merging.
Also, probably for related reasons, I've never truly understood why git has this whole separate staging concept...
I also work this way. A commit is really about, well, committing that these are exactly the right lines of code I intended to write. I also read them in another program, so it gets easier to not think that I already know the code and skip over. I basically start from the clean state (no changes) again and approve the changes line-by-line according to whether they fit the domain model of the program, whether they are in the right places, are the right amount of complexity/abstraction, if they are readable/understandable. I typically also add/remove comments at that point, because I've now tried to read the code with a "second pair of eyes".
I also use this to compare the changes to the commit messages. Ideally the changes follow from the message, not the other way around.
While a VCS is also useful for adding a time-axis to code, for me the main selling point is, that it's adding causality to the code. You can ask 'Why is this code how it is?'. (Of course causality is a side-effect of time.) When you don't curate the commits you completely loose this feature. (You can still ask, but the answers will be confusing and need way more work, which for me amounts to manually comparing commits to grasp the big picture and intention.)
> rebase/amend
When you messed up, you can of course change commits, but this has a danger of creating a version that was never there or mixing history, for example combining a single physical change, that are multiple logical changes. In order to test that the code at least works I use ```git rebase --exec="make -C build distcheck"```, not sure if that is common.
I also don't use MS GitHub -style merges, I find them inferior. (I also don't use MS GitHub, but that's for another reason.) I think they try to make merges the actual unit of change, which is stupid, because that is what a commit is for. To me merges are about semantic grouping of commits, i.e. maintaining a tree of commits representing the logical evolution of the code.
Yes the staging area is exactly designed for this approach and I don't like people telling me I'm holding it wrong and don't need it. To use a metaphor, I think the staging area is like my desk were I'm producing stuff (I'm not producing code, I'm producing changes). Committing is about having produced a complete opus and then cleaning the desk. Cleaning isn't seen as annoying, it is important for the mind to process completing/validating and then starting afresh. Stashing is in this metaphor about switching to an alternate desk. This is different from putting the opus aside and having also a clean desk. (I think that is what Jujutsu wants you to do?) Having the staging-area/stash different from commits is a feature, the fact that the stash is the same storage-wise is an implementation detail.
By default, the top (current) commit acts like a staging area and in fact is implemented as a dirty working tree in git.
Commits start live without a description, and can’t be pushed without adding one. jj commit names the current commit and creates a new empty, unnamed commit on top, which effectively is precisely git’s behaviour.
I implore you to try it. In practice the distinction just doesn’t matter, except it’s one less concept to keep track of, and jj allows you to make up other workflows if you want.
Sure, sometimes I want to work with a WIP commit and then I'm just doing that in git.
I think functionally of the stage is about the UI, so keeping it separate is kind of the point. I find that useful, as I can use both approaches, not sure how its better if jj can just use one?
> to ask why they do this.
This was what I was trying to explain. To me the feature is needing to manually mark every line to be committed. If I need to split the commit I'm already not paying that much attention.
>A commit is really about, well, committing that these are exactly the right lines of code I intended to write.
That would be a ‘change’ in jj. A commit in jj is more like SQL than git; it's the end of a transaction, not a value judgement.
> What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready.
I was extremely hostile to this aspect of jj too, but I've settled on a workflow where @ (i.e. the change/commit referring to the working copy) is always the same, named ".WIP: …", and as such is clearly never supposed to break out of the local system, and can never accidentally get pushed down the log. (..my jj workflow is just blatantly copying my git workflow (with the same-name shell aliases/helpers even), but even with said git emulation I'd say it's nicer than git itself)
Still may be weird/undesired to have the local repo preserve the changes (especially annoying if it happens to find an unignored build artifact, though there is a size limit for automatic tracking by default), but it's also rather neat that you can dig out your old deleted printf debugging or whatnot later on if you wanted to.
If you leave the WIP head change blank, i.e. don't give it a description at all, then JJ will block you from pushing it unless you pass a specific flag, which is useful for sanity checking this sort of stuff.
You might be able to configure JJ to also block commits with certain descriptions as well, which might be useful in your case.
I specifically have the WIP head description include the name of bookmark that it's attached to, if any (emulating git's non-detached head), so can't leave it empty (and generally wouldn't want to, for easy way to identify it, esp. with multiple workspaces).
Some push blocker for certain commit name patterns would make sense to look into.
You indeed can: https://jj-vcs.github.io/jj/latest/config/#set-of-private-commits https://jj-vcs.github.io/jj/latest/config/#set-of-private-co...
> I also don't use MS GitHub -style merges, I find them inferior. (I also don't use MS GitHub, but that's for another reason.) I think they try to make merges the actual unit of change, which is stupid, because that is what a commit is for. To me merges are about semantic grouping of commits, i.e. maintaining a tree of commits representing the logical evolution of the code.
Thank you for going into details, you helped me understand a few more details.
Let me describe how I think about it and why it might possibly conflict with your ideas.
I think the best way to describe it is as a series of approvals, each one being more important than the previous one.
The absolute lowest level of approval, writing the file to disk. You've made some changes and you're happy enough with them to at least save them in case you have a power outage or something.
The next level of approval is committing to your local git repo. This is code you're fairly confident you want later, even if it might not be perfect yet.
The next level is pushing your branch to the origin repo. Now you're saying that this is code you're willing to let other people look at.
The last level is merging this code into main/trunk/whatever. This is the final level of approval, this code passes all of our checks, it shouldn't need any more improvement.
Given this system, adding another level in there, for staging commits, feels pretty unnecessary.
I agree that merge commits tend to make for ugly git history, but I don't think that's particularly inherent to any of these systems of code integration, it's more a function of how much the developers care about the git log.
> change commits, but this has a danger of creating a version that was never there or mixing history,
I'm not sure what the benefit here is. I think I've basically ended up in a situation where I treat 'git commit' the same way I treated "save file" 20 years ago. My local commits/saves/edits aren't important, what's meaningful is the final unit of change I'm sending to the remote, and that unit needs to be deliberately constructed somehow. Whether that involves rebasing or amending or careful usage of git stage, it's an artificial unit you're creating just as much as the actual code changes.