Hacker News new | ask | show | jobs
by chasinglogic 720 days ago
> Using Jujutsu, “amending a commit” also produces a new commit object, as in Git, but the new commit has the same change ID as the original.

This is confusing to me, though to be fair I'm a "git expert" by trade. If you're amending a commit surely the "change" has changed so the change ID should also change? If the "change" isn't tracking the actual changes then what could it be tracking?

Overall I think this is just more confusing than using git but I think it's cool that people are building alternative clients. That's definitely the way to go if you want adoption.

Making history manipulation easier seems like a bit of a recipe for disaster given my experience training people. That old XKCD about git comes to mind and honestly that's where most people stay, if you bother to learn it then things like Jujitsu are probably harder to use for you. If you aren't interested in learning git to that level then I doubt you want / need something like Jujitsu.

For those curious the "multiple branches" at a time thing they're selling can be done with git, IMO easily, using worktrees: https://git-scm.com/docs/git-worktree

8 comments

> If you're amending a commit surely the "change" has changed so the change ID should also change? If the "change" isn't tracking the actual changes then what could it be tracking?

The author is using newer terminology around "changes", but I prefer the older "revisions", as being less overloaded. But yes, the revision/change ID remains the same even if the commits underneath changes. `jj obslog` will show you the history of commits underlying a revision. This stability is what we want when rebasing, and git doesn't provide it.

> Making history manipulation easier seems like a bit of a recipe for disaster ...

I used to think this too, but it was really due to git and its CLI. Under jj, history manipulation is easy, consistent, and easily reversible with `jj undo`. Because it's safer and easier, I routinely do way more rebasing, and stacked PRs are much less painful to incorporate feedback on. Basically, it makes git's more advanced operations feel like everyday tools.

(Of course, jj doesn't fix the problem of rewriting history that's already been shared with other people, but even there, its notion of immutable commits tries to stop you from breaking other people's histories.)

> the "multiple branches" at a time thing they're selling can be done with git, IMO easily, using worktrees

Worktrees aren't quite the same thing the author is describing. Worktrees allow you to check out multiple branches at the same time to different directories, but they're still sort of separate.

The author is making the working revision a merge revision where every parent is a branch they want to work on. This allows them to see what the code will do when those branches are merged. They can also add revisions to all branches simultaneously by working on the merge rev, and using `jj squash` to choose which parent branch to push work to on the fly. When done for the day, `jj abandon` the merge commit. AFAICT, it's both lighter and more flexible than worktrees.

imo revision is worse.

I feel like the best terms are patch id and patch revision id.

I mean none of the options are great, imo.

- "commit" is overloaded with git, and jj still uses commits for other things under the hood. - "patch id" is overloaded with patch files, and jj still uses git's snapshots, not patches (unlike darcs/pijul, iiuc) - "patch revision id" isn't bad, but it's a bit wordy - "change id" just seems vague, since it's unclear where one change begins and another ends

"revision" at least captures the idea that you are revising the same piece of functionality, but then you might expect each snapshot/commit to be a different revision, and not have the same ID, which also isn't quite right.

I am sad I read this, because patch is perfect, but I doubt they will change the language again.
patch sounds too specific... like an actual patch file tied to the actual contents of the patch.

change is probably the right word, you want to change something, the exact operations of the change (multiple revisions of different patches) can evolve over time.

Maybe because I have never used an actual patch file, but patch just feels right to me. As an end user, a patch is an intentional delta blob resulting in some difference to the software. Writing software is just organizing those deltas. If I need to cherry pick between branches, pulling a patch from one to another feels more right than “changes” as a collective object.

Oh well, naming things is hard.

Think of the change ID as a "symbolic name", as opposed to the commit ID which identifies a particular snapshot.

As you amend a commit, it creates a new commit ID each time. Only the commit ID of the most recent amendment is in your final graph. All the old ones are orphaned for garbage collection.

The change ID never changes as you make amendments (because you're still working on the same change), and this change ID will always be in the graph. So you can refer to the change ID (which doesn't change) instead of the commit ID (which changes with every amendment).

Another way to think of it would be akin to filenames vs inodes in a filesystem (it's not 100% the same, but the concept should help you visualize). If you delete a file and create another one with the same name, its filename will be the same, but its inode number will be different because it's technically a different file. The old inode gets marked deleted so that it can be reaped somehow. If you make a symbolic link to the file, you'll always get the intended one (because a symbolic link refers to a path). If you make a hard link to the file, you'll get an outdated file after something replaces it (because a hard link refers to an inode).

There’s still a git-compatible commit ID which changes along with the contents. There’s also an immutable revision/change identifier that persists even as you continue working on it.

This works extremely well in practice and makes rebase-heavy workflows practical even when collaborating with others.

  …can be done with git, IMO easily, using worktrees
Like many things in git, the capability exists, but I would not call worktrees easy. The few times I have tried to play with worktrees resulted in enough friction that it felt safer to use a clone in a separate directory.
I’ve also run into problems with tools that aren’t worktree aware so often that I’ve stopped using it.

I’ve been using jujutsu for about 6 months now, and the only time I’ve reached back for git was when I had to rebase and amend someone else’s branch to get it merged (when they weren’t available to do so themselves of course).

Switching between changes in jujutsu has been a pleasant experience for me thus far, although I’m not as good with it as I was with stacked-git to keep local only changes (things I’m hacking to match my workflow / local setup) out of change sets.

The way it displays diffs is also still something I am getting used to, and have made plenty of mistakes when pulling in changes from trunk. That’s probably more of a case of “old dog new tricks” than jujutsu.

Yeah, after the first month of jj, I abandoned git forever, because it's already so much better. There are some hiccups, though.

I switched over to colocation for all repos, because too many things expect git directories to be where they expect.

I think the revset language is cool and powerful, but if I'm honest, it's tempting me to spend too much time trying to master, when 99% of the time all I need is, "show me the nearby ancestors and descendants within k revisions".

I think the diffs need work. Or I need to get comfy with 3-way diffs. It's unfamiliar, and an obstacle to fixing conflicts. Luckily I get maybe 1/10th the conflicts I used to under git.

> I think the revset language is cool and powerful, but if I'm honest, it's tempting me to spend too much time trying to master, when 99% of the time all I need is, "show me the nearby ancestors and descendants within k revisions".

I just spend enough time to write a new function for what I want to do, and then just know the basics for regular day to day stuff. I feel like that gets me really far.

Yeah, I've done that as well. I wonder if the revset docs should be split into "Basic" and "Advanced" sections.
> I think the diffs need work. Or I need to get comfy with 3-way diffs. It's unfamiliar, and an obstacle to fixing conflicts.

You should get comfy, you won't regret it. I haven't got around to trying jj yet, but I use them in git; frequently see people messing things up or just having a hard time resolving a conflict that they wouldn't if they used (& understood) diff3.

In brief: a regular 2-way diff shows you the current state, and what you wanted to change to right? Well 3-way just adds an extra bit of information (the middle) which shows you from what state you were changing to the bottom.

So say you have:

  <<<<<< HEAD
  def wazzle(widget):
    try:
      widget.wazzle()
    except Exception:
      return False
    return True
  |||||||
  def wazzle(widget):
    widget.wazzle()
  =======
  def wazzle(widget):
    from wazzler import wazzle
    wazzle(widget)
  >>>>>>> (deadbeef Abstract wazzle implementation to own package)
If you didn't have the middle, it might not be at all clear why you were getting the conflict, and what the appropriate fix is. It allows you to see that Ah ok, master (or whatever I'm rebasing on or whatever) has changed to return a bool indicating success or error, that's fine, I was just trying to change the wazzle method to pass it to a library function instead.

Or you might have it that the same change is already in the HEAD part at the top, but there's a conflict because they put the import elsewhere. The middle then allows you to see that you were making essentially the same change, you don't care where the import goes (or like their idea better), you can just remove it and stick with the changes on HEAD.

My point is that it's strictly more information, that can help or make it easier to resolve the conflict. It shouldn't be confusing at all, because the same 2-part thing you're accustomed to is there too.

Others have explained the change IDs already in detail, but I want to try to give a short and sweet explanation: changes in Jujutsu are a mutable, high level abstraction built on gits immutable commits. Change IDs are stable across mutations.
Sounds like how Mercurial handles commits!
The people who work on Jujutsu explicitly draw a lot of inspiration from Mercurial :)
worktrees are pretty great but they make some things a bit more complicated conceptually compared to just checking out two copies for some people
TIL about worktrees, a feature I've needed in the past but didn't expect to even exist.
Jj also supports worktrees, though they call them workspaces.