1. 56
  1.  

    1. 25

      Btw, thanks to everyone in Lobster who helped named the absorb command back in the day.

      1. 7

        You can also use the absorb feature in git with https://github.com/tummychow/git-absorb I’m using it a lot at work and it’s super nice to have when jujutsu is off the table.

        1. 4

          Does git absorb work past merge commits (i.e. can it absorb into different ancestor commits that aren’t ancestors of each other)? It’s not clear from the docs.

          1. 4

            IIRC it stops at merge commits.

        2. 3

          Is there a version of jj absorb which creates a fixup commit instead of modifying a target in place, such that the fix can be reviewed more easily before being absorbed (or not)?

          An other thing i was wondering about, because it’s one of the major annoyances of git: does jujutsu have at least some understanding of the dependencies between commits and does it use that during rebase? Specifically, when using git rebase -i it’s very easy to move a commit before one of its dependencies, which creates cascading conflicts. Once upon a time, Darcs handled that by using a graph of patches with dependencies, so leaving aside that you wouldn’t usually need to reorder commits (because ordering was only between dependencies) it would know not to let you invert dependencies.

          1. 8

            You can use jj op show -p afterwards to see which changes moved where. Use jj undo if you’re not happy with it.

            Regarding the second question, jj absorb doesn’t create conflicts. There are or course other commands that move commits around and they can cause conflicts. Not sure if that answers your question.

            1. 1

              There are or course other commands that move commits around and they can cause conflicts.

              Yes, those were the ones I was asking about. jj seems to have a rebase command which allows moving commits around, does it improve on that aspect of rebase or does it have the same issue.

              1. 8

                We don’t try to prevent rebases that cause conflicts. However, as you may have heard, jj treats conflicts quite differently. You can reorder again to get rid of the conflicts, for example. I think it’s easiest to understand by just trying it.

            2. 3

              Is there a version of jj absorb which creates a fixup commit instead of modifying a target in place, such that the fix can be reviewed more easily before being absorbed (or not)?

              There was some related discussion about a mode for the jj fix command that would avoid immediately modifying commits, but instead leave “fixup”-style commits in the graph for review:

              In principle, to recover the “preview” workflow, you could write tooling to apply the absorb speculatively, observe the resulting commit hashes, and either not apply or revert the change.

              • Undoable transactions are already a kind of primitive in jj.
                • Today, you can already review the diff between an old and new version of a change with change ID X with something like: jj interdiff --from 'at_operation(@-, X)' --to X.
                • Then you can just undo the last operation with jj undo if you don’t like it.
                • [tangential] My preference for UI design would be to primarily rely on flexible experimentation and undo like this, rather than use previews/confirmation as the default paradigm.
              • I assume that jj absorb doesn’t already support templated output for the result, so that would have to be implemented.
              • Or you’d have to infer the commits that might be affected and check them in post-processing.
                • I suppose you could even diff the entire commit graph before and after an arbitrary operation, and create a fully-general previewing tool.

              An other thing i was wondering about, because it’s one of the major annoyances of git: does jujutsu have at least some understanding of the dependencies between commits and does it use that during rebase?

              Generally speaking in jj, the user is expected to encode dependencies explicitly in the DAG via the “parent” relation:

              • If you were to adhere to this strictly, it would result in complicated merging topologies.
                • [tangential] Many organizations choose to linearize the shared commit graph and lose dependency information.
              • jj intends to support merge-heavy workflows better than Git, hence the “mega-merge” workflow, and the workflow in this article.

              jj does not generally track dependencies on the content level like Darcs (in my best understanding):

              • This matches the Git/Mercurial style DAG-based DVCS model.
              • In any event, there can be semantic dependencies not represented by the textual merge relations, so you could argue that you would want explicit dependencies anyways.
              • But jj absorb does operate under the textual dependency model in some sense, in that it will try to find textual dependencies as part of determining the absorb target.

              jj currently uses standard hunk-based heuristics for most diff/merge operations, plus an additional algebra for graph operations themselves:

              • This makes it likely to conflict spuriously when reordering commits in the same way as you might see in Git.
              • However, the change algebra (which is symbolic rather than content-based like Darcs) ensures that if A <- B did not have conflicts in B, then B' <- A' won’t have conflicts in A', even if B' does gain a conflict.
                • This ends up being a nice quality of life feature for those who reorder commits often, so that you can defer unimportant conflict resolution until later in development.
              • Unlike most jj commands, I believe that jj absorb uses special heuristics to avoid conflicts, but I didn’t follow development closely enough to determine if they’re the same as Mercurial/Sapling’s (weave-based) or different.

              Once upon a time, Darcs handled that by using a graph of patches with dependencies, so leaving aside that you wouldn’t usually need to reorder commits (because ordering was only between dependencies) it would know not to let you invert dependencies.

              I think the jj UI could be enhanced to provide more contextual information to handle conflict scenarios more flexibly:

              • For example, all operations today succeed even if they introduce conflicts. You might prefer to abort an operation if it would introduce conflicts.
              • I suspect that some jj GUIs today handle it pretty well from a UX perspective because you can just drag-and-drop or reorder the commits, and the rebase usually completes immediately, so you would know of any conflicts immediately and could undo the operation.
                • Probably the only way to streamline that more would be surface speculative contextual conflict information before the rebase destination is even known. Like if you select a commit, the UI could highlight all other non-conflicting rebase destination commits in the graph.
              1. 1

                This ends up being a nice quality of life feature for those who reorder commits often, so that you can defer unimportant conflict resolution until later in development.

                I’ve read about this idea of deferring/not having to resolve conflicts a few times now, what does it mean? Wouldn’t you be unable to build/meaningfully run code that has unresolved conflicts?

                1. 6

                  Indeed you would not… in the conflicted commits. There are two key bits that are likely not obvious there, though:

                  1. You do not have to fix it while in the middle of a rebase, whether that’s an explicit rebase or one happening implicitly because of editing a commit. This is a big difference from Git. Rebases happen transparently and often with many idiomatic workflows in Jujutsu, whereas you only end up with rebase-caused conflicts while explicitly doing a rebase and cannot proceed until. But because Jujutsu doesn’t force you to deal with them right away, that’s fine in a way it is not in Git. This is related to the fact that Git basically requires you to work on the tip of a branch all the time. Jujutsu does not. Which takes us to:

                  2. Depending on where the conflict is and what commit you are working on, you may not be working on a conflicted commit. This is a huge difference from Git that is mostly invisible until you’re actually used to using Jujutsu a bit. Given that @ is “where I am at right now”, this is a totally normal and reasonable state to be in when using Jujutsu:

                    A -- B -- C -- D
                        (@)
                    

                    So is this:

                    A -- B -- C -- D
                          \
                           Q
                          (@)
                    

                    This means that you might have conflicts and C and D but it doesn’t matter while you’re working on B or Q! You can then go fix up the conflict in C (which in many cases will also fix whatever is wrong in D!) at your leisure, after you finish working on B or Q or both.

                    Edit to add: Both of these are topologies which can appear in Git, but in Git if you modify B, producing B′, the C -- D branch will not be attached to B′, but will instead still be on the original B, so the branches will have diverged and you will have to manually rebase them later. This defers the appearance of the conflicts, but does not prevent them from appearing. Because Jujutsu will automatically rebase the C -- D branch onto B′, the conflict becomes visible immediately, but you do not have to resolve it immediately.

                    You can also still get the Git workflow if you want to, but it is something you would opt into by doing something like jj duplicate B, which might produce K, then jj rebase -b C -d K, and then making changes on the original B. You would end up with this topology:

                      K -- C -- D
                     /
                    A
                     \
                      B -- Q
                          (@)
                    

                    Notice that, again, you can do what Git does, but you can also do things Git does not support.

                  So we Jujutsu-users/promoters need to show this kind of thing in practice more, because “rebase” is so much more general than it is in Git!

                  1. 2

                    Thanks for the detailed response! Your example with duplicate also answered something else I’d been wondering about.

                    1. 4

                      Gladly! A few discussions like this one have crystallized for me the need to start writing up and demoing a lot more of these kinds of things that speak less to the differences and more to the commonalities, so people can see that as a matter of daily experience you can basically just switch. The cool extras and simplifications in workflow are important to keep showing (they’re a reason to switch), but we also need a bunch of things that just say “Oh, and by the way, the normal day-to-day stuff is easy.”

                      1. 3

                        I’ve had a rough experience so far because what I’ve found so far either describes high level jj workflows or offers some direct low level differences between git and jj. It’s not clear how to translate one high level workflow to another. For instance with git I just keep multiple branches around and rebase them individually. I read the git comparison and I couldn’t really figure out what I should be doing instead (indeed, following the docs on bookmarks to try to do the exact same thing would end up with a terrible workflow). I would not have thought to use a megamerge if not for this article providing a clear example.

                        1. 2

                          Overall, I would say that jj doesn’t currently have good workflows for interacting with remote repos:

                          • This manifests as weird, high-friction branch workflows, among others.
                          • There are some configuration options to make things more natural for Git workflows (auto-advancing branches?) but you have to spend time and energy figuring them out.

                          Many of the major time-saving workflows are for complicated local “patch stack” manipulation. I suspect that for a prospective Git user:

                          • If they use these extensively already in Git, then they immediately see the benefits of jj workflows, and don’t encounter the high-friction ones.
                          • If they don’t, then they may not see any “game-changing” benefits of jj, as opposed to just a mishmash of quality-of-life improvements.

                          Feel free to drop by the Discord server to workshop some workflows.

                  2. 4

                    Besides @chriskrycho’s answer, which gives an example of deferring conflict resolution in descendant commits while you’re working on ancestor commits, it’s also possible to do the reverse in certain situations: defer the resolution of ancestor commits while you’re on a descendant commit.

                    That is, it’s possible for an ancestor commit to have a conflict, but for that conflict to be resolved in the working-copy commit.

                    This arises in the same situations where rebasing all of N commits would produce a merge conflict in one of the intermediate commits, but performing a merge or squashing all the rebased commits would merge cleanly. In Jujutsu, the rebase procedure includes more metadata about the source commits, in such a way that it can determine how to merge a descendant commit cleanly without necessarily merging the ancestor commits cleanly. There’s more detail here: https://jj-vcs.github.io/jj/v0.24.0/technical/conflicts/

                    1. 1

                      I think that might explain why a conflict seemed to stick around after I had resolved it. I guess I’d have to squash into or edit the ancestor commit if I wanted to resolve it there?

                      1. 2

                        Yes, that sounds plausible. You would have clean descendant commits but conflicted ancestor commits. My workflow:

                        • I usually switch to the first conflicted commit in the branch and resolve it there.
                          • I always use jj new + jj squash for this purpose, but you can also use jj edit directly at your preference.
                          • Once edited, jj will rebase the descendant commits automatically and reapply their patches, either resulting in more merge conflicts to resolve in the descendant commits, or they’ll all be clean.
                        • In principle, you can start from a head (tip) commit, try to resolve the conflicts there, and squash into a distant ancestor.
                          • But there’s no guarantee that the conflicts you resolved in the head commit themselves are the same as the ancestor commit, so doing this may result in even more incomprehensible merge conflicts.
                          • Therefore, I don’t personally do this because I expect I would make more of a mess of things.
                2. 2

                  If you’re using a branch based review structure, I’d imagine you’d absorb after reviews.

                  If you’re using a patch based review system like gerrit, you could absorb before, as it would be able to show you each version of the patches.

                  1. 2

                    The question is about reviewing absorb’s work. I assume jj works like git absorb / autofixup by looking at the textual overlap of changes, so it can associate change C with change A even though there was an intermediate change B which would be a better graft target, but change B happened not to touch lines close enough to C for a textual lookup to link them together.

                    git absorb / autofixup create fixup commits, so it’s easy enough to see what that encompasses and which commit it got linked to. I’m asking if it’s possible to easily review what jj absorb did.

                    1. 1

                      Ah, then Martin already answered, my bad :)

                3. 1

                  One thing I’m still confused about with jj is how code review works if I’m the only person using jj and the rest of my team is using git via GitHub. Here’s an example:

                  • I do some work, and push a sequence of commits to GitHub. If I remember correctly part of this process is creating a bookmark?
                  • I open a PR.
                  • I get some review suggestions.
                  • Normally I would make some fixup commits and push those so the reviewer can see how I’ve addressed the suggestions. (*)
                  • Once review is done I rebase and merge.

                  It’s the (*) step that I’m unsure about. With jj it seems like the natural workflow would be to just edit the relevant change ID without adding new commits. At that point you would do something like a force push? I’m not at a computer at the moment to verify, but if I remember correctly a force push does something weird to GitHub comments, like orphaning them (I can’t remember). So what’s the jj solution for this scenario?

                  1. 4

                    There’s no requirement for a specific flow. Doing what you do works just fine. Editing an existing change would work just as if you were rebasing the fix up commits into their parents with git.

                    Yeah, if you force push GitHub will collapse the comments, which is why a lot of people push fix up commits and only create the “clean” versions before merge. This is the step where tools like absorb make life a bit easier.

                    1. 1

                      I’ve always considered collapsing the dealt-with comments one of the benefits of force pushing…

                      1. 6

                        Not everyone considers the conversation “dealt with” until it’s gotten a re-review. And sometimes you may have two different conceptual reviews of the same code, and have only fixed one so far.

                    2. 4

                      Sibling comments touch on the fact that you really don’t have to change anything about that workflow if you don’t want to, so I want to instead flag up a meta point here (and maybe I’ll note this in future posts more explicitly—heck, I might turn the rest of this comment into a “notes”-style entry [edit: did just that]):

                      A lot of the focus in write-ups about jj right now (certainly including mine!) is on what you can do in jj which is difficult or impossible in Git. By nature, that means it focuses on things which are different from your normal/existing Git workflows. That can be a bit misleading, though, because it can implicitly suggest that the “normal” Git work flow is difficult or problematic in some way! In point of fact, basically all of the work flows folks are used to from Git are equally doable, and often simpler, in Jujutsu. It’s just that they seem less notable, not only in terms of things I think to share—“oh, this would be a neat thing for people to see!”—but also in terms of what people are likely to read. “Jujutsu does the same thing as Git with roughly the same number of commands” is not exactly likely to get a lot of discussion here, for example. That said, I think I will make a point to incorporate some of that in future posts (and, hopefully, videos) to help people see more clearly that they’re not giving up things from Git but rather keeping the things Git offers and also gaining others.

                      1. 4

                        I have a fork of the spr tool for this with jj support: https://github.com/sunshowers/spr

                        It’s fine and I use it everyday at work, but it doesn’t quite meet the quality standards of my published projects – that’s why it’s hosted in my personal workspace.

                        1. 3

                          I believe that’s addressed in the documentation at https://jj-vcs.github.io/jj/latest/github/#addressing-review-comments ?

                          1. 3

                            The other commenter points out the documentation, but it depends on your team’s workflow. Whether you use rebase+force-push, or whether you create fixup commits. Sadly my workplace is heavily regulated and basically disabled force pushing everywhere (even non-“production” branches) so rebase workflows are impossible for me to adopt. In your case, you would just create new commits like before.

                            1. 1

                              Can you delete branches 😈

                              1. 3

                                Yes, jj supports that.

                                NB: In jj, branches are called bookmarks (because a branch grows, but a bookmark stays in one spot; in git, a branch will grow as you add commits by default, but in jj, a bookmark will not automatically move by default, unless tracking a remote branch)

                                1. 1

                                  In my workplace? Yes. Ironically, the hooks only block activity from the git client, not the Bitbucket UI.

                              2. 2

                                If you don’t want to edit existing commits and force-push you don’t have to. You can create some new changes on top of the previous ones, run jj bookmark set <branch-name> -r <change-id> and jj git push again to add them to the branch.

                                If you want to fix them up later you can run jj squash -r <fixup-change-id> --into <change-id>. I believe you could also use absorb to do that more or less automagically, but I haven’t tried it yet.

                              3. 1

                                i’ve been seeing soo many jj posts here for the past months and it really got me curious

                                i tried using it on my small project but in general it’s been very hard to grasp what exactly is going on

                                i find it very hard to get use to other vcs after using git for the entirety of my programming journey, any tips?

                                1. 5

                                  Stick with it a couple weeks?

                                  Also, it’s worth checking out Chris’s first jj post: https://v5.chriskrycho.com/essays/jj-init/ And Steve’s tutorial: https://steveklabnik.github.io/jujutsu-tutorial/

                                  Lastly, it helps to understand that revisions are the same thing as git commits, but that a change is a list of revisions changing over time. This one confused me for a bit when I got started.

                                  1. 3

                                    Steve’s tutorial is definitely better than my essay for building a mental model—I intentionally structured that essay as a way of building interest, whereas Steve structured his tutorial as, well, a tutorial!

                                    I’d also say: join the Discord if you can! It’s a really friendly group of folks, and questions are welcomed, and a lot of times on projects like this (and certainly including jj!) the kinds of questions people see come up a lot turn into better docs, new features, bug fixes, or all of the above. The kinds of questions/discussions I see go by also often turn into new/more blog posts from me.

                                  2. 3

                                    My suggestion for learning purposes would be to avoid using branches/bookmarks except where necessary to push to a git remote:

                                    • This might help to get used to some of the jj workflows that aren’t as common in Git, such as modifying commits without branches pointing to them (like detached HEAD mode).
                                    • This helps avoid the incidental existing rough edges around working with remote repositories (such as branches not advancing when you commit).

                                    Generally, I recommend you run jj log regularly to visualize your repo state, and configure the default log revset to suit your preferences.