Git Is Not Fine

54 points by diktomat a month ago on lobsters | 47 comments

Would've been nice to show how jj solves the issues presented in the article. I assume it's obvious for jj users but they're probably also not the target audience.

polywolf | a month ago

Honestly I'm fine with it being solely git-focused. Too often with jj articles the author will excitedly tell the reader about this new awesome model they found, complete with many command examples, but that makes my eyes glaze over because I haven't had the same revelations yet.

Having this be just "here are the problems, learn about jj thru other means to solve them" is good, keeps the scope contained and helps the flow.

yawaramin | a month ago

I've never needed the things that the article claims git can't do as justification why git is not fine. Is it just me?

sunshowers | a month ago

You're not alone by any means. One of the most important things about tools is that they're part of dynamic systems. What a tool lets you do feeds back into what you believe you can do, which in turn feeds into your belief about the tool, which shapes how the tool evolves.

When a tool shakes up the status quo, your beliefs about what you can do shift, as do your expectations.

yawaramin | a month ago

OK but then maybe these kinds of articles should explain why I need those things they claim I do. Because when I have my beliefs it's hard to re-evaluate them by just seeing 'oh wow jj can do this cool new thing'.

sunshowers | a month ago

I'm not going to comment on the everything in the article (I'm not sure that's what you're looking for, anyway). But I can tell you why I use, and have spent a significant amount of my career, working on stacked diffs/PRs: it is to let developers communicate with the machine and with each other through smaller, atomic, bisectable units of code. I genuinely believe it is a better way to build software in teams, and I'm not alone in that belief.

yawaramin | a month ago

But...git can do stacked PRs too. It even added support with updateRefs: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---update-refs

I understand it's difficult to come up with compelling justifications that force people to say something is genuinely innovative. Sometimes the new tool just doesn't have one!

sunshowers | a month ago

updateRefs was indirectly inspired by a lot of the work we did at Meta to enable stacked PRs. But also, Git's support for this workflow is overall quite bad compared to Jujutsu. There are several things that come together to make this a reality.

My testimonial at https://docs.jj-vcs.dev/latest/testimonials/ (first one) covers some of the specifics.

I understand it's difficult to come up with compelling justifications that force people to say something is genuinely innovative.

I think, as a former professional in this specific niche, that Jujutsu is genuinely innovative in many ways. I'm not going to "force" you to agree, though. That would be quite silly. All I can do is say what I believe.

quasi_qua_quasi | a month ago

update-refs requires you to explicitly name the "furthest" branch. With jj it'll automatically update all downstream changes of the one you edit, and it does this automatically. So if you're looking at PR 2/5 and you notice you made a typo, you can just edit your change via jj squash or whatever and it'll automatically update 3, 4 and 5.

lhearachel | a month ago

...doesn't git already do this? I'm confused. Maybe not if you made use of the typo in commit 3, but wouldn't you have recognized the typo before finishing commit 3, if that were the case?

quasi_qua_quasi | a month ago

Often times I'll have three PRs (and I'll want to change something in the first one. git absorb makes this somewhat easier, but it's nice to be able to explicitly say "modify this one commit and its descendants" (and jj absorb is built-in).

nicoco | a month ago

Just as I am working on a few stacked PRs and after reading the article, I was thinking that I should really give jj a try... But with your comment and another, I discovered --update-refs and git absorb so it does not feel like I need to try just yet. ^^

MatejKafka | a month ago

I'll give an example for this part:

While finishing up this feature on device, you encounter a bug. It doesn’t block the changes, but it’s making development annoying. So you stash your work, switch to a new branch, create a repro test, and fix it

I work on infra and tooling for our dev team. Occasionally, I have an idea about some tweak in our tooling but I'm not sure if it will work in practice. The best way I found is to make the change, keep it locally for a few weeks, work on other stuff in the meantime and see how it feels. Using a single branch with all of the WIP features lets me use these tweaks.

I also tend to work in a somewhat unusual way where I typically have many different changes in-progress and switch between them quite often, e.g.:

  • I work on one issue, hit a snag and need to ask a colleague about something, so I send a message and then switch to working on a new feature.
  • Colleague pings me about a CI issue, so I fix it and go back to the previous feature work.
  • Eventually, I realize that the new feature will interact weirdly with some other WIP feature, so I switch to finish it up before going back to the original feature.

I tend to do all of this in a single working tree, and only when a feature/fix is finished do I commit it (and then possibly sit on the change for a bit while working on other stuff and occasionally amending it). I use commits only for organizing my work and publishing PRs, and I never explicitly use branches, except when pushing some cherry-picked commits into one to open a new PR.

yawaramin | a month ago

Thanks for the example. Tbh that does seem a bit unusual. I get the feeling that people using fairly niche and complex workflows are getting a lot of mileage out of jj. I’m not sure most developers working in more straightforward trunk-based dev styles would.

In fact the big idea of jj makes it incompatible with a very important workflow for many, maybe most, devs: pre-commit hooks. Love them or hate them, they are a super important part of many teams’ workflows.

In particular, many teams that take security seriously need them to prevent accidentally committing secrets in their repos. Once a secret is committed it’s very tricky trying to prevent it from being pushed. Any secret pushed into a remote host has to be considered potentially compromised and needs to be rotated. Afaik jj doesn’t have pre-commit hooks because of its very design.

hongminhee | a month ago

This is the first Jujutsu article that made me want to install it. The split-mutability framing explains the appeal better than “jj makes rebasing less painful.”

I'm curious how it compares to older systems like Darcs. Darcs had serious performance problems, but the theory of patches was trying to solve something Git still makes awkward. Does jj supersede that approach, or is it solving a different problem?

dvogel | a month ago

Darcs was great. I continued to use it for some personal projects well after adopting git for work. What part of the theory-of-patches ideas are still not used in git? AFAICT those ideas were adopted into git's combination of rebase and merge algorithms.

hongminhee | a month ago

I don't know the theory of patches well enough to say what else Git missed. But patch identity, where the same patch can still be recognized after being moved or replayed, never made it into Git. Git's cherry-pick just creates a new commit with no memory of the original.

Jujutsu's change IDs might be the closest thing. I'm curious what happens when jj is using the Git backend: since Git has no equivalent concept, does that identity just live in jj's layer on top?

sunshowers | a month ago

The change ID is added to the commit metadata. For example:

$ git cat-file commit HEAD
tree d0b0a0e77fbdc1d23e738ef11acd91dc3131fd95
parent 44df821d39e027f316a54a91f0477833aef8969f
author Rain <rain@sunshowers.io> 1778957691 -0700
committer Rain <rain@sunshowers.io> 1778957825 -0700
change-id ltmynxrozonvksxkwsupvprzqmxmutmo

[nextest-runner] generate and add schema for user config

Similar to the recently-added schema for repository config.

koala | a month ago

IMHO, change-ids are the key feature in jj.

Gerrit actually had them (as a trailer), it's also what enabled much of its greatness. I assume all stacked PR tools have some equivalent.

steveklabnik | a month ago

The jj, gerrit, and git butler folks have spoken a bit about trying to unify these so all of the systems can use the same thing, there's some work to do there of course, but it's a nice thing to see.

toastal | a month ago

Things today aren’t as dire as they were in the early 2000s. The failings of pre-git VCS tools were pretty obvious.

Darcs was before Git & has some fundamental fixes to part of the snapshot-based VCS issues. It lost out initially based on bad performance, but the performance improved, but folks didn’t check back in. There are other VCSs out there too doing interesting things… just make sure you don’t treat this as “if not Git, then Jujutsu” as the only argument—which I see too often.

k749gtnc9l3w | a month ago

It's a bit funny how author basically says that the data model part of Git is very good, then mentions in passing that a workflow is not nice because Git's branches are just pointers to the end-of-branch. It's a data model problem, too!

i do trunk oriented development, and mostly small teams, so i find most of the jj stuff uncompelling

there is a feature that interests me, though: your working copy always has a commit hash

several times, i've wanted to write a tool that uses commit hashes as an ID for something, but it needs a second way of handling the working copy for local use, or it needs to make a fake commit for you, etc.

the fact that jj exists, means tools of that shape are much more viable

runxiyu | a month ago

I would really love to try jj in more detail, after the dealbreaker disappears (it currently lacks support for SHA-256 repositories). The points on the workflows are, imo, correct.

dvogel | a month ago

But you probably won’t if you think git is fine. And that’s unfortunate, because git is not fine.

I dislike plenty about git. I did try j. A couple times. Both at work and on a personal project. The author doesn't define "fine" here but git is fine in the disappointed acceptance meaning simply because there's never been a time when I couldn't do what I wanted to do with it.

My main feeling is that jj is also fine in the disappointed acceptance meaning. It has some UI affordances that git doesn't. It also has plenty of warts too. I don't think one is better than the other for that majority of software dev work. Preferences between them are based on personal style. That is also fine in the disappointed acceptance way.

Git is absolutely marvelous in one particular way. It completely obviated all open source source control systems that came before it (maybe closed source too, my experience there is lacking). CVS and SVN are essentially gone. Even more modern projects like Mercurial are fading away. This is such a rare feat that it truly is a marvelous accomplishment.

I have really mixed feelings about jj in this regard. On the one hand, I love their ambition. I love that they make no bones about intending to be the only VCS frontend you'll need. On the other hand, I don't yet see any evidence that they are making something so truly ahead of git that they will replace it. So we are probably in xkcd #927 Standards territory. Which is fine, I guess.

radio | a month ago

Sounds interesting but the diagrams make me dizzy.

oftenwrong | a month ago

On a different topic: As shown in the diagrams, the local repository has both 'trunk' and 'origin/trunk'. I know this is the typical setup, but I question why. Is there any real purpose to maintaining a local 'trunk' ref? I essentially always work against the remote tracking ref, the equivalent of 'origin/trunk'. For example, I create new working branches off of the remote ref. If I want to work with the latest revision, I detach HEAD onto 'origin/trunk'. I have seen many developers make mistakes that are partly caused by things like having a local trunk branch that is behind, or diverged from upstream. It seems simpler to not have a local 'trunk' branch. One less moving piece.

sugaryboa | a month ago

It's an excellent article, and I see where jj is really better than git.

However, "stacked PRs" seem to be a thing which is really needed for big complex organisations with a lot of approval steps. Fine, a great use-case, but I suspect pretty niche.

MatejKafka | a month ago

Or you're working with someone in a different timezone. Or you're making a bit change and want to make it easier to review by splitting it into multiple stacked commits, but it doesn't make sense to merge it as multiple PRs.

sugaryboa | a month ago

Or you're working with someone in a different timezone.

Working with someone in a different TZ is usually just fine, as long as your PR can "hang" for a couple of days. In most companies I worked for, PRs would take about a weeks to merge (basically everyone merges just before the weekly team meeting), so it is not much of an issue. I agree that it is a valid use-case, just not that common.

Or you're making a bit change and want to make it easier to review by splitting it into multiple stacked commits

Our system allows reviewing commits instead of PR-branches. So people who really care review them for each commit in a PR independently. Usually, though, it doesn't work, because a review for C2 is usually just "fix your C1 first".

Say you’re collaborating with someone in a faraway time zone. You don’t want to merge anything without getting their review first.

That sounds to me like the real problem and it does not call for a technical solution. Of course, organizational or architectural solution are usually not available to developers, so technical solutions are their only way to work around such issues. So I can see how jj can help here. Still, I consider it a workaround.

Why are wp/bugfix and wp/refactor not parallel branches?

Why are wp/bugfix and wp/refactor not parallel branches?

The article says the refactor cleans up the same part of the code as the bug fix.

It’s better if the unit of review is a (short) branch so that this kind of cleanup commit can be reviewed together with the change that provoked it, which helps reviewers to make better use of their working memory. And there’s less busy-work needed to cross-reference a series of related work, which can be a lot of overhead for small cleanup commits.

Some of the popularity of review-per-commit is to work around limitations of GitHub. By contrast, GitLab is much better at reviewing multi-commit rebasing branches. On the other hand Gerrit requires a review-per-commit workflow. The question of how you manage work-in-progress branches is more a question of how your code review system is used, and that’s a combination of technical and organizational: the workflows it is technically capable of supporting and which workflow is in use by your organization.

sigmonsez | a month ago

i still prefer git, the author seems to have a bias.

runxiyu | a month ago

(Stating an opposite preference and claiming that the author has bias, while the author clearly demonstrated arguments that are at least compelling to some people, while providing no reasons for the alleged bias, is not super productive.)

roryokane | a month ago

Do you prefer Git because you never run into these situations the author says are difficult in Git, and Git is already familiar to you? Or do you run into these situations but think Git actually supports a better workflow for them than Jujutsu?

edwintorok | a month ago

You can mix git with jj, as long as you remember to run jj new (git will always point to the parent commit, and sees the current jj commit as the working tree/uncommitted).

That is how I learned jj: used jj for the things it is good at (handling rebases, moving around the tree, etc.). And I kept using git commands for everyday tasks, where I haven't learned the jj equivalent yet, or where I remember the git command first, e.g. git blame).

I've only started to appreciate how jj is better by actually using it day to day. Just reading about it made me think "maybe I don't need that feature", or "I can already do this with git".

There are downsides to jj. If you don't have an up-to-date .gitignore you'll end up accidentally adding binary files to your commits (fortunately jj warns if you attempt to add a very large file, but smaller ones may slip through). Also if you had a trace/logfile in the current dir while debugging something it may end up there. So worth reviewing all the diffstats when you are done manipulating the tree. Can be especially problematic if you use jj to bisect and it ends up testing a commit before the one that updates gitiignore. Maybe bisect should have a rewdonly mode.

sugaryboa | a month ago

Also, Fossil is still being developed and has its own view why it is better than Git.

LenFalken | a month ago

willhbr | a month ago

Your working copy commit can have multiple parents (ie act like a merge commit) with 'jj new A B', so your working copy will have the changes from both parents, and you can build off the merge or amend into either commit, etc.

LenFalken | a month ago

Does it only work with 2 commits? Can it work with 3 or more?

altano | a month ago

bjeanes | a month ago

Arbitrary number. This is actually quite common and has come to be known as a "megamerge"

roryokane | a month ago

jj new can take any number of parents when creating the working copy commit, just like git merge can.

If you’re wondering what commands you would use after you make changes on top of the merged working copy, here’s my understanding (I have read a lot about jj but not had the chance to use it):

  • To amend changes into one of the parent commits, the general command is jj squash --into <revision>. If you don’t already know how to identify the revision, jj log would help you identify a short identifier for the commit you have in mind, such as mnw. If your changes are to the same files changed in only one of the parent commits, you could use the simpler command jj absorb without having to specify a revision.
  • To build off the merge instead, you would optionally jj describe to give your change a commit message, then jj new to create and start editing a new, empty change on top of the change you just described.

swaits | a month ago

jj commit is shorthand for describe then new.

willhbr | a month ago

It can work with any number in theory, but I think in practice it's limited to 100 or so (I vaguely remember seeing this discussed and I think the limit is somewhat arbitrary)

matheusmoreira | a month ago

Is there a reason why git can't do this right now? Or is it just not automatic?

LenFalken | a month ago

That's what I'm wondering too!