Been using jj at work for months now. In colocated mode, JetBrains IDEs even retain some if their VCS integration.
The ability to easily work on top of an octopus merge and then push changes "down" into the contributing branches has been a live saver when my team had to do a big refactoring in a mono repo and split the changes into small PRs for individual teams (code owners).
The auto committing behavior is a bit weird at first, but now I don't want to go back to git. It feels a bit like the step from SVN to git back in the the day. ("this feels weird" -> "how did people ever tolerate the old way?")
Full agreement on both “it feels weird” -> “how do people ever tolerate the old way” as well as the auto commit behavior being one of those things. In fact I probably over-index on that specifically when talking about jj. I thought I’d hate this because I love git’s index. Turns out that by getting rid of the index, jj has a better index than git does, but that sounds insane at first!
Same about the index! I initially skipped over jj thinking the lack of an index was a huge step backwards but commit-splitting is a massive improvement.
I do wish there was a better front end for it though.
100% agreement on that transition. I feel like I had to unlearn a lot of git’s subtly-broken model and now things feel so much simpler and easier.
One of those for me was branch names that don’t automatically “follow” new commits. At first it felt weird but it unlocks the ability to do consecutive work as one linear set of changes, even when those changes need to be merged in discrete chunks. The git approach for this (stacking branches) is so painful, particularly when you need to edit an earlier change or add a new commit between earlier ones. This went from being so frustratingly difficult I wouldn’t even consider it to being utterly trivial.
Also rebase conflicts. Not being unceremoniously dropped into a half-broken “fix this NOW state” with no ability to inspect and poke at other commits in the chain and not being able to fix things incrementally in any order is something I couldn’t have imagined. And like you said now it’s insane to me that people continue to put up with it.
To me at least it makes so much more sense to be like:
1. I am going to work on $X
2. autocommit
3. My work on $X is done
rather than
1. I make changes
2. I am done making changes
3. Now I have to describe what I changed and how
Maybe this is just me, but with git it is at times hard at times to hit the right balance in terms of commit granularity — and for my flow planning forward ("I am gonna do $X") rather than describing what I did ("I did $X") seems more.. focused?
It's a bit of a magic trick. A "snapshot" is taken any time a command is run, and it happens implicitly before any actual algorithms or code for a given command is run (massively simplifying the internal design), so for all intents and purposes it's "automatic" from the user interface e.g. even checking repo status or otherwise small operations will cause a snapshot.
But you can integrate with https://github.com/facebook/watchman/ in order to have a truly daemon-ified option where any filesystem write will cause a snapshot to be taken.
What’s also amazing is this means you’re no longer completely on your own when fixing things up.
I can’t tell you how many times I got midway through a git rebase, realize I’d fucked up earlier, and had to abort and redo everything from scratch. With jj not only is this not a problem (thanks to conflict markers being first class citizens) but also any intermediate changes are being snapshotted without me having to do anything. So I can always go back to an earlier change if need be.
The first time I had to do this I’d royally fucked up a complicated commit reordering sequence while screen sharing during a meeting. I’d never needed to use the op log before and had no idea how to use it. It took maybe a minute or two to fix things completely.
> I also heard friends rave about "stacked diffs" but struggled to understand what exactly was going on there. Nothing I read or conversations I had have clicked.
I wonder what it is about descriptions of stacked diffs that doesn't land - it's literally just a rebase-centric workflow instead of the merge-centric workflow popularised by GitHub et al.
For me, my git brain is very low level. And none of them ever explains what actually happens under the hood, or how that was different than branching…
With some respect, I think “rebase centric workflow” doesn’t really cover it: I use rebasing heavily with GitHub. A “trunk based development where all branches are rebased before merge so there’s never merge commits, please add to commits and rebase the branches in response to review comments” development style is still very rebase centric, but not stacked.
You also have to remember (though there’s no reason you should know this) that GitHub came out of the community I was heavily involved in at the time of its birth. I’ve been using GitHub for longer than most people, and so the PR-style workflow has been the air I’ve breathed for so long, it can be hard to understand other things at first. I had used subversion before git, but didn’t really understand much about it.
Anyway, that’s just a roundabout way of saying that I think this space is super interesting because these tools are so flexible that they can be used in so many different ways, and it’s easy to assume that “how you use git” is some sort of shared experience when that’s not really true.
What I find confusing with people raving about it as something different is the considering the following scenario:
refactor/a # should be merged as independent PR
feature/a-b # depends on a; should be merged independently, after
fix/c # independent
Then I will probably have some local `dev` branch checked out with a combination of all of the above and maybe some.
How else than "stacked diffs" would you submit all of this for review concurrently while still working on all of them? It sounds like a new word for what we were always expected to do? What's an alternative which doesn't rely on compromising merge-strategy?
At this point I'm suspecting "stacked diffs" is just a crotch for people still barely coping with DVCS but I could be missing something.
GH did not really support automated base change (after merging) not that long ago, so doing this as stacked diffs/prs there was quite annoying. I guess that's why people tried to avoid it for a long time? I know I did some weird PRs in the past just to keep them independent. It could still be improved today (why is main the base when the branch contains an existing PR? adjust it automatically, please!), but it's not a big issue anymore.
Also this workflow is excruciatingly painful if you need to make a change or add a commit to an earlier part of the “stack”. Now you have to manually rebase everything after it one by one by one.
Yeah, and I think it's also worth noting that by far the largest set of folks advocating for stacked diffs are not actually using git.
Meta runs a custom mercurial-compatible monorepo that was scaled up to handle thousands of commits per hour, and they have a culture of constant codebase-wide refactoring commits (i.e. the ongoing live migration of PHP -> Hack) - that rate of change causes problems that most GitHub projects have no reason to solve.
The holy grail of a stacked diff workflow would be making multiple Github PRs each made against the previous branch instead of trunk, and then updating the branch that's closest to trunk and rebasing all of the children PRs without having to force push into any of them.
Git and Github do not support this workflow directly, and this creates space for all the other tools.
Jujutsu comes close to supporting this workflow directly, without having to provide any specialized utilities that mention "stacked diffs" at all.
https://github.com/spacedentist/spr (not to be confused with ejoffe/spr) is a utility that maps a local change to a remote branch, updating the branch (without force pushes) when the local change is updated. (Changes work the same as they do in JJ, but the implementation is different.) And this works even when you have a stack of changes and update one in the middle of the stack.
Maybe the problem here is that there aren't any open tools implementing the user experience, because I'd say it's exactly the opposite - that it is implemented using rebase under the hood is entirely secondary to the user experience of "stacking" changes on top of one another.
Steve, I see you’re in this thread. I was using jj for a while before reading your tutorial and yet still found it quite insightful and helpful. Thanks for your contribution!
I am torn between sapling and jj. Both make good progress in git/github integration which seems to have been the major road block in adoption before. One other major roadblock seems to be the limits of review tools supporting stacks: github PRs are too limited, gerrits ux is horrible, graphite does not work and is not open enough, saplings review tool is just a very slow performing POC (though with a really good UI concept as starting point)
for me important argument in favour of JJ over Sapling was "first-class conflicts" - JJ stores conflicts in the history and allows you to resolve them later, while Sapling forces you to resolve conflicts at the point when they happen
If first class resolution is done right, then instead of project generators we can just create sample projects that people fork, and when you make breaking changes or add new startup config to the project, you update the sample project(s) and people can pull the updates. Once you resolve the conflicts you’re done until the next change, at which point your repo remembers how the last conflict was resolved, and doesn’t ask you to redo it.
This is why jj is on my todo list. I’m not calling it jujutsu no matter how much someone pays me though.
Gerrit, as I like to say, has a user interface that only a mother could love. But ultimately it's a very productive tool, so I just got over it. I even wrote some integration between jj and gerrit, making submitting stacks very easy and smooth.
IMO, Gerrit is the best currently available option by a large margin, notwithstanding its quirks.
graphite only works with graphite tooling. you cannot just use sapling or jj to create a pr on github and then use graphites review tool, even though this should work in theory, but they block this somewhere in the pipeline.
One of my favorite people talking about my single favorite tool of the past 3+ years. Up there with (above, really) zellij and helix for changing my daily life.
Zellij looks powerful but also a bit too complex, following the "kitchen sink" school of design :-). No biggie but its name is too close to IntelliJ imho. What kind of workflow do you use with it?
I used tmux a bit back in the day, but these days I feel like good old tabs and app windows cover my needs. When I want to multiplex processes in a single window, I reach for Overmind. [1]
I don't know what you find complex about Zellij, as I haven't used it extensively, but the few times I've used it, the UI was eminently self-describing, I managed to do everything I've needed to do within a minute of first launching it.
Is there more advanced stuff that's more complex that I just haven't seen?
> Is there more advanced stuff that's more complex that I just haven't seen?
Yes? I mean, it even has a system to install plugins made with WASM, from what I saw in the docs. I guess you could just use the basics and be okay with it.
For me, after years of tinkering with apps like Vim, Emacs, and AwesomeWM [1], I've developed a bit of PTSD over the amount of time these kinds of tools can take to configure and master [2]. Zellij feels like it belongs in this category of tools, and perhaps I'm overreacting or flinching. :-)
2: I've benefitted a lot from being able to use vim key bindings with extensions in apps like VS Code and emacs key bindings on places and I'm somewhat glad I spent the time, though.
Not the OP, but Helix is a minimal fuss modal editor with sensible defaults. My config is maybe five lines? I say "maybe" because I haven't looked at it I first wrote it. And I think I'd probably be just fine with no config.
Sensible defaults but also out of the box features you only get in vim by finding and configuring external plugins (fuzzy file picker, LSP integration, multi-cursor editing, inline keymapping help).
Cons: no session save, good Helix keybindings not available in other tools so confusing to switch between vim/hx mental models when in colab/vscode, no AI-assistants since no plugin system yet.
nice to see zellij+helix combo users out there. Helix been my fav editor for like a year already, zellij is also a daily driver, and now i'm learning jj :P
Love it, read a couple of chapters already and planning to finish the rest. As a person completely new to jj and someone who also enjoys git CLI, this is an intuitive, very useful and enjoyable read.
I’m especially interested after learning about the git compatible backend:
> There's one other reason you should be interested in giving jj a try: it has a git compatible backend, and so you can use jj on your own, without anyone else you're working with to convert too.
This was informative, thanks Steve! The only problem I had was that the difference between changes and commits wasn't clarified enough in the beginning, and I got lost trying to distinguish between the two. I'm on chapter 4 and I'm still not sure what a change is and what a commit is.
From a tiny bit of previous jj experience, my mental model is "a commit is the snapshot, and a change is what happened between snapshots", but that might be wrong. It would be great if this could be clarified a bit more in the tutorial.
Changes are a stable ID for a single conceptual modification to the repo. They are backed by—at least right now—a regular git commit with a content hash.
As you iterate on one change, new underlying git commits are made to snapshot the latest state of the current change being edited. The change ID remains stable though the backing commit ID varies as the change’s contents are modified.
You can always revert a change back to a previous backing commit.
This has a few cool implications.
First, you never have uncommitted changes. You are always editing a “current” change with an ID that is regularly persisting your work (either every time you run a jj command or with a filesystem event monitor).
Second, you’re never “on” a named branch: you’re only ever “on” a change and named branches are just mutable pointers to change IDs. This is awesome for a long-lived linear bit of work that needs to be merged in piecemeal. In git you’d need multiple branches based on one another and nothing tracks the relationship between them. Updating earlier work is painful. With jj, it’s all just one linear branch with some changes having names. If you need to edit an earlier change or insert new ones, you just… do that.
They (and you, unless you go digging) only see the latest commit associated with each change.
The older ones are there in your local repo but they’re invisible unless you need to look through the “obsolete log” to find earlier iterations of changes. This is similar to (and built on, essentially) git’s reflog that holds on to commits that are no longer in active use.
There’s also an “operations log” that, instead of tracking history of individual changes, tracks history of the entire repo. So if you reorder or drop changes, you can always undo those operations.
Thanks for the feedback! I think these terms are used a bit loosely, even though I try to be precise about it.
Your mental model sounds decent. I think there’s a few ways to describe things. I like to think of changes as a stable ID for something I want to accomplish, and a commit as the intermediate steps that make up a change. You can kind of think of a change as a branch… but I think that’s stretching it.
OK, going through the tutorial again, I think the difference between commits and changes are definitely the thing to focus on. Before your comments, I felt like I was building on a small foundation, because I didn't understand how commits and changes relate, and didn't have a good mental model of them.
After your comments, the entire tutorial is more solid, but still I feel like I have gaps. My feedback would be to really focus on commits vs changes and define their relationships, what affects them, etc before you go into anything else.
Ahh, this really helps. I think you should definitely mention that a change consists of commits (unless it already is and I missed it). This helps because the two questions I had before were "if I change some text, will the change ID change too?" and "does a commit end a change?".
Clarifying early on what exactly a change ID is, when it changes, and what the relationship between changes and commits is would really help, I think.
I’ve started to use jj much more often (and actually used this tutorial to get me started!). I do wish its interaction with Nix flakes is less annoying though, but that’s not the fault jj.
I get that naming is one of the hardest problems in computer science, but naming software after a martial art is just lazy and will lead to problems with things like searches
If it helps, they actually named it after their desired CLI abbreviation:
> The command-line tool is called jj for now because it's easy to type and easy to replace (rare in English). The project is called "Jujutsu" because it matches "jj".
I would have spelled it "jiu-jitsu" if I had not looked up the spelling first and found that Wikipedia decided to spell it "jujutsu" (https://en.wikipedia.org/wiki/Jujutsu). Maybe I trusted Wikipedia too much; I have never practiced jujutsu/jiu-jitsu myself.
I’ll just be honest with you: Pijul never really caught my interest, but I always felt pretty neutral about it until I started noticing the project authors acting very snide and aggressive on here. That is not something I want to be around these days, and so I doubt I’ll ever try Pijul.
It's a little surprising to hear, from what I've seen here there's not much besides the usual whiff of academical loftiness, but nothing that I'd qualify as aggression.
What caught my interest:
* Separate operations and data
* Partial repos = no big repo issues, no need for shallow clones and such
* Proper and easy merging with no shuffled lines
* Patch-based model is much more intuitive (e.g. rebase and merge are the same operation)
* Conflict resolutions are stored and can be reapplied!
Steve wrote this in a very approachable style. It is the first time I really understood what 'jj' is about. I'm actually kind of excited to start using this tool with my git repos.
I’d love a Sapling vs JJ comparison post. I use Sapling at the day job and… it’s pretty dang good! Although I don’t see how I’d recommend it outside of Meta.
I swear the modern programmer doesn’t realize how extremely bad Git is. It does do a lot of things better than SVN. But it’s a long, long ways from “good” imho.
I blame GitHub. Git didn’t win because it’s good. Git won because GitHub won. If only HgHub had won instead, alas.
My dream VCS system would have a virtual file system, copy-on-write storage, and a system wide blob cache. The goal being to allow open source repos to commit *ALL* their dependencies, up to and including toolchains in many cases.
I used Sapling for many months before switching to JJ. In my mind -- and I'm insanely biased as I am a JJ developer at this point -- they are both leagues ahead of Git, but I think JJ's Git interop, conflict handling, and general UX make it much more powerful and general than Sapling. Conflict handling alone is leagues better. (Martin worked on version control for a long time, and JJ is a tool that can only be created by someone with deep expertise in the domain, which I think shows itself over and over again in its use.)
If you are already using Sapling then at least trying it should be fairly easy and familiar. You could also write a JJ backend for Mononoke and EdenFS if you wanted and then use it at work ;) You wouldn't be the first JJ user from Meta, actually...
We do have plans to explore server-side designs with virtual filesystems, chunked storage, etc. There's nothing concrete yet.
(Another benefit is that JJ is much easier to write patches and contribute to due to being a relatively new, small Rust project, whereas Sapling is much more developed but much bigger and harder to get into, I think.)
JJ's conflict handling seems nice. Can't say that it's a big enough problem for me to qualify as a "major feature", but a seemingly nice improvement.
I'm not sure how I feel about JJ's "working copy commit". One of the great things about Meta VCS is that all commits are automatically backed up into the cloud. Which seems incompatible with the JJ model? Not sure. I think the D in DVCS is *wildly* overrated. 99.999% of projects are defacto centralized.
I'm #TeamMonoRepo 100% of the way. My background is gamedev and perforce. An industry which still uses Perforce because Git is poopy poop poop. The Git integration I want to see is the ability to easily sync a monorepo subfolder with an external GitHub repo. Syncing commits for internal projects that are open sourced requires a big ugly custom set of tooling. And I'd kind of like a way to do an "inner fork" within a monorepo if that makes sense.
If you're interested here's a pair of blog posts I wrote that have at least some of my thoughts on source control.
> JJ's conflict handling seems nice. Can't say that it's a big enough problem for me to qualify as a "major feature", but a seemingly nice improvement.
The advantages people first think of when they hear about jj's conflict handling are usually that you can collaborate on conflicts and that you can leave conflicts for later. What's less obvious [1] is that being able to store conflicts in commits means that we can always rebase descendants, so there are states like what Mercurial (and Sapling, I think) call "obsolete" and "orphan". There is also no "interrupted rebase" state when you're resolving conflicts.
These things simplify for the user. They also simplify a lot for developers. An example is how I spent about 2 weeks on a `hg amend --into` command for amending the changes in the working copy into an ancestor. I then implemented that in under an hour in jj. Much of the complexity in hg stemmed from dealing with the interrupted states while dealing with conflicts. (Other complexity in hg that jj doesn't have is dealing with a dirty working copy, dealing with concurrent operations, and simply complexity in the APIs for creating new commits in memory.)
[1] IIRC, it took me about a year after I added support for "first-class conflicts" until I figured out that it meant that we should simply always rebase descendants. Jujutsu had a orphans and a `jj evolve` command before then.
Conflict handling is much more useful whenever you actually have conflicts arising regularly. :) For working on JJ itself, this is super useful because it's still actively having tons of code written by 2-3 people, continuously. In other words, the velocity for newly written code is still very high, internal APIs change breaking PRs, etc. I think how often you handle conflicts in a project has a big number of factors. But JJ really does make conflict handling not 1 but like 3-5 times easier, IMO. So you only really need to do it once to be wow-ed, in my opinion. (One of my previous coworkers said something like, and I quote, "This is fucking awesome.")
JJ has an abstract notion of a working copy and even an abstract notion of a backend that stores commits. It is a set of Rust crates, and these are interfaces, so you can just implement the interfaces with whatever backend you want. For example, the working copy can be described in terms of the filesystem (POSIX copy/rename/move files around) or in terms of a virtual filesystem (RPC to a server that tells you to make path x/y/z look like it did at version 123.)
You can absolutely have "cloud commits" like Mononoke/Sapling does, and that is a central feature desired at Google too, where Martin works. Skip to the end of this talk from Git Merge 2024 a few weeks ago, and you can see Martin talk about their version of `jj` used at Google, that interacts with Piper/CitC: https://www.youtube.com/watch?v=LV0JzI8IcCY
My understanding is that the design works something like this (no promises but we may approximate something like this in JJ itself): A central server (Piper) stores all the data. A local pair of daemons runs on your machine: a proxy daemon that talks with the server on your behalf (is used to reduce RPC latency for e.g. commits and writes, and does asynchronous upload) and a virtual filesystem daemon (CitC). The virtual filesystem daemon manages your working copy. It "mounts" the repository at version 123 (the "baseline version") onto a filesystem directory, by talking to the server. Then it tracks changes to the directory as you update files; sort of like OverlayFS does for Docker.
When you make a commit, tell the VFS layer to snapshot the changes between your working copy and the baseline, uploading them to the server. Then tell the backend that you've created version 14 out of those files/blobs. The new version 124 is now your new baseline. Rinse and repeat to create version 125, 126, etc...
The TL;DR on that is the VFS layer manages the working copy. The server/proxy manage interaction with a backend.
OK, monorepo export of subprojects. For the purposes of exporting monorepo subfolders, I'm actively exploring and thinking about "filtering" tools that can be applied to a commit graph to produce a new commit graph. Mononoke at Meta can do something like this. Check out this tool Josh that does it for native Git repositories: https://josh-project.github.io/josh/reference/filters.html
The idea is let's say you have commits A -> B -> C and you want to export a repository that only contains a project you want to export, located under paths path/to/oss/thing. You basically sweep the commit graph and only retain commits that touch path/to/oss/thing. If a commit does not touch this path, remove it from the graph and "stitch" the graph back together. If a commit does touch this path, then keep it.
If a commit touches that path and other paths that don't match, then create a new commit that only touches files under that path. In other words, you might derive a new commit graph A' -> C' where B was thrown away because it touched private code, and A' and C' are "filtered" versions of the originals that retain a subset of their changes. The secret is that this algorithm can be fully deterministic, assuming the input commit graph is "immutable" and cannot be changed.
This idea actually is kind of like applying a MATERIALIZED VIEW to a database. In fact it could actually be a kind of continuous materialized view where a new derived graph is computed incrementally and automatically. But how do you express the filters? The underlying filter is really a way of expressing a functional map between graphs. So should filters only be bijective? What if they're injective? Etc. I haven't fully figured this out.
So anyway, I've never really worked in gamedev or huge monorepo orgs before, but these are all problems I've acutely felt, and Git never really solved. So, I can say that yes, we are at least thinking about these things and more.
> whenever you actually have conflicts arising regularly.
I mean I work at Meta. We have lots of people working on stuff at the same time. :) AraxisMerge auto-merges quite nicely. Text conflicts have just never been a meaningful issue for me in my 15+ year career.
Now binary conflicts are a source of pain! Unfortunately require Perforce style file-locking once you hit a team size.
> I've never really worked in gamedev or huge monorepo orgs before, but these are all problems I've acutely felt, and Git never really solved. So, I can say that yes, we are at least thinking about these things and more.
Good to hear! Yeah the workflow for subproject syncing it's super obvious. It definitely can be done but will require a lot of thinking and a few iterations.
Interesting. I'm not a professional developer and also love fossil for toy/hobby projects. I find it fascinating that this well crafted, solid piece of software is so forgotten, and that git has just rolled over everything.
Honestly you could sell me the best alternative to Git that exists and I still wouldn’t switch, or even try it. I don’t care enough about my versioning system to use something else than what everybody uses. Ubiquity beats convenience.
What I care about is the tooling around it: GitHub and its ecosystem mainly. I also want my open source projects to be on GitHub specifically, and I don’t want to ask contributors to use something other than Git.
Totally respect you wanting to not care about tools, but for the latter part, I do agree with you, which is why I like jj: it uses a git repo as its backing store, so your projects can live on GitHub and all the rest of your collaborators can use git. Nobody else needs to know or care.
Yeah, I was also wondering 'Traditional JJ or BJJ?'
I've never really messed around with VCSes other than git and Subversion, there's people who really like Mercurial though, so I wonder how jj compares to that.
Same. I miss the old times when people tried naming their projects sensibly. I mean, we're constantly telling ourselves how variable and function names should speak for themselves, but then we name our projects using random, completely non-descript names. It's a annoying.
Which old times are you referring to / what are "sensible" names?
I thought about it and I don't know what a better name would be. Off the top of my Head, I know Perforce, BitWarden, Subversion, fossil and git. And then the abbreviations CVS, RCS and SVN.
I don't just mean version control systems, but since you mentioned them: CVS (concurrent version system), rcs (revision control system) and subversion all seem fairly descriptive to me?
Jujutsu is terrible in my opinion. people hate the index, but I think they just dont get it. to me a commit is something that is ready to push, and the index is for stuff that is done but not ready to push. just because I wrote one line that I am happy with, doesn't mean I am ready to commit and push that. I prefer to add stuff thats done, then when enough is done I can commit and push. if you remove the index it makes it too easy to push half done stuff
OK but again thats awful, because @ IS A COMMIT, so you are only a "git push" away from accidentally pushing arbitrary garbage instead of proper changes
You need to think commits as of a non-depleting commodity. And named-branches (bookmarks) as something that is explicitly set to newer commits/changes.
You are simply misinformed. `git push` always pushes the HEAD by default without question, but jj does not. @ is not special in any way, so it does not get special treatment from push. It will only push branches you tell it to or that have been amended/updated and are not immutable. jj also does not update any branch pointers (or "bookmarks" as we call them) unless you explicitly ask it to. You have to explicitly run `jj branch set ...` and then run `jj git push` to get the behavior you are describing, which is much more explicit.
> The very first character at the top left is an @. @ is a special name for "whichever commit the working copy reflects." At first I kind of thought about it like HEAD in git, but that's not correct: HEAD is the most recent commit, but @ represents the working copy, which may be "dirty" from git's point of view. This is our first glimpse into the power of the index-less workflow, though we'll explore that fully in the next chapter. For this moment, just realize that we have one less concept, but haven't actually lost any of its power.
OK but again thats awful, because @ IS A COMMIT, so you are only a "git push" away from accidentally pushing arbitrary garbage instead of proper changes
Bookmarks in JJ are closer to tags than branches. They aren’t updated whenever you add new commits on top of the bookmark.
If you assign your bookmark to the current commit, yes, and I have done that before. But it’s very easy to not do that, and if you push the wrong commit (a WIP commit, let’s say) you can reassign the bookmark and then push again. It’s not a big deal to push arbitrary garbage to the remote if you do, and it’s not common that I do.
On the flip side, jj has been remarkably useful at ensuring I never ever, ever lose any of my work. Ever. It’s not an issue that often, but when it is, oh thank god
No, because `jj git push` only pushes bookmarks. If you `jj git push -c @` or create a bookmark, yeah, you will push the current commit, but then you're asking for that pretty explicitly.