Jujutsu: A Git-compatible DVCS that is both simple and powerful
github.com
github.com
- `jj log` is awesome for getting an overview of all your branches. If, like me, you have a lot of work in progress at once, this provides a great map
- Conflict resolution is really cool, as you can partially resolve conflicts, and then switch branches. Conflicts are also tracked specially, but I haven't done too much with this yet.
- The abbreviated changeset ids are really handy. I often will just `jj log` (or in my case just `jj` as that's the default), notice a changeset I want to rebase, then run `jj rebase -s qr -d master`. `qr` here is an abbreviated changeset id for a branch/commit, and usually much quicker than typing the branch name out! This will probably change when clap gets updated to support dynamic tab-completion though.
Also from the documentation:
https://github.com/martinvonz/jj/blob/main/docs/git-comparis...
"As a Git power-user, you may think that you need the power of the index to commit only part of the working copy. However, Jujutsu provides commands for more directly achieving most use cases you're used to using Git's index for. For example, to create a commit from part of the changes in the working copy, you might be used to using git add -p; git commit. With Jujutsu, you'd instead use jj split to split the working-copy commit into two commits. To add more changes into the parent commit, which you might normally use git add -p; git commit --amend for, you can instead use jj squash -i to choose which changes to move into the parent commit."
We’re on an old school feature branch/master is production topology though.
In git you would always leave the changes unstage, does that mean with jj you would always have to remove them before pushing? I haven’t found an answer on the linked page.
Side note: I really wish git had a way to mark commit has ‘no-push’ so they never leave your local copy, as an upgrade of the unstaged workflow.
I do this too, but I quite frequently forget that I've done it and "git commit -am" and end up pushing my private changes anyway.
i have these 2 aliases assume = update-index --skip-worktree unassume = update-index --no-skip-worktree
"assume" as in "assume it's unchanged / not wanted"
which lets me say "git assume path/to/file" and then "unassume" it when/if i want to commit it.
It's been a while since I worked at a place doing this, and I'm on my phone, so some details may be fuzzy or wrong, but when I was figuring all this out, I remember this SO comment being really helpful:
https://stackoverflow.com/a/23806990
Basically, there's two different APIs, and neither of them are designed for ignoring config files (but both of them happen to do things that look like ignoring config files, therefore get misused).
`assume-unchanged` is an optimisation tool that tells git that it shouldn't both checking files and folders that are expensive to check but never changed. If changes do ever happen, and git realises this, then git will remove the assume-unchanged flag - because clearly it wasn't true!
`skip-worktree` is a more aggressive version of this that tells git never to touch certain files or folders at all. But now it changes do happen to those files, it's not clear what git should do. Overwriting the files would cause data loss, but ignoring the files completely means that you miss out on upstream changes. And because you're telling git that these files should never be checked, there's no good way to handle merging and conflicts.
What typically happens with both of these flags is that they work well about 80% of the time, and then cause a lot of confusion that last 20% of the time.
The alternative is almost always some sort of configuration file that is directly referenced in .gitignore, and that therefore never gets checked in. (In addition, it's often useful to have a (e.g.) config.json.default file for getting new developers up and running quickly.) Any system that needs to be configured (database connections, API URLs, IP addresses, etc) should use this configuration file. Alternatively, I find environment variables, along with .env files for local development, to be really effective, because most things can already be configured that way.
This typically takes sightly longer to set up the first time, but will work very reliably from then on.
See also these changes in git's official documentation, and the commit message that explains why it was necessary:
https://github.com/git/git/commit/1b13e9032f039c8cdb1994dd09...
Git kind of has some "local tracking" already: if I am not mistaken, lightweight tags behave like this. It would be cool if it could track files in the same way.
Under the hood it could be done via a separate database, stored not in .git but in .gitl directory or some such. The database in .git would behave as if both .gitignore and .gitlocal contribute to ignoring, and .gitl as if .gitlocal was .gitignore in reverse: it would ignore anything not covered in .gitlocal (and also ignore what's in .gitignore). Or something along these lines.
There's .git/info/exclude, but that has some kinda large surprises if it excludes a tracked file and I don't recommend anyone use it unless they know what to look for and can always remember what they've excluded.
Git will fight you if you ignore .config files which are actually used.
If my local setup requires me to ignore changes in checked-in files, I usually find that I need to handle configuration more cleanly.
(I did work on a project that made use of git update-index - this was a terrible mistake and caused pain every time we needed to update the config file in the repository. Please never go down this route!)
Most places I've worked at have created tooling that more or less merges the two - sane defaults and non-sensitive values go into a `.env` file of some kind (.env, .env.development, whatever), and then a tool merges a subset of that config from a remote store which contains the stuff you don't want committed in the repo.
Usually used for connecting to a remote dev or staging instance if you can't or don't want to run the entire stack locally.
The ability to rewrite older commits easily in jj also looks like it would help with this usecase if you get it wrong once.
Concretely I think you would do is: Instead of staging part of your changes and then committing as in git, you would call jj split and split the commit into the part you want to keep local and the part you want to push. This way the local changes always stay in your working copy commit.
Even better, just commit the local changes once when you start. Work locally and before you push you call jj diffedit on your initial commit of the local changes and remove them. Now all the work you did since then will be automatically rebased on the edited initial commit and you can now push up. Instead of excluding your local edits every single time you just have to do it once before pushing.
I like seeing what I'm about to commit, so I always do `git commit -p` or `git add -i`. Most people where I work do the same, so I don't think this workflow is uncommon.
I never, ever commit -a. That flag horrifies me. I want to choose, specifically, each line of code that I am going to publish.
I'll then go back through, tidy and add all my changes.
"add -p" is great for showing you all the chunks of code you've written one-by-one and then you do "y" or "n" for whether you're adding them. Doing it like this means that you are reviewing what you've changed as a final check that you haven't left a debug line in, or forgotten part of what you meant to do. It's also a natural way of splitting up what you've done into two separate commits.
Also learning about progress git workflows though, so that's cool.
> error: unexpected argument '-i' found
Actually, maybe I'm just a complete git, but I couldn't figure out how to `git reset HEAD~` my accidental commits, `git rebase -i HEAD~6`, format `jj log` more like `git log --color --oneline --graph --full-history` (which shows one-line-per-commit), `git checkout -p` (and obviously, `git add -p`), `git show HEAD~`, refer to N-th parents, e.g. `master~5`, and a bunch of other things...
It also feels a bit weird that new files are automatically committed without being manually approved, but I suppose this might theoretically help with some of git's annoyances.
I appreciate that there are times when this has to be in the middle of an otherwise committed file, but it's worth avoiding that if at all possible and putting the locally-changed bit in a different file because, as others have pointed out, this is error prone. It feels like the equivalent of keeping your important files in the recycle bin because it's easily accessible.
For 90% of git users 90% of the time, the staging area is an inconvenience that adds an extra step.
Worktrees allow you to have multiple branches open potentially with dirty state on any or all of them
My feature request is about having n stages instead of one so uncommitted work can be organized better while staying in the usual working copy.
How would I accomplish this with worktrees?
Disclaimer : I'm not on my computer, I know a file like that exists, it may not be at this exact location
Usually these temporary changes are to config files, so rebasing shouldn’t create conflicts if you don’t otherwise modify the config file.
An alternative is to stash only those temporary changes but I find branches to be easier to work with.
For once, it avoids the downsides of carrying untracked files around. Ever accidentally committed an untracked file somewhere deep in history during a long rebase?
Also I find it clearer what files are committed: every file - except if it is in .gitignore. Meanwhile in git you scourge through your untracked files before each commit to decide which ones you don’t want to add. Ever accidentally committed all files and forgot that there were untracked files you didn’t want?
It mentions that it can be used with backends like Dropbox, but it would be wonderful if we finally had a system that could easily be used with IPFS. This is especially important for large data, since you can't store 1TB on github (and no, I don't count lfs, since you have to pay for it).
IPFS is the natural solution here, since everyone that wants to use the dataset has it locally anyway, and having thousands of sources to download from is better than just one.
So if this uses IPFS for the data repo, I'm switching immediately. If it doesn't, it's not worth looking into.
This work might be extensible to include IPFS and other distributed virtual file systems.
[0] https://docs.google.com/presentation/d/1F8j9_UOOSGUN9MvHxPZX...
1. When we ingest files or make new commits, how are these additions to the object store persisted?
2. When operations modify the working copy, how should these changes be reflected in the user's view of that working copy?
Ordinary git handles (2) by directly modifying the files on the filesystem. If you `git checkout` a branch, git will `rm` nonexistent files, `open()` and `write()` new ones, and adjust modification timestamps etc as needed. As you make changes to these files, some commands will occasionally "notice" that the file changed after the fact, and some may choose to modify the index to match.
the jj on github also does this, but inside Google, our concept of "working copy" needs to be disconnected from local files. Developers don't have their own local "working copy" backed by files on the ordinary filesystem; instead, we do all development inside a FUSE-mounted virtual FS called "client in the cloud" (CitC), so working anywhere inside our giant monorepo doesn't take any disk space (except caching). I think that's what the "Distributed file system" refers to - instead of modifying the local filesystem, jj would need to talk to whatever remote service provides the user's FUSE-backed view whenever the user uses `jj checkout` or some other jj operation that modifies the working copy.
When you speak of implementing IPFS storage, I think instead you want to keep the object store and operation log on IPFS while keeping the local working copy right on the ordinary file system, similar to how git-LFS keeps local files untouched while modifying the way they're persisted to the git object store.
Alternatively, perhaps we could imagine an IPFS backend similar to `jj git` and `jj native`, perhaps `jj ipfs push/pull`. Then, a completely local repository could push/pull to and from IPFS, completely agnostic of how the user's repository is stored on disk.
In any case, jujitsu's API surface is flexible enough to support any of these use cases since the author designed it from the ground up to smoothly support very different needs for internal and external users. Most users outside Google just want a familiar working copy containing ordinary files, and the fact that the repository structure happens to be backed by a git-like object store (linus' "git is just a merkle tree of files" philosophy) is incidental under the hood. That's just fine, even though most internal users will be interacting with a very different way of using jj when everything's said and done. Ideally, nobody needs to notice or care about the difference.
[1]: More about Google's internal VCS needs: https://cacm.acm.org/magazines/2016/7/204032-why-google-stor...
[2]: Linus Torvalds on git: “In many ways you can just see git as a filesystem — it’s content addressable, and it has a notion of versioning, but I really designed it coming at the problem from the viewpoint of a filesystem person (hey, kernels is what I do), and I actually have absolutely zero interest in creating a traditional SCM system.”
(And yeah, git's historically a poor choice for this – so you may see companies sticking with Perforce or other non-distributed solutions.)
In most projects today, the code is (or generates, anyway) the data. This is true for materials science in physics, neural networks, and creation of databases via ETL. So, it would make sense to remove the requirement of making users of some software to regenerate this data, which may take 2 months on a supercomputer. Downloading that would be much faster. You can put it on a university server, or AWS, but now the data is in some system that is not guaranteed to be there. In fact, it's almost guaranteed to *not* be there in a very short period of time (people move positions and lose their access to these servers constantly).
So the very obvious best solution is IPFS for distribution of the data, but it does need to be linked to the git repo somehow. Of course, the data may not be simple or textual and play well with simple text based diffs for version control, so using something like borg can solve the issue of both data privacy, if needed, and block based diffs.
So this isn't to suggest "just git everything", but rather to say, 'if there's a new version control system for data and code, it's probably added some improvements to fit, and this could be a direction that makes sense'.
So I was checking to see if it had gone that direction yet.
This was done with SVN projects. Keeping the blobs out of your source repos has been the preferred way for a long time.
[Edit] The only folks who seem to want to do this are game developers, and they are generally not people you would want to emulate.
> Keeping the blobs out of your source repos has been the preferred way for a long time.
This is just appeal to tradition.
It might be, but the arguement was that we don't do it because of git.
We haven't been doing it for a long time, but that's not because of git.
If the choice is "run this code in the repo, wait 10 weeks while it's running, and retrieve the 50GB file", vs "download this file", of course, the latter is better. But many of these processes exist in academia, wherein you are essentially guaranteed to lose access to the server and maintenance of that file for download, it can get pretty annoying. Additionally, there's no seamless way of distributing it (it's in the docs, point somewhere else that may or may not exist, etc).
Since essentially all big data is really just code, it would make much more sense to tie these directly at the hip. So, a git/repo commit hash that is a key directly to the IPFS data hash would fix this problem directly.
So it's not "wanting big files in a git repo" (an obvious no-no, since central servers shouldn't be used for storing large data, and github centralized repos only should store single digit MB or so), it's wanting to relieve the cost of running processes that may require supercomputers weeks of processing for QM calculations, etc by providing a guaranteed hash pairing of the output of the code.
Unfortunately, the model of putting every block in the DHT instead of having roots mode be the default, and then spamming your wantlist to tons of peers seems to still be at least partly around.
Right now IPFS looks pretty good thanks to the gateways and services, so I would imagine well see more of it in the future, but I can see why it took so long.
Jujutsu: A Git-Compatible VCS - Git Merge 2022:
Video:
Slides:
https://docs.google.com/presentation/d/1F8j9_UOOSGUN9MvHxPZX...
That's got to feel good!
Full disclosure: This is my tool
On top of this is a great set of primitives for manipulating commit history - `jj move|restore` to move|copy changes from one commit to another, `jj split` to break up commits, `jj rebase` to move commits around. _Everything_ is inside commit history (no "worktree", no "index"), so one set of commands and flags will do everything you want.
The operation log is also incredible. Most operations can be undone with zero problems. Compare that to Git, where your best hope to fix a bad rebase/commit --amend is to scrounge around the reflog for the right commits and manually fixing up your refs.
The biggest pain points for me are:
- Automatic working copy commit sometimes tries to commit things I don't want. Usually .gitignore has things covered, but sometimes moving files around and rebasing commits breaks things. Fixing up broken commits with `jj split|restore` is pretty easy though.
- No rename detection. jj doesn't handle merges as elegantly as Git. jj devs are thinking about this, but haven't quite cracked it. I'm not sure how long that will take.
I’m really glad people are trying this out. I’ve spent the last decade or so playing with collaborative editing algorithms. Ideally I’d like tools like git to eventually be replaced by CRDT based approaches. CRDTs would let us use the same tools to do pair programming. CRDTs also handle complex merges better (no self-conflicts like you can get with git). And they’re generally a more powerful model.
One problem with all modern text CRDTs (that I know of) is that they do automatic conflict-free resolution of concurrent edits. But when we collaborate offline on code, we usually want conflicts to show up and be resolved by hand. CRDTs should be able to handle that no problem - they have more information about the edit history than git, but doing this properly will (I think) require that we put the conflicts themselves into the data model for what a text file is. And I’m not sure how that should all work with modern text editors!
Anyway, it sounds like jj has figured out the same trick. I’m excited to see how well it works in practice. With this we’re one step closer to my dream of having a crdt based code repository!
I'd be interested to see how this plays out in practice.
It seems to be in conflict with the idea that scm history is a meaningful deliverable that should be arranged as series of incremental atomic changes before a patch series leaves your development machine.
However, most developers I interact with already treat git history as an infinite editor undo history, this approach seems like it would crystalize that fact.
How do you envision the (long-term) history working? Do you think it would provide more/less utility?
1. An audit log of what actually happened, and when
2. A curated story of features added and changed
I don’t think git can be both of those things at the same time already. And we see this tension play out when people argue about squishing commits and rebasing before merging.
Personally I think both pieces of information have value. And they should be managed separately - as different features. Eg Git could (and perhaps should) have a second semantic layer which marks a set of commits (atomic history of changes) as semantically associated with a particular feature or issue. This is how I imagine a crdt based scm working: the history of all changes is stored immutably, and synchronised. And change sets can be grouped and marked as “this set of changes is associated with feature XXX”, to make it easier to understand the intent behind code, and roll back specific change sets.
It does (sort of)! They're called branches and tags.
If you have a bug which is reproducible, but whose cause is complex, do you not think it's useful to be able to find the commit that introduced the bug in order to see which change caused it? If only to get a good first idea of what might need to be fixed?
Currently, `git bisect` works best if every commit is buildable and runnable, in order that any commit can be automatically tested for the presence of the bug, to narrow down the problem commit as quickly as possible. If some commits don't build or run because they contain conflict markers, this make `git bisect` need a lot more manual intervention.
Can you think of a way in which an equivalent of `git bisect` might be adapted to work in this scenario?
Note that just scanning for conflict markers might not be appropriate, in case a file legitimately contains text equivalent to conflict markers - e.g. in documentation talking about conflict markers, or something like `=======` being usable as an underline in some markup languages.
One approach to preserve the ability to bisect would be to allow users to periodically mark points in time with “commits” if they want. The commits wouldn’t be used for synchronisation or merging (since we have the crdt information for that). Instead, they could act semantically much more like anonymous git tags. But they would still be useful as landmarks to mark working builds and milestones. And for git bisect. We could give them associated commit logs. (“Got feature X working”, “Release 1.0.5”, etc).
Commits might also be a good way to manage pruning. If users type then delete something, many CRDTs will keep a copy of the deleted characters indefinitely in a log. But we could design it so it only durably persists inserted characters which still exist at at least one commit. Yjs already does something like this.
- when new artefact files are added and you have not yet added them to .gitignore, they'll be automatically committed
- when you have added ignored files in one branch and switch to another branch, the files will still be in your working copy but not listed in your .gitignore file, and would then be automatically committed
- staging only some files and comitting is much easier than splitting a commit after the fact
Check out the documentation, many of the cases you are concerned about are explicitly mentioned:
https://github.com/martinvonz/jj/blob/main/docs/git-comparis...
A big downside I can see is that sensitive information might end up in the repo forever because I forgot to revert the auto commit.
Haven't used this yet so no idea if it's actually great or terrible, but I like to see ingrained conventional wisdom challenged like this. We're always in some local maximum, so I think it's often interesting to be pushed in some new direction on the gradient.
I think you don't need to do this after every autocommit but only before the manual ones? Isn't that how amending commits in git works? The unamended commit is no longer around, right?
Also it seems to me that you can rewrite old commits in jj to get rid of accidentally committed information a lot more easily than you can remove it from when you accidentally commit it to git...
Meta comment: Bit frustrating to see so many downvotes in these threads when people are just trying to grok what the jj model means concretely for important workflows.
> switch to another branch, the files will still be in your working copy but not listed in your .gitignore file
This is a failing of git, imo. There should be a .local.gitignore to somesuch, that is "added to" .gitignore. It's VERY common for me to have files that I want ignore, but are specific to me; they don't belong in the project's .gitignore. I know there are ways to do this, but all of them are clunky. There should be a simple, out of the box way to do it.
there's a newer flag for the same thing but my brain is hardcoded to the old "-u" option from "back in the day". It feels like a "simple, out of the box way to do it" to me.
Having a “local gitignore” makes a lot more sense. I’d like to be more explicit about which local files should be ignored.
You can even ignoring changes in files that are already tracked with `git update-index --assume-unchanged <file>`
I think .git/info/exclude is what you're looking for? (and also the global ~/.config/git/ignore)
The same location as .gitignore seems like the obvious location for it, to me.
I see this project as a challenge to that conventional wisdom. This view is certainly the one I have embedded in my mind. But is it right? I end up fixing up the index and amending commits post facto quite often. I can also do it pre facto. But in a world where you can't fully avoid editing after the fact, mightn't it be better to have a single workflow for this kind of editing? That is, if you can't totally get rid of post facto commit editing (which I think is reality), can you actually get rid of pre facto editing, and be left with just one editing workflow? If so, maybe that's good!
I haven't used this yet, but this strikes me as a very plausible attack on a conventional wisdom that we take for granted but may not actually be doing us any favors.
Obviously it depends on your workflow. If you're working on one feature at a time and only saving things out of your editor that you want to go to the project you cloned, it probably is.
That's not my world at all. I have logging and instrumentation all over the place when debugging. I have multiple features in flight. I have tweak patches that I maintain externally I don't want committed. I'm trying things for someone else, etc...
The index is the tool that allows you to manage all that without hating the process. That's why it was invented, in fact. Now, if you don't need it it seems needless, like someone is making you type "git add" for no reason. And jj seems to be aimed at that demographic, and that's fine.
But jj then needs to jump through some odd hoops to get back the "partial commit" workflow that is natural in git. Meh. Not a win for me personally.
There's also the problem of "patch hygiene", which flows like this tend to do pretty badly with. It's routine in some projects (Linux is the flagship here, obviously) to demand clean commit messages and perfect bisectability. Pull requests in that world routinely need to be split/squashed/reworked during review and when moving across branches. You need tooling to do that.
So for instance, to work with the index with all your logging and instrumentation and debugging that you describe, you must be doing a lot of `git add -i` over the course of your workflow to partially stage things. Maybe you're also stashing the unstaged changes to make sure what you have in the index works on its own.
Well, I don't think that interactive add workflow actually seems any different fundamentally from copious use of `split`ing changes out of the working copy commit with jj. And I don't think stashing seems fundamentally different from rebasing the changes you split onto the branch you want to apply them to, leaving behind all the logging and instrumentation.
Yes, it seems like a lot of commit editing. But this is downstream of your workflow that requires some step to differentiate between what to check in and what not to, at a fine-grained level. You would have to split things up manually with jj, but you're already splitting things up manually with git, it's just a different workflow with a different command, but I'm not convinced it's any harder. Just different.
> Pull requests in that world routinely need to be split/squashed/reworked during review and when moving across branches. You need tooling to do that.
Yes, but jj seems to come with all that tooling! And I think its model of implementing all that on top of commits, rather than a combination of the concepts of unstaged+staged+stashed+commits, may actually be a better fundamental model.
But again, I still haven't used jj. My workflow is a lot like yours, at least oftentimes. I split stuff up ahead of time, I use the index a lot, I stash stuff a lot, I edit commits, I rebase interactively, I squash and split. But by my reading of jj, I think I can accomplish all that stuff, and just maybe in a better way.
There's a very serious and glaring error in doing that if you don't commit everything. When someone checks out your commit in a clean repo, his codebase is in a state that has never existed before, not even on your computer.
Some people (I think we've all done it at least once) split their work into multiple commits after a few days of hacking. Like "Adding data services for X", "UI features for X". This is a bad example, but you get the point.
Normally this doesn't cause a problem since all commits get pushed at the same time. But it can easily break things like git-bisect (which is just an amazing tool for finding astonishing bugs).
Re this point, how is it any different? "Staging" the files is essentially the same as splitting the commit, anyways — it's just that the newly-split contents go into a "staging area" vs a commit. Do you mean that the tooling to accomplish this is not good?
From only reading the README, my understanding is that you would just add the files to .gitignore and at the next auto commit they are removed again from the anonymous working copy commit. I agree it feels unintuitive that files are added and removed from the anonymous working copy commit, instead of not added in the first place. But since the anonymous working copy commit is largely invisible to you it shouldn’t make a difference.
Interesting nonetheless, I never liked staging in git but I never hit the case you mention so now I realize there's a need for a phase between a file system and a commit
Apparently, I'm in the minority here (also considering all the talk about git being such an essential skill that real programmers can issue commands blindfold). Why is that?
The other thing would be to make sure people understand what things like 'squash' and 'rebase' to and how they effect the tree.
Also to explain if you have multiple features, please don't work on it all on the same branch unless you have too. (This is a internal workflow, not really related to git but how we handle merges)...
Yeah, a UI could be just fine for an experienced git user. In my experience though, experienced git users don't use it though, so it's a moot point.
Just a really quick aside...
Do you... say "oooey" instead of "you eye" when reading "UI"?
Their workflow was basically "blindly point and click in Visual Studio because I don't understand even the basics of git. I miss the old custom in-house VCS and am very grumpy".
I learnt how to use git years before I learnt to use the git CLI.
The skillset is portable across environments (I can remote into a box and look at a repo as easily as I can interact with one locally), across editors (I don't have to learn and re-learn how each editor interacts with the VCS), and I can use all my familiar tools to work with it.
As for those workflow examples, I can just as easily do all those things via the command-line. The editor integration isn't anything special. And when I need to something weird and advanced (e.g. interacting with the reflog), odds are I'm gonna have to bust out those command-line skills, anyway.
Why would that be so hard to believe?
Edit: BTW, to be clear, I have no issues with people using GUIs. If you're productive with your tooling, who am I to judge? But you asked why, so I answered why. I don't claim my way is any better than your way.
I'm merely wondering why people seem to prefer a lower-level, separate tool to a higher-level, integrated one. To me it seems similar to writing your Makefile by hand vs. using automake.
The same as you, I don't want to dismiss anyone's tooling. Just curious :-)
* Low-level tools come out and become robust sooner than high-level ones. It makes sense to learn them when no alternatives exist. Once alternatives are mature they're more effort to learn. I'm just much less fluent with an IDE than I am with the commandline, and the ubiquity of the commandline has meant I haven't been forced to learn to use the IDE well.
* There's some value to shaving off levels of dependencies. An IDE sends commands to git. There's just more moving parts and more to go wrong compared to just using git. When things go wrong, you don't have to understand two levels of error messages. Even when things are right, things can be slower. git is designed with obsession for large repos. Many UI screens might not scale as well as the underlying command providing them data.
In my experience most low-level people as you put it focus on the second reasoning. You don't use git in an IDE because you prefer to use Vim over an IDE, etc. But the first seems valid as well.
The skillset is portable across environments (I can remote into a box and look at a repo as easily as I can interact with one locally), and I can use all my familiar plugins from the extension marketplace to work with it. Yes, when I switch my favourite editor I'll have to relearn most of the particulars but that happens only about every 5 years or so, so it's fine.
As for those workflow examples, I can just as easily do all those things via the GUI. The shell integration isn't anything special. And when I need to something weird and advanced (e.g. interacting with the reflog), odds are I'm sure as hell NOT gonna have to bust out my command-line skills: from experience, 50% of the time I use some advanced commands, I mangle my repo into a broken mess that I am not exactly sure how to fix; not to mention that for routine tasks I keep re-doing "git status/diff" after every change because, again from experience, 10% of the time I issue slightly wrong commands.
No thanks, I'd stick with GUI which shows me what exactly I am going to modify and how. Would that be so hard to believe?
Now on a less facetious note: I prefer vi to ed, mc to naked shell, and gdb in dual mode (even though it routinely mangles my xterm's geometry into something non-Euclidean) to plain gdb for the same reasons — I can clearly see the state of the system that I am about to change, and the preview of the changes I am about to make, too. I don't have to second guess myself, or review the output of complicated shell pipelines with "echo" or "--dry-run" appended to the actual worker commands before actually committing to them.
It's not. That's, you know, why I specifically said "I don't claim my way is any better than your way."
The TUI feature?
And you don't have to learn about each editor, just learn about one
And those command line skills can just be used in those advanced cases, that doesn't mean the 90% of the time you have to have worse experience
It's not hard to believe, it's just the arguments don't square
Have you ever had a conversation like this?
Person A: Hey, what's your favourite food?
Person B: Pizza.
Person A: Really! Why?
Person B: I dunno, I just like the taste and the whole experience of eating it. But obviously that's just my opinion, and I totally get that some people might prefer something else.
Person A: Your argument doesn't square. Here, let me explain why your preference for pizza is wrong...
The question you might consider asking yourself is: why do you insist on trying to create an argument where none exists?
This is an incredibly bad habit. Arguing with people about their personal, subjective preferences when it's clear that person isn't inviting in an argument in the first place is incredibly annoying and it pisses people off. Don't do it.
And you've turned a constructive conversation into off-topic pizza moralizing. Don't do it
(and of course you're having an argument, just a using poor arguments)
Or it's folks that think the base set of linux tools are the be-all-end-all of programming. ("Why use Dropbox when I can rsync", "Use the ext4 filesystem as a database and store metadata in inodes and use git for MVCC", "I will do sed | awk | cut | xargs find | tr instead of a 10 line python script").
Or it's folks that cult-follow one of the two groups above.
The only time I ever reach for the CLI when I'm using IntelliJ is for `git bisect`.
That you're using IntelliJ makes a huge difference. VSCode's git integration is okay, but I honestly just reach for the command line if I'm using VSCode for a project. IntelliJ's, though, is hands down the best git UI out there. Even standalone apps can't compete with the convenience of having all the features bundled directly into your editor.
From what I've seen, a lot of people have tried git integrations in other IDEs and found that they are missing functionality and the features they do have aren't well done, so they assume that all git integrations will be the same. But as I've been reading through all the jj testimonials here, I can't help thinking that I already have all of this through the IntelliJ git plugin.
What _is_ annoying and something I could probably automate if I thought about it for more than a few minutes is when:
a) I have stacked PRs for isolation/workflow purposes in the form of: A <- B <- C
b) we use squash and merge
c) now when A gets merged I need to fix B and C because while the change set is the same the history is not (because of the squash & merge)
d) when B gets merged I have to fix C for the same reasons
(Here’s an example from a few days ago from someone who proposes using GitHub Desktop in order to avoid learning git commands: https://www.reddit.com/r/learnprogramming/comments/15b7pra/s...)
It makes fewer errors than I do. Besides forgetting syntax, fat fingering is a thing.
Conflict resolution is faster, easier, and less error-prone.
Pretty typical sorts of efficiency gains that IDEs can give.
I 100% agree with you that a GUI is by far the sanest way to do most git operations (I have yet to find an interactive rebase GUI that is better than a text editor). But if you just pick a random GUI it's probably going to be not very good.
Good GUIs I have discovered:
* GitX (Mac only) * GitExtensions (Windows only I think) * The VSCode Git Graph extension is basic but decent. Unfortunately it seems to have been abandoned.
I have tried many many others and they all sort of work but don't present things in an easy to follow way.
I ask this because most developers are already quite familiar with the git CLI so in production use one would probably see developers co-working with jj and git in the same codebase. Or would the realistic production scenario be always using git (as opposed to native Jujutsu database) as the backing storage to allow accessing both with git and jj CLIs?
If you're going to work on and contribute to a project that is already using Jujitsu, it is reasonable to expect that you'd adapt your workflow to the project itself and not the other way around.
At this time, there's practically no reason to use the native backend. The backend exists mainly to make sure that it's possible to eventually add functionality that cannot easily be added to the Git backend.
I'm curious about its management of conflicts. I know that pmeunier has taken a lot of care to formally work out a theory of patches to drive Pijul, and that unsound or problematic notions of patches/conflicts can lead to serious problems— they say that's what led to all the performance problems with Darcs, right? I'd love if the comparison page on the repo wiki gave a little more detail than that Pijul's handling of conflicts seems 'similar'.
Storing the conflicts symbolically in this way lets you reproduce the conflicts later and even auto-resolve certain conflicts, but it doesn't address resolving the actual contents of conflicts. You could probably use Pijul as a jj backend and get the best of both worlds (if someone were to implement it).
Neat. I've been leaning more and more towards systems that are dumb-sync-friendly because I can throw them anywhere with anything and they just work. Always glad to see new things doing this!
I've heard of this issue before but haven't experienced it myself. How does it arise, I.e. what's the race condition between git and rsync/Dropbox/... that causes problems?
---
For example, branches. From what I can tell (I'm moderately confident here, but definitely not 100%):
Git stores branches as a file at .git/refs/heads/branch-name which contains the current commit SHA. When you make a new commit in that branch, it modifies the file's contents to contain the new SHA. Syncing a conflicting change here means... well lots of possibilities. Maybe one write silently wins and the other SHA gets silently garbage collected eventually, maybe there's now a new branch with a conflicty name, maybe you have a broken file because it contains a `diff` conflict and it has <<<===>>> markers, maybe demons erupt from your nostrils.
Jujutsu stores branches as a folder at some/path/branch-name/. The current head of the branch is a file in that folder with the SHA as its filename (the file is empty), giving you some/path/branch/c0ffee . If you sync changes from somewhere else due to concurrent changes to your clone, it either agrees and there's no harm, or it becomes a new file in that folder, some/path/branch/00f .
Your local tool doesn't know which is correct, but it can tell that a conflict occurred, and both actions' results are still available.
---
That's a thing you have to design carefully around, so it's completely understandable that it's not the default. But one of the benefits is that it means you don't need special protocols to exchange data, so tons of things simply work correctly without further effort. Git has to use lock files to protect its internals, and they have numerous issues and are completely useless in some situations (e.g. NFS, Dropbox). Jujutsu simply doesn't need them, and works safely regardless of your system or usage patterns (for this at least, dunno about everything).
All this^ is why I pay to store my git repos in a git host rather than simply using my own backup systems (and why I don't have two remote backups). And it's why I avoid SQLite for much of my heavy-use personal backed-up stuff - it makes one large file that mutates, so fully syncing is a MUST before making any changes. Same with keepass - one large mutating database (but it has pretty good database merging built in, so the dozen or so conflict files I've gained are easy to resolve). There are pros and cons to splitting things up into a million files of course, but I run into sync issues pretty regularly so that's the pain I feel most keenly.
Thank goodness. This has been needed for a long time.
Props to the author Martin for investing himself in creating elegance!
Edit: anonymous branches are destined to be pushed remotely (to be reviewed and merged) and there is no local merge as far as I can tell, you can name these branches but no "merge back to development branch once done". Completely different workflow, having the ability to merge or "collapse" the anonymous branch to its parent would be nice, when you don't really need to push your feature branches anywhere.
You would expect the push to fail in the normal way, as if you had manually done the rebase, because your commit history may have diverged. That being said, I don't think this happens much in practice: the automatic rebases are typically for explicit history-rewriting operations that users tend to only do on their local work. If a user prefers to use a "no-rewriting" workflow, then they can certainly do so by simply not issuing the history-rewriting commands.
> anonymous branches are destined to be pushed remotely (to be reviewed and merged) and there is no local merge as far as I can tell, you can name these branches but no "merge back to development branch once done".
I'm not sure what you mean by this. You can do `jj merge` in a similar way to `git merge`, or you can do a rebase workflow.
Yeah, most of those rebases happen in the working copy or inside the anonymous branch.
> I'm not sure what you mean by this. You can do `jj merge` in a similar way to `git merge`, or you can do a rebase workflow.
I just meant merging a feature branch back to its parent, jj merge exists but it's not obvious which revisions you are supposed to pass to it and jj log doesn't even seem to be able to update the HEAD for the parent branch. It should be an easy operation but it's not, clearly not the suggested workflow (or at least, not documented from what I've read).
I don't think I understand your confusion. `jj merge` is essentially like Git merge, except that you also normally pass the current commit as one of the revisions. For example, you could write `jj merge @ main` to make a merge commit with the working copy[1] and the `main` branch. Feel free to open a GitHub discussion or drop by the Discord channel to discuss more.
> jj log doesn't even seem to be able to update the HEAD for the parent branch
`git log`/`jj log` are non-mutating operations, so why would you expect that they can update `HEAD`/branches?
[1]: In my workflows, I typically use `@-` instead of `@` in this situation because I consider the working copy commit to contain "uncommitted" changes, but there is a significant complement of users who primarily use `@`, so it depends on your workflow.
> For example, you could write `jj merge @ main` to make a merge commit with the working copy[1] and the `main` branch.
Since it's my first time seeing this, the syntax is a bit confusing, the man says "Unlike most other VCSs, `jj merge` does not implicitly include the working copy revision's parent as one of the parents of the merge; you need to explicitly list all revisions that should become parents of the merge.", it's not clear to me if the order matters and how many can be listed (and why for more than 2). What I wanted to replicate was just doing a "git co main; git merge feature-branch", thanks for the explanation.
> git log`/`jj log` are non-mutating operations, so why would you expect that they can update `HEAD`/branches?
What I meant was that with git status/log the differences between your local clone and the remote are clearly shown, you know if they are misaligned and see the last commit for each branch without using additional commands, jj log/st provide way less info. I see where "main" is in jj log but have no idea if that label will update after a push or a fetch of the remote. Just a bit confusing for newcomers, I have to play with it.
It's worth noting that in Git, 1) the order is recorded and tracked in the commit (but almost always doesn't matter), and 2) you can have any number of parents for a commit. A >2-parent commit is called an "octopus merge" in Git, which you can search further to learn about. Thus, the only real difference is that Git defaults to including the current commit as one of the merge parents (and this can't really be disabled via `git merge` itself; you'd have to use one of the plumbing commands and construct the commit manually).
In Git, you can have 0+ parent commits; in Mercurial, you can have 0-2 parent commits; in Jujutsu, you can have 1+ parent commits (except for the special root commit with the zero hash, which is the ancestor of all commits that would otherwise have no parents).
To explicitly answer about the merge parent order, you can see a question like this: https://stackoverflow.com/q/49715421/344643. I suspect that under jj the difference is not nearly as complicated because of how it records conflicts, but I don't know for certain.
> What I meant was that with git status/log the differences between your local clone and the remote are clearly shown
In general, the multi-remote visualization could be improved in jj to handle some common workflows. You can customize the default `log` by setting `revset.log` https://github.com/martinvonz/jj/blob/7751cea47cfe6dd9654275... to show the commits you care about.
If the same branch exists on multiple remotes pointing to different commits, then I believe they're rendered with the remote to disambiguate as e.g. `main@origin`. This originates from Mercurial.
I really hope this succeeds... VSC support would be a great start...
I need to try it out at $WORK since constant rebasing on a busy repo with a hundred or so committers is not fun.
Meanwhile, if rebasing on git is an issue, you should probably try stacked-git (https://stacked-git.github.io/). It manages commits as a stack of patches - like quilt, but on top of git.
That said, I’ve been using darcs this past month & the performance issue seem overblown (/ largely fixed, tho not fully addressed) while the stability & usability are there. Email support is a bonus.
1. Not until I run some jj command? It's kind of begging for a 'jjd' isn't it? Or if you use an IDE you'd want/need it to be not just saving but doing some kind of 'jj nop'.
2. I haven't looked more into it than the readme, but that at least doesn't discuss (and I think it's important) withholding commits from the remote(s)? If everything's always(ish) committed, I've lost some control of untracked files or unstaged or locally stashed changes that I now need at the point of pushing; to mark those commits 'private' or something. I assume it does exist, and I'll look for it when I make time to play with it, but I find it slightly concerning (for how good it will be, or how important it's considered to be) that it's not more prominently discussed.
In practice, I find that it doesn't matter much. Some people do run `jj` in a loop incidentally (usually they have a live graph-log open on some screen). I suppose that you could get a "local history" feature like in some editors of more fine-grained changes to the codebase in this way. Folks have discussed adding a jj daemon, but so far it's not a priority.
> I haven't looked more into it than the readme, but that at least doesn't discuss (and I think it's important) withholding commits from the remote(s)? If everything's always(ish) committed, I've lost some control of untracked files or unstaged or locally stashed changes that I now need at the point of pushing; to mark those commits 'private' or something. I assume it does exist, and I'll look for it when I make time to play with it, but I find it slightly concerning (for how good it will be, or how important it's considered to be) that it's not more prominently discussed.
Usually it's pretty obvious to me which of my commits are public or private. When interfacing with GitHub, commits that are not reachable by a branch are definitely private . Additionally, commits without a description are private, and `jj git push` will warn you before allowing you to push them.
There has been some discussion about adopting Mercurial-style "phases" (https://wiki.mercurial-scm.org/Phases), which would explicitly accomplish the goal of marking commits public or private.
It is worth noting that jj is designed as a CLI and a library. So, for the hypothetical IDE integration, it could use its own custom written daemon or just integrate all this as part of its autosave functionality via the Rust crates. That's the long-term goal, anyway.
How does it handle very large repos and binary files? I want petabyte scale history and terabyte scale sparse clones.
Alternatively, why use this over Git? I read the differences but feel like I’m missing a higher level difference. What problems does this solve that Git/Hg don’t solve already? What was the inspiration for starting the project?
I'm long accustomed to spelling it, in English, as Jujitsu. I've also seen Jiu-jitsu. "jutsu" is much less common, IME.
Is there such thing as canonical Romanisation of Nipponese? I can deal with a project being "wrong" better than not knowing which of us is wrong.
As a Brazilian Jiu Jitsu practitioner, I cringe when I see it spelled any other way, but also I have to recognize that I only feel that way because I have more exposure to that specific martial art/spelling.
It's jujutsu in both, but there are varying ways of representing the long vowel on the first "u" -- either omitting it (jujutsu), using a macron or circumflex (jūjutsu), or repeating the vowel (juujutsu).
Ju-jitsu and jiu-jitsu are not correct in any romanisation system that I know of, I'm not really sure how they came about. Probably historical accident.
Unless we're talking about the English word, which I'm not sure how to pronounce. If we're talking about the American word then it's really just like a lot of other murkin words: butcher it any way you like.
The first kanji in 呪術 means curse, so while "sorcery" could be a cool name for a software project in English, a Japanese speaker would probably have apprehensions about using 呪術.
Pronunciation is more like joo-juh-tsu. "Tsu" is its own syllable.
(the UX around this is the shortcoming of all current DVCS..)
* Disclaimer: I'm one of the creators/maintainers of the project.
That said, any git users will still get slowed down - only jj users would see the benefit. Git does have better features these days for shallow clones though and even git is on the way to killing off the need for LFS.
JJ doesn't seem to do that though, right?
I've been playing with a homegrown centralized VCS client and server project, aimed at basically being the VCS host for monorepos, and this looks like a much better foundation for the VCS layer.
However, if you don't manually put a branch on it, it'll never get pushed and will stay on your machine only.
You can sit on this personal commit and rebase it on top of any other commit to move around the repo, again and again if you like.
In case it helps your use case:
git update-index --assume-unchanged <file>
git update-index --no-assume-unchanged <file>
This would ignore changes while you're testing - but you have to remember to turn it off or, iiuc, you won't pull intentional changes either.You might find hooks useful too. Not to assume your knowledge, these are shell scripts placed in .git/hooks that are invoked, e.g., before commit or before push. You could have it parse git status, detect changes to <file>, prompt for confirmation if changed and remove from working set if the change is unintentional.
WTF?? Me neither on none of my git project. Ever. So what? I’m a git genius?? The truth is I don’t even understand how one can reach a point where re-cloning a (git) repo is needed: git can always reset --hard and it has never ever failed for me, even during complicated interactive rebase and co.
https://github.com/martinvonz/jj/issues/58#issuecomment-1247...
There's definitely nothing foundational blocking it though and it will happen one day if you'd like to give it a go in the meantime.
I'm a bit confused by this. I don't think that's what monorepo means, is it? Monorepo is what you choose to put in a repo? And I thought Mercurial was extremely similar to Git as it's also a DVCS?
SQLite uses a custom vcs called Fossil, but doesn't make much effort to push broader adoption (afaics) so it remains academic at this point.
Jujutsu keeping git compatibility looks like a differentiator that reduces cost of adoption. I'm excited!
Anything new in the project?
Looks very interesting, will definitely have to give this a deeper try.
Both of these are on my TODO lists but haven't had time to try them yet.
In my opinion:
- Sapling is much more mature/full-featured at this point. - Jujutsu improves source control workflows in a more principled way. - Jujutsu currently supports colocation with a Git repo, while Sapling requires that the Git repo be kept separately.
- Currently no partial/shallow clones, so you need to materialize the entire repo on disk.
- Working copy status can take a while (see https://github.com/martinvonz/jj/issues/1841 for tracking issue). This can be ameliorated at present by configuring Watchman as a filesystem monitor, and work is underway to improve status further.
- No support for Git LFS at present (even in colocated repos). When using the Git backend with jj, you would expect the same problems with regards to large file management.
- I haven't noticed any particular performance issues when interacting with the object store a large repository. It should be approximately the same as for Git since it uses libgit2 for the Git backend.
- Faster for operations that can be done in-memory in jj but only on-disk with Git, such as various rebase operations.
It is less apparent now but still, the repeated flex of “Google”, 20% project etc when no typical reader would assume them is classic corporate charlatanry.
Shame because I like the project otherwise
Does [git log] have that? I think too much hash looks noisy.
All I could find was:
$ git rev-parse --short=4 HEAD
8f60This is some kind of background process that automatically commits any changes you make.
You can use the CLI to check what it did and if you want to modify the auto commits.
> Commands snapshot the working copy before they start, then the update the repo, and then the working copy is updated (if the working-copy commit was modified). Almost all commands (even checkout!) operate on the commits in the repo, leaving the common functionality of snapshotting and updating of the working copy to centralized code.
weird choice, isnt explicit always better than implicit
Facebook version control system built on mercurial is the best I've used. One of the huge benefits of the rebase based workflow is commits can be reordered and land as they pass tests rather than a single linear commit history.
Too bad support/discussion happens entirely on Discord.
The reality is that the project is in a part of its life where active discussion and feedback with the userbase is pretty valuable. So, you just go where the users are, and Discord is a big place for that.
GitHub Issues and GitHub Discussions are also actively used by a lot of people, and you can just post things there. Every major committer watches those venues as well, AFAIK, I know my notifications are set up for it.
Over time, assuming things are wildly successful, you'd probably have a lot of different venues to discuss things. I would view Discord mostly in the context of a new project that wants feedback, in that regard.
Ooof, random "ASCII" (actually: Unicode) art & dev-chosen colors, my bane of the "modern" CLI applications. That drawing you like? Doesn't work for me, give me the raw output please. Those colors you love? Aside of red-green weakness being the most dominant factor, what you're really doing is trying to set things apart, connotating color with semantics as well. It's nice this works fine on your white-on-black terminal. Have you tried this on a white-on-firebrick terminal? Yellow-on-green? Or anything else than _your_ "normative" setup? Man ...
Also not sure the information presented is adequate. E.g. consider commit 76(2941318ee1) - jj makes it look like that was committed to that repository, while it was done to another. The git presentation looks more spot-on (for that particular commit, while the rest of the display is just a mess - ASCII art that does not add semantics, random colors); also where is 1e7d displayed in jj's output? Why is jj's order different? I remain unimpressed by both UIs.
" Create a file at ~/.jjconfig.toml" ... $XDG_CONFIG_HOME ?
When is that working copy committed? When I run jj? Why bother, when it's not working asynchronously and automatically? And if you commit working copies, do you sync under the hood with stuff the other folks you collaborate with? If not, why bother?
Oh nice, a command to fix "stale" workspaces.. how about you don't let workspaces go stale?
This may all seem to make sense to git-minded people, given the comments here. To me, neither jj nor git make sense (as fossil-minded person who has to work with git), so shrug enjoy....
..but please fix that ASCII Art and Color Stuff, thank you very much.
But the one CLI trend that annoys me is using Emoji terminal. I often find their colors and shapes to be too distracting, commanding too much of the visual hierarchy of output. They also have a tendency to kind of fall apart when some characters or combinations of characters are missing or they no longer line up with the monospace output. A big part of CLI output is being able to scroll through the logged output, but the Emoji actually make visual scanning more difficult.
If not, then I have https://github.com/alrs/nofun
I was curious about this as well, as I found the images in the README a bit hard to read. In fact the program itself seems to use quite sensible colours in my white-background terminal, and it also respects the NO_COLOR environment variable.
But, TIL about NO_COLOR!
$XDG_CONFIG_HOME/jj/config.toml is supported, that's where I keep mine.
The working copy is updated whenever you run jj by default, but watchman is also supported (recently added). [4]
In my experience, the command to fix the stale workspaces only needs to be run in exceptional cases where a bug got triggered and a command failed to complete or if you're doing manual poking around.
It's a mindset shift, but it's well worth it in my opinion.
[1] https://github.com/martinvonz/jj/blob/main/docs/config.md#ui... [2] https://github.com/martinvonz/jj/blob/main/docs/config.md#gr... [3] https://github.com/martinvonz/jj/blob/main/docs/templates.md [4] https://github.com/martinvonz/jj/blob/main/docs/config.md#fi...
> my bane of "modern" CLI applications
Ok boomer, I guess you're also a Rust hater too - because I love that every last CLI written in Rust is not garbage because clap is so good and so ubiquitous
All the colors and unicode graph can be configured and disabled through config and CLI flags, and the default pager is less which supports NO_COLOR and piping and everything else that less does.
It literally has an extensive template system as one of the features to make the log output ANYTHING you would want:
adjust some colors that you hate? sure (also there's a config for color names that are used in templates, for colorblind or contrast adjustments etc)
add/remove/adjust some info (that you hate)? sure
just list full commit ids to be machine-readable that match some revset? - absolutely
Man, why do I feel so second-hand embarassed from reading this dumb ignorant reply, lol
git is about working locally github (or similar solutions) is the cloud part
cloud native scm sound like a bad idea
From the README, no advantage for now.
According to the readme, “jj” was chosen because it was easy to type, and a name was found to fit that.
Really?
The wiki audio sample[1] seems fairly close to how people in the US pronounce the word, just with a more emphasized first syllable.
---
1. https://upload.wikimedia.org/wikipedia/commons/f/f1/Ja-Jujut...
Ambiguous romanization is weird, I wish romanization rules would just follow IME rules (except in the case of consecutive n's, that'd look weird).
That's kind of tautological ...
However, yes, that is certainly true. Japanese has a pitch accent rather than a stress accent and all syllables are roughly the same length. English speakers don't tend to talk that way.
I wish these well-intentioned Googlers realise what they are doing.