Does JJ really prefer for me to think backwards? It wants me to start with the new and describe command, but with git I first make the changes and name the changeset at the end of the workflow.
I also often end up with in a dirty repo state with multiple changes belonging to separate features or abstractions. I usually just pick the changes I want to group into a commit and clean up the state.
Since it's git compatible, it feels like it must work to add files and keep files uncommitted, but just by reading this tutorial I'm unsure.
> Does JJ really prefer for me to think backwards? It wants me to start with the new and describe command, but with git I first make the changes and name the changeset at the end of the workflow.
A good way to think of it is that jj new is an empty git staging area. There's still a `jj commit` command that allows you to desc then jj new.
> I also often end up with in a dirty repo state with multiple changes belonging to separate features or abstractions. I usually just pick the changes I want to group into a commit and clean up the state.
jj split allows you do to this pretty well.
> Since it's git compatible, it feels like it must work to add files and keep files uncommitted, but just by reading this tutorial I'm unsure.
In jj you always have a commit - it's just sometimes empty, sometimes full, has a stable changeid regardless. jj treats the commit as a calculated value based on the contents of your folder etc, rather than the unit of change.
> A good way to think of it is that jj new is an empty git staging area. There's still a `jj commit` command that allows you to desc then jj new.
This always made me feel uncomfy using `jj`. Something that I didn't realise for a while is that `jj` automatically cleans up/garbage collects empty commits. I don't write as much code as I used to, but I still have to interact with, debug and test our product a _lot_ in order to support other engineers, so my workflow was effectively:
git checkout master
git fetch
git rebase # can be just git pull but I've always preferred doing this independently
_work_/investigate
git checkout HEAD ./the-project # cleanup the things I changed while investigating
```
Running `jj new master@origin` felt odd because I was creating a commit, but... when I realised that those commits don't last, things felt better. When I then realised that if I made a change or two while investigating, that these were basically stashed for free, it actually improved my workflow. I don't often have to go back to them, but knowing that they're there has been nice!
I think calling them "commits" is doing it a disservice because it's not the same as git commits, and the differences confuse people coming from git. I'd say "jj changes are like git commits, except they're mutable, so you can freely move edits between them. They only become immutable when you push/share them with people"..
It's a mouthful, but it's more accurate and may be less confusing.
I'm using jj exactly this way, but `jj commit -i` is still somewhat backwards compared to `git commit -i`: jj displays the commit timestamp by default instead of the author timestamp like git. In addition, in jj the author timestamp of a commit is set to the time you started and not ended a commit/change. This results in unexpected timestamps when working with git-using people or tools. Also, it's rather weird if you use a previously empty commit for your work which was created months earlier by a previous `jj commit`, resulting in a timestamp neither correlating to when you started nor ended your work.
I guess the idea of jj's authors is that jj's commits are far more squishy and can always be changed, so a fixed finished timestamp makes less sense. I still prefer git's behaviour, marking work as finished and then keep the author (but not commit) timestamps on amends.
I use this jj alias to get git's timestamp behaviour:
[aliases]
c = ["util", "exec", "--", "bash", "-c", """
set -euo pipefail
change_id=$(jj log -r @ --no-graph -T 'change_id')
desc=$(jj log -r $change_id --no-graph -T 'description')
commit_author=$(jj log -r $change_id --no-graph -T 'author.email()')
configured_author=$(jj config get user.email)
jj commit -i "$@"
if [ -z "$desc" ] && [ z"$commit_author" = z"$configured_author" ]; then
echo "Adjusting author date"
jj metaedit --update-author-timestamp --quiet $change_id
fi
"""]
[templates]
# display author timestamp instead of commit timestamp in log
'commit_timestamp(commit)' = 'commit.author().timestamp()'
I often will use `jj new -B@` (which I made an alias for) followed by `jj squash -i` to split changes. I had no idea about `jj split`, so I need look into that!
jj is very flexible when it comes to workflow. One thing to note is that commits don't have to have messages. What I tend to do is to run `jj new` frequently while I work on something and leave all of them without messages. Then when I'm ready to make my actual commit, I squash the temporary commits together and then add a message. If my changes are separable, I can split the commits before squashing. This workflow acts as a kind of undo history. I can easily go back to what I had 5 minutes ago and try a different approach, but then also jump back to my original changes if I want. It makes experimentation much easier compared to git.
`jj new` simply means "create a new commit [ontop of <location>]" - you don't have to describe it immediately. I never do.
I know that the intention was to do that, and I tried forcing the habit, but I too found it counter-productive to invariably end up re-writing the description.
I don't usually do that right away, but I often use squash or absorb to move additional changes into a commit I already made in my stack. I think the spirit still applies if you take that course.
This is me! I often find that in the process of making one change, I have also made several other changes, and only recognize that they are distinct after following the ideas to their natural conclusion.
Hence I have multiple workspaces, and I shelve changes a lot (IntelliJ. I end up with dirty repos too and that can be painful to cherry-pick from. Sometimes I just create a git patch so I can squirrel the diffs into a tmp file while I cleanup the commit candidate. I often let changes sit for several days while I work on something else so that I can come back and decide if it’s actually right.
It’s chaotic and I hide all this from coworkers in a bid to seem just a bit more professional.
I admire people who are very deliberate and plan ahead. But I need to get the code under my fingers before I have conviction about it.
I'm about the same. jj is kind of perfect for that. Example:
# I've finished something significant! Carve it out from the working "change" as its own commit.
`jj commit --interactive` # aka `jj commit -i` or `jj split`, depending on how you prefer to think of it: making a commit for some work, or splitting a separate commit out of the working change.
# Oops, missed a piece.
`jj squash --interactive` # aka `jj squash -i`
# Let me look at what's left.
`jj diff`
# Oh right, I had started working on something else. I could just leave it in the working change, but let me separate it out into its own commit even though it's unfinished, since I can always add pieces to it later.
`jj commit -i`
# Wait, no, I kind of want it to come before that thing I finished up. Shoot, I messed up.
`jj undo`
# Let me try that again, this time putting it underneath.
`jj split -B @-` # aka `jj split --insert-before @-`. @ is the working change, @- is its immediate parent(s), @-- is all grandparents, etc.
# Note that instead of undoing and re-selecting the parts, you could also `jj rebase -r @- -B @--` to reorder. And in practice, you'll often be doing `jj log` to see what things are and using their change ids instead of things like `@--`.
# I also have some logging code I don't need anymore. Let me discard it.
`jj diffedit`
# Do some more work. I have some additions to that part I thought was done.
`jj squash -i`
# And some additions to that other part.
`jj squash -i --into @--`
# etc.
There's a lot more that you could do, but once you internalize the ideas that (1) everything is a commit, and (2) commits (and changes) can have multiple parents thus form a DAG, then almost everything else you want to do becomes an obvious application of a small handful of core commands.
Note: to figure out how to use the built-in diff viewer, you'll need to hover over the menu with the mouse, but you really just need f for fold/unfold and j/k for movement, then space for toggle.
# I've finished something significant! Carve it out from the working "change" as its own commit.
git add -p
git commit
# Oops, missed a piece.
git add -p
git commit --amend
# Let me look at what's left.
git diff
# Oh right, I had started working on something else. I could just leave it in the working change, but let me separate it out into its own commit even though it's unfinished, since I can always add pieces to it later.
git add -p
git commit
# Wait, no, I kind of want it to come before that thing I finished up. i didn't mess up, this is standard procedure
git rebase -i <some older commit ref> # put commits into the desired order.
# I also have some logging code I don't need anymore. Let me discard it.
don't know what jj does here
edit files
or
git revert -p
# Do some more work. I have some additions to that part I thought was done.
git add -p
git commit
git rebase -i <some older commit ref> # choose the right place and then tell git to squash into the parent
# And some additions to that other part.
git add -p
git commit
git rebase -i <some older commit ref> # as above
you list a number of different commands that i do in git always with the same sequence of commands.
i don't see how jj makes those examples any easier. it looks like maybe they help you get away with not understanding how git works, by instead giving you more commands that do specifically what you want.
Yes, and? I wasn't trying to demonstrate jj superiority. I was responding to a post about a normal messy workflow and showing how to handle it in jj.
If I'm writing a description of how to use jj, I could take several different approaches. Am I writing for a git novice? A git expert? An expert in a different VCS? A novice in any VCS? And even within those, there's a big difference in whether you're a solo dev working alone on their own project, a solo dev working across multiple systems, a random github contributor working alone against a github repo, a group of contributors who work together before landing something in an upstream repo, or whatever. And then, it matters whether my objective is to show that jj is somehow superior, or to just show to accomplish something.
Those are going to require rather different approaches. I was not going for "jj is better than git". I was aiming more for "here's how straightforward it is to do the sort of stuff you're talking about". Even with the example actions I described, jj does have a couple of advantages that I didn't highlight: first, your git equivalents would require looking up commit hashes whereas in jj I tend to remember the recent change ids that I've been working with. Second, `jj undo` (and its stronger variant, `jj op restore`) is easier and simpler to work with than the reflog. A longer example would have demonstrated that, but I didn't want a longer example.
But I have no dispute with your assertion that this workflow is not harder in git. I could write a description of why I think jj is better than git, it's just that my post was not that. (I could also write a post about how jj is still missing some important functionality that git has, and therefore git is better than jj.)
But just to sketch out what I would use if I wanted to make git look bad, I'd probably use an example of multiple independent lines of development where I want to work off of a tree with all of them applied at the same time, without artificially linearizing them because I don't know what order reviews are going to come in, and then doing fixups and incorporating new changes that turn out to conflict, and not getting stuck working through conflicts in the other patch series when I'm actively working out the finishing touches on one of them that turns out to be high priority. And then getting some of that wrong and wanting to back up and try a different path. All while carrying along some temporary logging or debugging changes, and perhaps some configuration changes that I don't ever want pushed. And keeping my patch series clean by separating out refactoring from changes, even when I actually do bits of that refactoring or those changes out of order. And doing all this without risking modifying anything other people might be using or building off of, by preventing force pushes on stuff that matters without preventing it for in-development stuff that is only relevant to me. And in the middle of this, wanting to look back on what the state of things was last Wednesday, including the whole commit graph.
All of that is possible with both git and jujutsu. In practice, I wouldn't even try much of it with git. Perhaps I just suck at git? Very possible. I'm better with mercurial, but I wouldn't do a lot of that there either. I won't say all of that is trivial with jj, but some of it is easy, all of it is doable without thinking too hard, it's the sort of stuff that arises for me quite often as I'm working, and none of it requires more than the same handful of commands and concepts. I know what changes are tentative and what are more fixed without juggling commits vs staging vs stash, and I can freely move bits and pieces between them using the same set of commands. I could do the exact same things in git, but I wouldn't. The core git data model is very nice, so it's pretty clear what can and can't be done. jj gives me the ability to manipulate it without tangling my head or my repo in knots.
i apologize if my comment came across as accusing you of claiming superiority and failing your intention. it was not meant to do that. if there is any accusation then it is the general assumption that jj is better that can be felt in the overall tone of this discussion thread.
your examples show a particular aspect of jj, and to me they demonstrate that jj isn't better across the board for a certain group of users at least.
i'd be interested to know if i got the impression right that jj provides more commands for specific actions that don't require you to understand how the system works underneath. that is significant because i like to understand how things work underneath. more commands then means that i have to learn more to understand each one of them.
in a sense it is like high level languages vs low level languages. don't get me wrong, i like high level languages. i prefer them actually, but i also like minimalistic syntax, so i prefer smalltalk and lisp over, say scala which has a reputation for being complex. but scala is starting to make more sense once i learn what lower level primitives the syntax translates to.
same goes for jj. i want to understand the primitives that the commands translate to. git puts me closer to those primitives, which makes it harder to learn but possibly easier to use or understand once you get it. since jj is built on top of the same primitives it should be possible to reach the same understanding. jj uses those primitives differently, and that's the part that i find interesting. the more i understand how jj works with git primitives the more i like it. but it also shows that git primitives are not bad if they enable such different approaches. (i wonder how different git primitives are from other approaches such as pijul which is based on a theory of patches).
i guess what would help me would be a guide to jj for those who are familiar with how git works underneath.
the examples in your second to last paragraph sound very interesting. please show. of link to references if you know any.
Heh, sorry, I guess you triggered my defensive "book-length response" reaction. Here's the next book:
As you say, jj uses git primives so the core data model is the same. I say "core" because jj subtracts out the staging area and stash and adds in "changes" and the operations log. But otherwise, your understanding of the git data model translates seamlessly, with one exception: git-style branches aren't really a thing, they can only be emulated. What I mean is that in git, the topological head that is associated with a branch ref (apologies if I'm getting terminology wrong), when accessed through a branch name, is taken to represent not just that commit but all ancestors of it as well. So "merging a branch" or "checking out a branch" are natural things to do.
jj prefers to be grounded in the commit DAG. Since you're always "updating" a commit (really replacing it with new ones), the only other state is the location of that commit in the DAG. You specify that before that change exists, with the default being the same as in git: the single descendant of the last thing you were working on (the branch tip in git, the @ change in jj). `jj new` (or `jj commit`) creates a single child change of your current change, unless you pass flags putting it elsewhere in the graph -- and it is very common to put it elsewhere if that's the thing you want to do.
An example: say you have a sequence of changes (and thus commits) where you're working on a feature: A->B->C, with C being what you're editing now (the @ change). You realize that your latest change, C, might not be the best approach and decide to try another. You do `jj new B` to create an alternate descendant and implement a C2 approach. Now you have A->B->{C,C2}. Which of C or C2 is the tip of your "feature branch"? Answer: who cares? At least until you want to push it somewhere. My default log command will show the whole DAG rooted at A, the first change off of "trunk" commits. I might do further work by creating descendants of C2 and thus have A->B->C and B->C2->D->E, and I might rebase or duplicate ->D->E on top of C if I want to try it out with that approach. Or I might inject or modify things earlier in the graph like A->X->B'->Y->{C,C2} (where B' is an updated B). If I push part of that DAG up to a server that I have declared to be shared with other people, then jj will know what parts of the graph should no longer be mutated and prevent me from doing that (by default).
All of this follows the core git model, it's just that mutating the graph structure is expected and common with jj and only restricted modifications seem to be common with git. You can do any and all of it with git, but rebase, undo, and conflicts are better-supported and feel less exceptional with jj. And you can jump to anywhere in the graph, or move around pieces of the graph, anytime and without necessarily changing the @ (working change) you're looking at.
This also means that jj doesn't need a `git switch` or `git checkout` command. That notion is replaced by making your current change (there's always a current change, it's the one that will be associated with any edits you make) be the child of wherever in the DAG you want, probably with `jj new <parent>`. As with git, there's a snapshot associated with it, so the other purpose of `git checkout` (to make your on-disk copy of a file match the contents of a given commit) is `jj restore filename`. In `git help checkout`, it says "Switch branches or restore working tree files". The "switch branches" part is `jj new`, the "restore working tree files" part is `jj restore`. `jj new` is equivalent to `git checkout --detach` or `git checkout <branch> <start-commit>` (or plain `git checkout <treeish>`). If you only consider the "make (part or all of) the working tree match that of this commit", then all of the different variants `git checkout` being in one command makes sense. But jj distinguishes things that operate on the DAG vs trees (file contents). And it doesn't need different flags or modes for the index/stage vs a commit; everything is a commit. `git stash` is unnecessary, the equivalent is `jj new <ancestor>` (you're just moving your working directory to be based on a different commit; the previous patches aren't lost or anything, they stay where they were in the DAG. Grab them from there if you want them and put them anywhere else in the graph, with or without changing your working directory as you wish.) `git reset` is redundant with `jj restore` -- well, mostly; the other part of what it does is handled by `jj abandon`. (Again, `restore` for tree contents, `abandon` for the graph node.) `git merge` is `jj new` with multiple parents instead of one so a command isn't needed.
One way to say it: with jj, `new` + `describe` + `rebase` + `abandon`, you can do all the graph manipulation. Add in `squash` + `restore` and you can do all content manipulation too. That's it for local operation. Well, maybe `jj file track/untrack`. The flags are pretty consistent across commands and don't fundamentally change what they do. Everything else is either for working with servers and other repositories, convenience utilities for specific situations, details like configuration, or jj-specific functionality like the operation log.
The other major difference is that jj adds in the notion of persistent "changes" (I wish we'd come up with a better name) that in normal operation correspond to a single "visible" commit, but which commit that is changes over time. A change has a single semantic purpose and doesn't lose its identity when rebased or merged or the contents of its files are changed. This difference is actually starting to diminish, because git (or rather, the git forges and tooling) has started preserving the footers that identify these change IDs over various operations. So if you're using git in connection with gitbutler or gerrit or whatever, you'll be getting the benefit of these persistent identifiers even if you continue using the git cli.
Sorry, that's a lot, but it'd be my attempt at "jj fundamentals for someone who understands git primitives well".
removing the staging area and the stash and "emulating" them with real commits for one simplifies the model, but it also upgrades them to first class citizens because you can treat them like every other commit. i think this adds a lot of power to manipulate the history. on the other hand changes and operations log don't add new primitives but are just additional pointers to track the history.
so in summary: less complexity, more tracking, cleaner commands (like restore instead of checkout to fix files)
somewhere a comment stated that git history is more static because it is not so easy to move commits around. i never felt that. i made copious use of rebase and interactive rebase to shove commits around as i liked. in the end i know that a rebase is successful when the diff between the old state and the new state is empty. but you have to understand how git works in order to feel comfortable doing this. it looks like jj makes this easier because it looks like it adds more safeguards. in git for example i would often tag a state before starting a complex operation, despite the reflog so that i could keep track of the changes and easily see what to throw away if the operation failed, so that i could start over. sometimes i redid a rebase two or three times because the outcome was not what i wanted or i messed up somewhere. it was never a big deal because i know how it works. but it was always something that i felt not everyone had the courage to try.
i installed jj last night and i look forward to an opportunity to try it.
> I often find that in the process of making one change, I have also made several other changes, and only recognize that they are distinct after following the ideas to their natural conclusion.
I do that all the time. With git, everything starts "unstaged", so I'd use magit to selectively stage some parts and turn those into a sequence of commits, one on top of another.
With jj I'd do it "backwards": everything starts off committed (with no commit message), so I'd open the diff (`D` in majutsu), selecting some parts and "split" (`S` in majutsu) to put those into a new commit underneath the remaining changes. Once the different changes are split into separate commits, I'd give each a relevant commit message.
My preferred workflow is to start with a new change, pick the changes I want, then use jj commit to describe the change and create a new empty one on top. Feels very similar to my old git workflow.
If I end up with multiple features or abstractions in one change (equivalent to the “dirty repo”), jj split works very well as an alternative to the git add/git commit/repeat workflow tidying up one’s working copy.
> It wants me to start with the new and describe command
jj doesn't "want" anything.
I always end a piece of work with `new`: it puts an empty, description-less commit as the checked-out HEAD, and is my way of saying "I'm finished with those changes (for now); any subsequent changes to this directory should go in this (currently empty) commit"
The last thing I do to a commit, once all of its contents have settled into something reasonable, is describe it.
In fact, I mostly use `commit` (pressing `C` in majutsu), which combines those two things: it gives the current commit a description, and creates a new empty commit on top.
Personally haven’t used jj but as far as dvcs’s are concerned Fossil is great complement to Git because it does things differently than git and truly has a decentralized feel.
The autosync feature is really nice too, and you can store backup repos in cloud storage folders and auto sync to those as well.
Fossil is delightful and definitely nails a feeling of decentralization that I think we ruined completely with `git` by constantly centering around centralized repositories.
I also find it interesting that so many people want to switch to something that's not `git` but are simultaneously somehow super invested in it being basically just `git`.
Most teams could switch to Fossil and just have a better time overall. It's made for smaller, high-trust teams, `git` is not. Fossil also manages to actually support external contributions just fine; it's just that it's not the default.
I tend to approach jj commits as local PRs. Decide what it is I'm working on, make a new, empty commit on top of that :
jj new -m 'do thing'
jj new
As I work on the "local PR", I `jj squash` my working changes into the named commit. By keeping my working commit description-free, I avoid accidentally pushing it (and potentially broken code) to origin.
Nothing stops you from making changes in a commit that has no description and then at the end doing `jj commit -m` to describe them and make a new commit in one go, which is essentially the same as git. The difference is that it's essentially amending in place as you make changes rather than needing to stage first.
Think of it this way: the current change is like a staging area/index in git. Leave it without a a description while you're working (just like git's staging area). Rely on jj's auto-snapshotting to capture all your changes. Then, when you're ready to do something else, give it a description ("jj describe") and switch to a new blank change ("jj new"), and that becomes your new "staging area"/index.
If you want to have a workflow similar to Git with index, check out the Squash Workflow: basically, you would edit your files in a disposable commit having the same purpose of Git’s index.
> Does JJ really prefer for me to think backwards?
Perhaps it’s the other way around. I have a standard agentic instruction to summarize prompt instructions using jj describe. After it makes changes I can see both the file changes made and a log description of what prompted those changes. When done it’s just jj new and its ready for the next set of prompts.
JJ doesnt prefer you to do anything. I regularly just create description-free commits, do whatever, then name them later (or squash, split, absorb the changes into other commits). It is exceptionally flexible and forgiving. Even moreso if you use jjui, the best TUI ive ever used.
it's actually git that makes you think backwards - in jj the working tree is a commit, in git it isn't until you at least stage it.
the working tree being a commit has wide ranging implications as all the commands that work with commits start working with the working tree by default.
> Does JJ really prefer for me to think backwards? It wants me to start with the new and describe command, but with git I first make the changes and name the changeset at the end of the workflow.
Yes, but this is not backwards, the way you do it in git is backwards. =)
I don't think the term "version control" has any implication about precedence, and I don't understand what you mean by "the versions predate the control". In git, you add items to the worktree (control), then you commit (create a version), so doesn't that mean git does it "wrong" according to what you're saying? In jj, you are always on a committed version and the contents of that commit are controlled by your edits, if you want your edits to be on a different commit, you usually just change to that commit and make the edits, although there are other ways to move edits around (which is also true in git).
The point is that there actually isn't a correct order to do these operations, just one that you're familiar with. Other orders of operations are valid, and may be superior for your or your team's workflow.
> Does JJ really prefer for me to think backwards?
No, you run `jj new` when you’re done with your work just like you’d run `git commit`. You can even just run `jj commit` which is a shorthand for `jj describe && jj new`.
If you're already super comfortable in git, it's not. I'm saying this as someone who recently converted from git to jj and never wants to go back.
You also don't have to follow what the GP said. I never say `jj describe` before writing code. I write the code then just say `jj commit -m "Foo stuff"`, just like I would in git.
The bigger difference I've noticed is:
1. Switching between changesets just feels more natural than git ever did. If I just run `jj` it shows me my tree of commits (think of it like showing you git's branches + their commits), and if I want to edit the code in one of them I just say `jj edit xyz`, or if I want to create a new commit on top of another one and branch it off in a new direction, I just say `jj new xyz`. It took a little bit for my brain to "get" jj and how it works because I was so used to git's branches, but I'm really enjoying the mental model.
2. `jj undo`. This alone is enough to convert me. I screwed something up when trying to sync something and had a bunch of conflicts I really didn't want to resolve and I knew could have been avoided if I did things differently, but my screwup was several operations ago! So I ran `jj undo`. And ran it again. And again. And again. And then I was back to my clean state several stages ago before I screwed up, despite having made several changes and operations since then. With git? Yeah I could have gotten it fixed and gone back. But every time I've had to do something like that in git, I'm only 25% confident I'm doing it right and I'm not screwing things up further.
3. Rebasing. When I would try to sync git to an upstream GitHub repo that used rebasing for PRs, I would always get merge conflicts. This was because I stack my changes on top of each other, but only merge in one at a time. Resyncing means my PR got a new commit hash, even though none of the code changed, and now git couldn't figure out how to merge this new unknown commit with my tree, even though it was the same commit I had locally, just a different hash. With jj? I never get merge conflicts anymore from that.
Overall the developer experience is just more enjoyable for me. I can't say jj's flow is fundamentally and objectively better than git's flow with branches, but personally and subjectively, I like it better.
In sort of the same way juggling apples is better than juggling hand grenades: it's mostly the same in the simple cases, but once you start doing the really fancy stuff, one of the two will get you a lot fewer messy explosions.
(Your question is not dumb, BTW. The pithy answer is: UX matters, but it does so in ways that can be hard to convey since it's about the amount of cognition you need to put in a given thing to get a desired outcome, and that's tricky to express in text. Also there will always be some people for whom a new mental model just doesn't work. That doesn't make them dumb either, at least provided they have the wisdom not to petulantly piss in the cornflakes of those who get a kick out of how much better the new thing works for them.)
Thanks for the answer!
To be honest, maybe because I've been using git for 16 years now, I find jj interface more confusing than git.
All it takes to understand git, to me, is understanding the files and the operations.
There are annoying things for even experienced users like merging the history of merged repos, or sometimes patching a bisect for a bug.
Complex operations in git are complex and often there is nothing that makes them palatable enough to understand without really understanding git internal workings.
I will try once more when I feel my brain is more opened to the ideas jj is proposing.
Thanks again
As a jj user, I would say I had the same experience for an entire year used it on and off, and then I just forced myself to use it, and it left a bad taste on the first week.
But on the second week I bungled a command that did some rebase and stuff and I paniced that I destroyed the work but alas it was all there.
I just hit jj undo and it all was there it snapshotted it all. I fell in love and just kept using it unafraid of making mistakes.
I think the worst part about git is being afraid to bungle up your work, jj makes obvious things obvious, asking someone in git land how to copy a commit as in duplicate it. And they might actually start crying even, if they have been using it for a while.
Generally you just create two branches but what if you want to copy and try to check which branch it would be easier to rebase on to, if you had to rebase.
This is not that hard if you have done it a couple times, but jj makes it so much easier, in big teams I feel like JJ is the only sane choice when git commands can get insane.
Also jj doesn't snapshot large files by default, aka, perfect no one mistakenly commiting a binary again. That is just unhinged when newbies do that and you need to flush that out of your git history.
Just the sane defaults + undo + obvious and simple commands was a huge sell for me and I grit my teeth and learnt it. And ofc in a jj git init repo you can always use git, best part often you can do all the simple operations in jj and then the hard one in git after creating a duplicate of the commit. To ensure nothing gets destroyed beyond recovery, further you can use jj state to recovery bad git state as well. It's all so nice that you will accept doing the harder stuff with git as well.
My pleasure! FWIW I used git for just as long as you, and after a few months with jj I feel more proficient with it than I ever was with git. Somehow it clicked in a way the git commands and options never quite did. But git is not going anywhere either way and is still a good tool, so if it works for you, when it comes down to it, that's the important part.
I’ve done it multiple times without much effort. Or skill. Really it was a skill issue and I tried things that I thought would work but apparently don’t.
I screwed up jj a few times while picking it up, but jj’s undo command made that trivial to go back and try again. With git? I know a lot of things can be undone but I can never remember how to do it!
I'm glad to hear you never encountered the kind of quagmire that can occur around e.g. non-trivial conflicts while rebasing a chain of git commits. On large enough codebases, those can be common.
It's not, you can literally do everything this tool does with Git, and 80% of the features could be replaced with commands in your shell rc file also using vanilla git.
This tool was described perfectly the other day. JJ is the Dvorak of Git. Most people could careless about Dvorak layout, 99.8% of people use qwerty just fine. Those 0.02% though, they're loud, and they want everyone to know how great the reinvention of bread is.
I also often end up with in a dirty repo state with multiple changes belonging to separate features or abstractions. I usually just pick the changes I want to group into a commit and clean up the state.
Since it's git compatible, it feels like it must work to add files and keep files uncommitted, but just by reading this tutorial I'm unsure.