|
|
Log in / Subscribe / Register

Looking forward to Git 2.56 — and 3.0

By Jonathan Corbet
September 18, 2026
The Git source-code management system is at the core of development processes worldwide, so changes, especially incompatible changes, are of great interest to the developers involved. The Git 2.56 release, which can be expected around the end of September, is currently available in release-candidate form. It is not the most earth-shaking of releases, but the one that follows, which might be the long-awaited Git 3.0, may well be.

What's in Git 2.56

The Git 2.56 release contains something over 700 non-merge commits; it brings a number of nice improvements, but will not fundamentally change the Git experience for most users. One feature that offers some potential in that regard is the addition of the drop subcommand to the (still experimental) git history toolbox:

    $ git history drop commit-id

This command will cause the identified commit to be removed from the history of the current branch, replaying all commits that were added after it. It offers an easier way of removing an offending commit. Unfortunately, it still refuses to work if the history contains merge commits, making it unusable for many (or most) repositories.

The git status command will now suggest a git pull command to update a branch that is behind the branch it tracks. It still, though, will say that a branch is "up to date" even if it lags behind an (unfetched) remote tracking branch.

The git refs command exists to manipulate references at a low level; in 2.56, it has gained a number of new subcommands. Those commands, create, delete, update, and rename, perhaps surprisingly for Git, do exactly what their names suggest they would.

There are a number of minor usability tweaks. The new --delete-merged option to git branch will remove local branches that have been merged into their remote tracking branches. An attempt to delete a branch with git branch -d will fail, with a useful message, if that branch is being used for bisection. Attempts to lock the configuration file will be retried on failures, avoiding annoyance when multiple commands try to modify the file at once. git add has a new --resolved option that only adds files with merge conflicts that have been resolved.

Beyond that, there is the usual long list of bug fixes, refactorings, and performance improvements. All told, 2.56 looks like a solid release, but it also shows the signs of a project that is holding back much of its more significant work for the future. That leads to the question of what comes next.

After 2.56?

In early September, Git maintainer Junio Hamano asked the community what the next release should be. Would it be best to put out the 3.0 release that the community has been working toward for some time, finishing the year on a high note? Or, instead, is there a need for one or more 2.x releases still before the 3.0 release can happen?

This is an important question, because 3.0 will contain a number of compatibility breaks that may make some users pause before upgrading. Of those, perhaps the most significant is the switch to using the SHA-256 hash function by default, rather than the SHA-1 hash that Git has used since the beginning. SHA-1 has long been deemed to be weak, which is worrisome for applications like Git. Hashes are used to identify every object (files, directory trees, commits) in the Git repository, and are used to verify the chain of commits leading to any given point. If SHA-1 can be broken, it can conceivably be used to modify the history of a repository in ways that are difficult or impossible to detect.

Git has long included defenses against the known SHA-1 attacks, and few people appear to be seriously worried about the potential for compromised repositories now. Still, it makes a lot of sense to move to a more secure hash function.

Non-experimental support for SHA-256 has existed in Git since the 2.42 release in 2023, though some of the glue for interoperating with older repositories has taken longer. The biggest concern that has kept the Git developers from moving to SHA-256 by default for some time now has been support (or the lack thereof) at the major forge sites. GitLab has had support since 2024, and Forgejo has support as well. The elephant that is still missing from this room, though, is GitHub; releasing a version of Git that creates GitHub-incompatible repositories is a worrisome prospect. It still is not clear when GitHub might add that support, but it is notable that brian m. carlson, a GitHub employee and a key developer behind the SHA-256 transition, responded to Hamano's question by saying that news on that topic was coming, and that having the next release be 3.0 might be the best choice.

That said, carlson has posted one other change that they would like to see added before 3.0 comes out. While Git has always managed hexadecimal numbers (specifically, object IDs) as lower-case strings, it has also accepted upper-case IDs. That leads to situations where two seemingly different IDs (f00f00 and F00F00, say) are actually the same. Seemingly, bugs and security vulnerabilities have arisen from this ambiguity. So carlson wants to change Git's behavior to only accept IDs in lower case. That is a change that, seemingly, should not cause problems for too many users, but there is almost certainly somebody somewhere who relies on that behavior.

Another significant change that has been waiting for the 3.0 release is the switch to the "reftable" mechanism. A reference (more commonly "ref") in Git is an association between a name and an object in the repository; branches, tags, and remotes are all refs, for example. Git currently (by default) stores each ref as a file under .git/refs/ in the repository. If a repository contains a branch called foo, there will be a file, .git/refs/heads/foo, containing the ID of the commit at the head of that branch. There is also a mechanism to pull all of these refs together into a packed-refs file, which increases performance — to a point.

This mechanism works, but becomes increasingly inefficient as the number of refs grows. In some projects, the number of refs does indeed grow. According to the project's reftable documentation, the Android repository contains over 800,000 refs. At that scale, looking up refs, or determining whether any refs pointing to a specific commit exist, can be an expensive operation. Slowing down development can be a yellow-card-level annoyance, leading to a desire to do something about the refs.

The Git 2.45 release added reftables as a more efficient way of storing refs. It is a binary file optimized for both space efficiency and quick access. Since then, it has been possible to create a repository that uses a reftable rather than the old file-based mechanism, but that has never been the default. Switching to reftable should have no visible consequences (other than better performance) for users of Git itself, but it can be a problem for users of other software packages that access Git repositories. In his email, carlson mentioned libgit2 as a potential concern.

As it turns out, libgit2 will not be a blocker here; Patrick Steinhardt let it be known that he has added reftable support to libgit2, and that SHA-256 support had been enabled by default in August. So libgit2 should be ready for the Git 3.0 transition.

Finally, as mentioned by carlson, there is the Rust question. The project has added some trial support for code written in Rust, but has hesitated to make Rust mandatory for the building of Git. That, too, is expected to change in the 3.0 release; after that, any platform that does not have a working Rust compiler will be unable to upgrade.

After listing these concerns, carlson suggested that they probably should not hold up the 3.0 release:

I think anyone else who is not already extremely far along on SHA-256 (and reftable, for software working with local repositories) is likely not worth considering. JGit and Gitoxide were both informed that SHA-256 was coming in Git 3.0 at least a year ago, for instance. (I know because I did the informing.)

Similarly, I am not aware of anyone who is seriously undertaking Rust support for platforms that do not already support it, so I don't think that should be a blocker, either.

That led to their conclusion that, most likely, designating the next release as 3.0 would be the best choice.

What will actually happen has not, yet, been announced publicly; that is waiting for Hamano to make a decision. As he put it: "this is not a popularity contest, nor is it even a democracy". In the nearly two weeks since his query was posted, though, there has been no strong opposition to moving into the 3.x era and leaving some of Git's early design decisions behind (though, naturally, repositories using SHA-1 and lacking reftables will continue to be fully supported). If the 3.0 release does not happen in 2026, it seems certain to show up shortly thereafter.

Update: at Kernel Recipes, Steinhardt described the plan that had been agreed to regarding releases. The December 2026 release will be called 2.98, as a signal that the 3.0 transition is coming. The 2.99 release will follow in April 2027; that will be a long-term-support release. Git 3.0 will also be released at the same time, and will be the base for all subsequent releases.


to post comments

GitHub

Posted Sep 18, 2026 14:34 UTC (Fri) by dskoll (subscriber, #1630) [Link] (7 responses)

Ah, the fact that GitHub doesn't support SHA-256 almost makes me want to go and convert all my repos so they are harder to mirror to GitHub. 😛

GitHub

Posted Sep 18, 2026 17:34 UTC (Fri) by tux3 (subscriber, #101245) [Link] (6 responses)

There was the https://git-merge.com conference yesterday, brian carlson showed a GitHub SHA-256 repo: https://github.com/bk2204/talk-rust-in-git

Sorry/glad to be the bearer of bad/good news!

GitHub

Posted Sep 18, 2026 23:49 UTC (Fri) by himi (subscriber, #340) [Link] (5 responses)

That was one of the stranger experiences I've had with reading slides from a conference presentation . . .

Very interesting to read (and I suspect the actual talk would have been more so) - it's not often you get to see that sort detail about Rust/C interoperability in such a well-established project.

GitHub

Posted Sep 19, 2026 14:19 UTC (Sat) by Paf (subscriber, #91811) [Link] (4 responses)

I tried but couldn’t find the slides there; the site is charming but a bit of an eyesore.

Could you give a link? Thanks!

GitHub

Posted Sep 20, 2026 0:19 UTC (Sun) by himi (subscriber, #340) [Link] (3 responses)

Clone the repo, and follow the instructions in the README - you'll need some ruby tooling (specifically the bundle/bundler tool, which should be part of any distro ruby packages), and you'll need to be willing to install a bunch of shit into the local directory, but the end result is an HTML file you can load with the actual presentation.

GitHub

Posted Sep 20, 2026 1:20 UTC (Sun) by Paf (subscriber, #91811) [Link] (2 responses)

Wow, that’s a … remarkably unhelpful way to distribute slides. I might just for the novelty, but wow.

GitHub

Posted Sep 20, 2026 11:10 UTC (Sun) by am (subscriber, #69042) [Link]

I think you can just open presentation.adoc

GitHub

Posted Sep 21, 2026 7:13 UTC (Mon) by gspr (subscriber, #91542) [Link]

It's not very different from how most FOSS is distributed: in its preferred form of modification, i.e. source code :-)

Submodules with different hash types

Posted Sep 18, 2026 16:42 UTC (Fri) by KJ7RRV (subscriber, #153595) [Link] (20 responses)

Are there any plans for supporting submodules with different hash algorithms from the parent repository? I've been using SHA256 for most new repos, but I had to convert one back to SHA1 because I needed to include an existing GitHub repository as a submodule.

Submodules with different hash types

Posted Sep 21, 2026 4:46 UTC (Mon) by marcH (subscriber, #57642) [Link]

That could be tricky because it would make git tree objects (~= directories) a hybrid of SHA-1 and SHA-256 objects?

Just thinking out loud sorry.

Submodules with different hash types

Posted Sep 21, 2026 10:03 UTC (Mon) by Tobu (subscriber, #24111) [Link]

This is the first thing that blew up for me when trying the repo linked from this comment. This was due to an unusual workflow (every repo I clone gets added to a parent module that indexes everything), but it is the kind of thing that would block popular repos from making the transition to sha256.

"A repo must use sha256 to reference sha256 submodules, but a sha256 repo can also reference sha1 submodules" would at least speed the transition along.

With the current submodule intolerance, the transition could work with every repo having both hashes. The major forges could handle it (configuring extensions.compatObjectFormat and generating v3 pack indexes with both hashes) but it would penalize everybody using git in a more federated way and needing to reference repos outside their control.

Submodules with different hash types

Posted Sep 22, 2026 21:36 UTC (Tue) by bk2204 (subscriber, #177014) [Link] (17 responses)

No, there is no intention to support mixed algorithms in submodules. SHA-1 repositories only allow SHA-1 submodules and SHA-256 repositories only allow SHA-256 submodules. The hash function transition plan doesn't allow mixed hashes except in the mapping from one algorithm to another.

The reason is that the tree format is binary and doesn't allow mixed-size object IDs, so allowing mixed submodules would mean that the main repository would not round-trip across hashes due to truncation or padding of the object ID value.

Submodules with different hash types

Posted Sep 22, 2026 23:19 UTC (Tue) by Cyberax (✭ supporter ✭, #52523) [Link] (3 responses)

It should be possible to retrofit SHA-1 signatures into SHA-256 structures.

Just consider every signature with trailing 96 zero bits as SHA-1. It can't reasonably happen by pure chance.

Submodules with different hash types

Posted Sep 23, 2026 7:22 UTC (Wed) by taladar (subscriber, #68407) [Link] (2 responses)

That seems like the horrible kind of workaround that would come to bite you in unexpected ways a few years down the line.

Submodules with different hash types

Posted Sep 23, 2026 12:45 UTC (Wed) by mathstuf (subscriber, #69389) [Link]

Prefixing all sha256 hashes with g would have made things unambiguous. I suggested this years ago (though, admittedly, not in the best places for upstream action).

Submodules with different hash types

Posted Sep 23, 2026 18:26 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link]

Why? This is an internal detail. It can be refactored later into explicit format selection if needed.

Submodules with different hash types

Posted Sep 22, 2026 23:30 UTC (Tue) by ceplm (subscriber, #41334) [Link]

That's unfortunately quite a problem. See for example https://src.opensuse.org/mcepl/somewm, openSUSE upcoming sources forge, where all packaging repos are sha256 (because of the long term perspective and some certification requirements). There are some plans to support upstream code in a submodule. It would be pity if we could use it only for upstreams which are already in sha256.

Submodules with different hash types

Posted Sep 23, 2026 7:24 UTC (Wed) by taladar (subscriber, #68407) [Link] (7 responses)

What are people who use some sort of common code submodule across many projects supposed to do then? Convert dozens or hundreds of projects to SHA256 in one enormous conversion? That just doesn't seem like something anyone would do, especially if those projects cross administrative boundaries.

Submodules with different hash types

Posted Sep 23, 2026 13:19 UTC (Wed) by mathstuf (subscriber, #69389) [Link] (4 responses)

Perhaps this pushes investigations into something better than submodules…

Submodules with different hash types

Posted Sep 24, 2026 7:41 UTC (Thu) by taladar (subscriber, #68407) [Link] (3 responses)

I have never understood what people dislike about submodules, they do exactly what I want, i.e. allow me to include a shared bit of code with an exact reference to a specific version of said code into multiple repos.

All those attempts to "fix" or replace submodules like git-subtree seem a lot more limited in their applications.

Submodules with different hash types

Posted Sep 24, 2026 13:31 UTC (Thu) by mathstuf (subscriber, #69389) [Link] (2 responses)

I think they can be useful, but they are best when either "everything" is a submodule (e.g., Qt's main repository) or "nothing" is a submodule. Trying to mix bare code with submodule changes is a mess to manage, IMO. A lot of this is that tooling is poor. Examples:

  • git checkout won't, by default, keep submodules up-to-date[^1]
  • git worktree new needs another git submodule --init
  • forges just show a hash diff which is…not directly reviewable

We have tooling to help some of these things:

  • submodule references must be merged into the tracked branch and on its first-parent history
  • CI will fetch from the MR author's fork of each submodule before git submodule update so that submodule changes can be tested without being merged into its main repository
  • git bump is used to craft commit messages (by referring to all merged MRs since the last update and prompting for "why" the submodule is being bumped as well as asking for a summary of other, incidental, changes coming along with it)
  • creating source archives uses git archive-all with some minor massaging to make [attr] declarations in submodules no-ops in between (because git only allows them to be declared at the top-level)

We do have processes for doing subtree imports. This includes:

  • subsetting the repository (e.g., stripping docs and test suites)
  • enforcing that all changes to the imported subdirectory always come from a dedicated history
  • enforcing that the merge from that dedicated history does not involve "evil merges"

It is definitely harder to manage than submodules as a developer doing the update, but for long-term maintenance and day-to-day development, I think it is far easier because the code you need is available right with all the other code. We also use git-fetch-file to pull individual scripts from a common area for things like CI scripts (which have non-overlapping subsets of the complete sets in use across the company's repositories).

[^1]: One place we use submodules has a check that makes lots of noise if the committed submodule commit is not an ancestor of the current checkout's commit to avoid "things aren't working" / "is your submodule up-to-date?" / "no…" cycles.

Submodules with different hash types

Posted Sep 25, 2026 9:19 UTC (Fri) by taladar (subscriber, #68407) [Link] (1 responses)

> Trying to mix bare code with submodule changes is a mess to manage, IMO.

I would disagree here.

We use just that pattern pretty successfully in a lot of projects, often for either common code that e.g. all our dotnet projects share or all our puppet environments share (in a sysadmin setting) or for tooling (e.g. code for making our pass password manager easier to use lives in its own submodule in our pass repo) or tooling config (e.g. custom eslint rules shared across all JS projects).

I think where it breaks down a bit is more if you try to treat submodule changes and parent repo changes as one MR/PR. There some extra tooling could be helpful, yes.

And I would agree that it would be helpful to have tools like git grep and git diff work across the boundary for certain tasks.

Submodules with different hash types

Posted Sep 25, 2026 11:30 UTC (Fri) by pizza (subscriber, #46) [Link]

> And I would agree that it would be helpful to have tools like git grep and git diff work across the boundary for certain tasks.

'git grep' works across submodule boundaries, as do a number of other commands (not 'git diff' though). look for submodule.recurse in 'man gitconfig'

Submodules with different hash types

Posted Sep 23, 2026 17:38 UTC (Wed) by ralfj (subscriber, #172874) [Link] (1 responses)

Yeah, this is a huge problem. The Rust repo has various submodules that aren't even "owned" by us, like LLVM and GCC (for the respective codegen backends). They must either all switch to SHA256 at the same time, or never, or we are kind of screwed.

Submodules with different hash types

Posted Sep 24, 2026 1:23 UTC (Thu) by NYKevin (subscriber, #129325) [Link]

Presumably you do this so that you can pull directly from upstream into the submodule. As a stopgap (i.e. preferably not a long-term solution), you could mirror the upstreams and convert your mirrors to SHA-256. This would still require a process for importing SHA-1 commits from upstream and converting them into SHA-256 in the mirror, but at least it would work. Probably.

Submodules with different hash types

Posted Sep 23, 2026 12:53 UTC (Wed) by pizza (subscriber, #46) [Link] (3 responses)

> No, there is no intention to support mixed algorithms in submodules. SHA-1 repositories only allow SHA-1 submodules and SHA-256 repositories only allow SHA-256 submodules. The hash function transition plan doesn't allow mixed hashes except in the mapping from one algorithm to another.

This seems insane; why not treat the sub-module commit id you're referencing as an arbitrary string? After all, git already stores freeform references for other things.

And as others have pointed out, this limitation means that in practice, sha256 repositories will only ever be feasible if you never [wish to use] use submodules.

Submodules with different hash types

Posted Sep 23, 2026 17:09 UTC (Wed) by mb (subscriber, #50428) [Link] (1 responses)

>why not treat the sub-module commit id you're referencing as an arbitrary string?

It's not just the references.
If I understand submodules correctly then the objects of the main repository and all of its sub repositories live in the same object database under .git.

>And as others have pointed out, this limitation means that in practice,
>sha256 repositories will only ever be feasible if you never [wish to use] use submodules.

Yes, I also think this limitation is extremely unfortunate. And I also didn't know about it until it was mentioned here.

It's basically an unsolvable problem in many use cases (e.g. if you don't own the submodules).
What should I do, if I need one foreign sha1 submodule and one foreign sha256 submodule? I can then only vendor the code. Even asking the maintainer to migrate might be impossible, because there may be other dependencies upstream.

I think I will stay on sha1 for the foreseeable future to at least stay compatible with my own existing repos.

Submodules with different hash types

Posted Sep 23, 2026 19:54 UTC (Wed) by intelfx (subscriber, #130118) [Link]

If I understand submodules correctly then the objects of the main repository and all of its sub repositories live in the same object database under .git.

No, I don’t think that’s right.

They live “under same .git” alright (at least in the default “absorbed” layout), but they do not share objects databases (or any data structures). The gitdirs of submodules can be found under .git/modules/$submodule-id, each complete with its own objects/, refs/, configs and other files.

Submodules with different hash types

Posted Sep 23, 2026 20:31 UTC (Wed) by martinfick (subscriber, #4455) [Link]

Just because a submodule upstream hasn't switched to sha256 doesn't mean you can't 'import' the submodule in sha256 format. The formats are convertible afterall. It's more work, but doable, and a fairly pragmatic solution in my opinion.

One wishlist item I should have raised long ago

Posted Sep 18, 2026 17:06 UTC (Fri) by mathstuf (subscriber, #69389) [Link] (12 responses)

I've long wanted a way to distinguish integration branches from development branches. We currently enforce this by making everyone use fork-based workflows so as to not pollute the main repository (finally acknowledged by forges with their "only copy the HEAD branch" checkbox…that also ignores tags or any active release-maintenance branches). Only recently the obvious solution is to have refs/heads/ for integration branches and refs/topics/ for development branches.

One wishlist item I should have raised long ago

Posted Sep 18, 2026 18:34 UTC (Fri) by acarno (subscriber, #123476) [Link] (11 responses)

I'm not sure I follow the logic here - a branch is a branch, no? The notion of "integration" and "development" branches is a higher-level process distinction, not a technical one. It doesn't quite seem right to embed that high-level distinction into the VCS.

One wishlist item I should have raised long ago

Posted Sep 18, 2026 19:05 UTC (Fri) by mathstuf (subscriber, #69389) [Link] (10 responses)

Effects this would have:

  • git branch -d would refuse to remove integration branches (by default)
  • The ability to ignore "primary" developers' topics when I clone the main repository
  • No-rewind enforcement based on "is integration" instead of doing server-side name-based enforcement

It might not have to be enforced, but if there was the space to have another top-level namespace of refs to do so, defaults would still work well.

Git distinguishes annotated tags from unannotated; I think this is a similar concept. Perhaps it'd just need some kind of associated metadata to distinguish them? branch.<branch>.integration or something?

One wishlist item I should have raised long ago

Posted Sep 18, 2026 19:35 UTC (Fri) by mb (subscriber, #50428) [Link] (3 responses)

This looks more like a forge concept than a low level git concept to me.

One wishlist item I should have raised long ago

Posted Sep 18, 2026 21:24 UTC (Fri) by mathstuf (subscriber, #69389) [Link] (2 responses)

Ok, but I've come to prefer having roots of at least the data model in the base instead of forcing interaction through the forge to enforce things like this.

One wishlist item I should have raised long ago

Posted Sep 19, 2026 0:44 UTC (Sat) by ejr (subscriber, #51652) [Link] (1 responses)

Base git is the plumbing, not the porcelain. There are porcelain pieces nowadays, but they don't define git's model.

One wishlist item I should have raised long ago

Posted Sep 19, 2026 8:50 UTC (Sat) by pm215 (subscriber, #98099) [Link]

Yeah, but it's helpful if the plumbing lets you consistently implement a model that can work regardless of the specific porcelain individual developers prefer. For instance it would be helpful to be able to mark branches as "this is going to rebase or otherwise have history changes" versus "I promise not to rewrite this branch's history". Then the porcelain can default to warning or protecting you from accidentally doing things you didn't intend, flag up the difference in its UI, and so on.

One wishlist item I should have raised long ago

Posted Sep 19, 2026 4:52 UTC (Sat) by intelfx (subscriber, #130118) [Link] (5 responses)

No-rewind enforcement based on "is integration" instead of doing server-side name-based enforcement

Given that you propose "is integration" to ultimately resolve to a ref name prefix check (i.e. "is refs/heads/"), it seems to me that you are suggesting to replace server-side name-based enforcement with... server-side name-based enforcement? :-)

One wishlist item I should have raised long ago

Posted Sep 20, 2026 0:16 UTC (Sun) by mathstuf (subscriber, #69389) [Link] (4 responses)

Yes, the server would, ultimately, still be the enforcer. But your tools can say "hey, that rebase you did looks fishy; did you mean to create a topic instead of rebasing all of release-1.x onto main?" before you push.

One wishlist item I should have raised long ago

Posted Sep 21, 2026 5:13 UTC (Mon) by marcH (subscriber, #57642) [Link]

Your definition of "integration branch" still feels vague but now I wonder whether jj's "[im]mutable" commits would help in your use case?

https://news.ycombinator.com/item?id=42938561

PS: every time something is missing or awkward in jj, you can find it fixed in jj ;-) Except the "all your projects/bases are belong to us" Google CLA - jj's only major regression compared to git :-(

One wishlist item I should have raised long ago

Posted Sep 21, 2026 11:40 UTC (Mon) by iabervon (subscriber, #722) [Link] (2 responses)

I think the core aspect that's missing currently is having the remote able to provide configuration information about its policies with respect to naming of its branches and acceptable operations for them. Last I heard, there isn't a suitable trust level for information that may be from a malicious server but that you'd like to pay attention to anyway.

One wishlist item I should have raised long ago

Posted Sep 22, 2026 7:48 UTC (Tue) by taladar (subscriber, #68407) [Link] (1 responses)

Thinking of it as "the" remote is already wrong in git terms. One local git repository can - and frequently does - have many remotes and there is nothing inherently different between a remote that publishes the repo for general consumption and one that is just on another person's workstation.

One wishlist item I should have raised long ago

Posted Sep 22, 2026 11:49 UTC (Tue) by iabervon (subscriber, #722) [Link]

Certainly. I actually often use local repos with a remote of the common project home and another remote of my backup for WIP development. But there isn't a way for each of these to provide their different policies on branch naming and pre-push checking. By "the remote", I intended to refer to the one you're interacting with in a particular operation; push and fetch only do one at a time, and each remote tracking branch is from a particular remote, so it's often the case that there is one relevant remote at a time. But it's also importantly the case that no remote is relevant; you can have local branches that aren't associated with any remote branches and nobody else's policies would matter. On the other hand, if a local branch is configured to push by default to a remote branch that prohibits rewinding, you probably don't mean to make it not descend from the tracking branch without also changing something else.

refs vs reftables

Posted Sep 19, 2026 1:26 UTC (Sat) by mirabilos (subscriber, #84359) [Link] (1 responses)

What’s the reftables equivalent for 'echo commitid >.git/refs/heads/foo'?

I found some plumbing-or-below things over the years that make working with git much easier (this one in particular is nice for resetting a branch, whether checked-out or not, to a previous commit).

refs vs reftables

Posted Sep 19, 2026 7:26 UTC (Sat) by jrn (subscriber, #64214) [Link]

git update-ref

on leaving behind

Posted Sep 19, 2026 1:27 UTC (Sat) by mirabilos (subscriber, #84359) [Link] (71 responses)

I’ve got to make it known that GNU CVS is still here for y’all on platforms without Rust, and that it will not be taking either Rust or “AI” slop.

-- de-facto CVS maintainer

on leaving behind

Posted Sep 19, 2026 7:35 UTC (Sat) by rrolls (subscriber, #151126) [Link] (60 responses)

Presumably, to people who have been using Git for the past 15+ years and want to avoid Rust, a no-Rust Git fork would be far more useful than a completely different VCS.

on leaving behind

Posted Sep 19, 2026 13:17 UTC (Sat) by josh (subscriber, #17465) [Link] (59 responses)

To people who have been using git and want to keep using it, and happen to be on a platform without Rust, the most useful thing would be a port of Rust. Which would be welcome.

on leaving behind

Posted Sep 19, 2026 16:41 UTC (Sat) by martinfick (subscriber, #4455) [Link] (58 responses)

The 2.99.x releases will be maintained long term without the need for rust.

on leaving behind

Posted Sep 23, 2026 7:29 UTC (Wed) by alx.manpages (subscriber, #145117) [Link] (57 responses)

I have issues with git 3 more than just Rust or LLM usage. The direction seems to be acting more like jj(1) (jujutsu), but the behavior of jj(1) is in general worse than git(1); I don't see why git(1) is trying to follow that.

Something common in most features that imitate jj(1) seem to be that conflict resolution is deferred, and the conflict is just stored in a commit. One problem with that is losing the original branches from which the conflict originated. Those are very useful for resolving conflicts, as sometimes the conflict itself is unreadable, and you can only understand what is meant by looking separately at the two branches that produced the conflict, and seeing what each branch was trying to do.

I think git-rebase(1), which for some reason git(1) developers are now seeing as something weird, is way superior to jj(1)'s way of working. Of course, it won't disappear; however, if it's seen as a bad design from the past, I wonder if the development will make sure it's always as easy.

One related thing is the reflist. Stuff under .git/ will now be less useful, because it will not be text-based anymore. At least, it has saved me when I accidentally broke some repo during some complicated operations, and the only way I was able to resolve it was to go to those text files, and fix a few things. It's not a common operation, but when it's needed, it's a very useful operation. In theory, new plumbing commands might help do the same, but I'm not 100% sure I'd be able to do everything. I understand the speed improvements might make it desirable for some large projects, but I don't think it should be the default. Of course, I can change my defaults to keep using the filesystem-based text-based .git/, but is there guarantee that support won't be eventually removed in some future git-4?

I've seen one feature which seems interesting, though, which is the ability in git-show(1) to compare a merge commit with how it would have been done without human intervention (by replaying the merge in memory), to see how conflicts have been resolved.

Is this worth upgrading to 3.x series? Is the problem worth a git-classic fork? I don't have my own answer, but if I see a git-classic fork I'd probably join. We still have some years until 2.99.x is EOL to decide, so we could fork from there.

on leaving behind

Posted Sep 23, 2026 13:40 UTC (Wed) by mathstuf (subscriber, #69389) [Link] (25 responses)

I think having a reified conflict object that one can pass around is very useful. It let's one resolve some obvious conflict resolution (say, release notes) but able to publish the rest as a conflict for others to assist. Of course, you can just commit the conflict markers and send that around, but I think having it explicit makes it easier to share and build tooling around. I also want a way to reify a review as well for similar reasons: person A reviews part A and lets others "import" their review to "clear" it from their review state.

I also don't really like the jj way of hiding the stage as I use it all the time. I also haven't migrated off of git checkout (to git switch and the other tool that does the "other half" of what git checkout does) yet either…

on leaving behind

Posted Sep 23, 2026 18:29 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link] (24 responses)

Another advantage is being able to postpone the conflict resolution. Just commit the current state and return to it later.

on leaving behind

Posted Sep 24, 2026 14:07 UTC (Thu) by alx.manpages (subscriber, #145117) [Link] (23 responses)

> Another advantage is being able to postpone the conflict resolution.

git worktrees already permit this. If you're on a branch that's
rebasing, and find more conflicts than you expected, but don't want to
abort the rebase, you could leave that worktree on that rebase, and
continue your work in a different worktree. Literally happened to me
last week.

> Just commit the current state and return to it later.

The problem with committing the current state is that you've already
moved the branch, and by moving the branch, you've destroyed the old
branch. Which means you don't have it available anymore to help with
the conflict resolution.

Real-life example:

I currently hold in a project (shadow-utils) almost a hundred branches
(forming some trees, since some are queued after others), with around
half a thousand commits, on top of master. More often than not, merging
one branch introduces conflicts in many other branches, and in some
cases, the conflicts are complex enough that I can't rebase one branch
and all their children in a single session.

When that happens, I leave the worktree on the rebase, and continue
doing other stuff in other worktrees, then come back to working on the
rebase when I find more energy to do the rebase.

One frequent issue is that the conflict has often been caused by more
than one commits in the master branch, and it is easier to abort the
rebase, and do a rebase first on the lastest commit of master that
didn't trigger any conflicts (a binary search can help, trying somewhere
in the middle, and aborting at any conflicts). Many small rebases can
help with difficult conflicts quite often.

When that's not enough, looking at the conflict markers is often
helpless. One needs to understand not just how each branch diverged
(shown in the conflict), but also why (looking at the first commit that
causes the conflict in each of the two branches). I often have
4 terminals open during difficult rebases:

- A graph view of the two conflicting branches, and the current state
of the rebase, with `git log --graph --oneline branchA branchB HEAD`.
This is essential in every rebase operation, even without conflicts.

- A view of the commit from branchA that causes conflict. If we've
already rebased to the latest commit that didn't introduce conflict
(and for difficult rebases, we should have), then the conflicting
commit is the first commit of branchA (assuming we're rebasing
branchB on top of branchA). Thus, `git show commitA`.

- A view of the commit from branchB that causes conflict. The hash of
this commit is shown in the conflict markers. `git show commitB`

- The file with the conflicts, with vim(1), for the actual conflict
resolution.

If you've already committed the conflict and moved the branch with all
their descendants, you've lost the information about commitB that might
be essential for resolving the conflict correctly. The conflict markers
give you a hash of the commit, so if git(1) hasn't garbage-collected
yet, you may be able to have a look at it, though. But you've most
likely not been careful to first find which commit in branchA was the
first that introduced the conflict, and while branchA didn't go away,
you can't find this information anymore.

Moreover, if there are further conflicts in branches that are descendant
of this one, the situation complicates further.

My approach includes having all branches have a remote (even if the
remote is another local repository). That helps keep track of the old
(pre-rebase) branches, and the new (post-rebase) branches, so that once
you've rebased one branch, you can still see its old version in a
--graph log, and that information will be useful again when rebasing
descendants.

Then, I only push all branches after every descendant has been rebased,
so that I don't need to look up the old branches anymore. Another good
consequence of having the old branches around is that I can easily use
git-range-diff(1) to verify that the rebase of each branch makes sense,
after I've finished rebasing them all. I go one by one looking at the
range-diffs at the end. I also publish those range-diffs in the patch
set submissions (currently, a github PR), just in case I made any
mistakes, I can go back and look at what I did, and why it was wrong.

I think publishing conflicts as a commit should be a last resort,
and certainly not a default.

on leaving behind

Posted Sep 24, 2026 14:09 UTC (Thu) by alx.manpages (subscriber, #145117) [Link]

Actually, the screen that shows the log should also show the old states of the branches:

git log --graph --oneline branchA origin/branchA branchB origin/branchB HEAD

on leaving behind

Posted Sep 24, 2026 14:29 UTC (Thu) by mathstuf (subscriber, #69389) [Link] (17 responses)

git worktrees already permit this. If you're on a branch that's rebasing, and find more conflicts than you expected, but don't want to abort the rebase, you could leave that worktree on that rebase, and continue your work in a different worktree. Literally happened to me last week.

Great, now my conflict state is pinned to a directory on a given machine. I didn't say it should be a commit (that is jj's solution though?), but I do want it to be something sharable and distinctly represented by Git's store.

"rebase walks"

There's a tool (I don't recall its name) which supported this kind of "rebase on top of every commit along git log --first-parent base..target to help deal with conflicts incrementally". It reminded me of an enzyme walking the DNA strand until it found a problem (conflicts) or the end (everything is fine).

on leaving behind

Posted Sep 24, 2026 15:07 UTC (Thu) by alx.manpages (subscriber, #145117) [Link] (5 responses)

> Great, now my conflict state is pinned to a directory on a given machine.

Have you ever invested so much in a rebase operation that you really need to pass around the conflict to avoid starting from scratch? I have only had to do that a few times, and since I could eventually work on the same machine, the worktree solution was just fine.

If the same machine is not viable, I think starting from scratch elsewhere won't hurt too much.

If you really need to share the conflict, you can always tag at the conflict, and share the tag (maybe call it 'conflict_rebase_branchA_branchB_20260924'). I think this is sufficiently rare, that a tag is an appropriate solution. I do hundreds of rebase operations per week, and sometimes even per day, and don't recall needing that.

> I didn't say it should be a commit (that is jj's solution though?), but I do want it to be something sharable and distinctly represented by Git's store.

Yeah, I think the tag could work for this. A commit (indeed, jj's solution; and FWIW, git-3's solution too) is just bad for this.

> "rebase walks"

I think this would indeed be something I'd like git(1) to work on. I think `git rebase --walk` could be implemented to do this. It would probably be much more helpful to resolve conflicts than imitating jj(1).

FWIW, I'll try to see if I can develop a git alias that is simple enough. I'll share if I find something that works reliably.

on leaving behind

Posted Sep 24, 2026 15:27 UTC (Thu) by alx.manpages (subscriber, #145117) [Link] (1 responses)

"rebase walks"

$ git merge-base HEAD master | xargs -I{} git log --oneline {}..master | cut -f1 -d' ' | tac | xargs -L1 git rebase

This experiment seems to work. For an alias, I'd of course replace 'master' by a placeholder for the target commit. I need to improve some details for improving experience when a conflict is found, though. I'll update when it's more robust.

on leaving behind

Posted Sep 24, 2026 17:06 UTC (Thu) by alx.manpages (subscriber, #145117) [Link]

Turning this into a git alias seems non-trivial. However, a script works quite well:

$ cat git-rebase-walk

#!/bin/bash

set -Eeufo pipefail;

git merge-base HEAD "$1" \
| xargs -I{} git log --oneline {}.."$1" \
| cut -f1 -d' ' \
| tac \
| while read -r c; do
	git rebase "$c";
done;

It stops when a conflict appears, as it should. It's exactly what I need.

on leaving behind

Posted Sep 24, 2026 15:40 UTC (Thu) by mathstuf (subscriber, #69389) [Link] (2 responses)

Have you ever invested so much in a rebase operation that you really need to pass around the conflict to avoid starting from scratch?

Yes. We had a vendored project that had been patched locally over years. We need to rebase on top of the latest development branch. Reconciling multiple years of patches and syncs (and a reformatting pass or two…) over time was quite involved. Note that this is a main reason why we now 100% ban direct patches to vendored projects and instead force them to go through an intermediate git repository. Now, this is pre-git worktree as well, but that has always stuck with me as a situation to consider (even if processes should avoid it if at all possible).

A more modern instance (~2019) was the rewrite of an entire build system. Hundreds of commits I rebased daily (or so) to keep on top of ongoing development for around a year. Sometimes needing to test the Windows or macOS situation mid-rebase. This was handled by just git push otheros +HEAD:testing and iterating until the (sometimes only logical) conflicts resolved, but there's no reason I shouldn't have been able to ship the rebase-state over to the machine and do it directly there.

on leaving behind

Posted Sep 24, 2026 17:10 UTC (Thu) by alx.manpages (subscriber, #145117) [Link] (1 responses)

Hmmmm. Then yeah, I guess it could make sense to pass conflicts around. I think such commits should be marked as having a conflict, so that you'd get a warning saying they're not meant to be stable commits, so that you don't accidentally merge those in main.

on leaving behind

Posted Sep 24, 2026 21:59 UTC (Thu) by mathstuf (subscriber, #69389) [Link]

I think marking them with a conflict! commit message is suitable. These can be rejected just like any fixup! or squash!-prefixed commit subject. This also allows one to manually mark commits with logical conflicts[1] as such (though without the conflict structures I'd like to see since there's no actual code conflict).

[1] As an example, one side renames a function while the other side adds a new call site with the old name. No "physical" code conflict, but it still doesn't build.

Bisecting a git-rebase

Posted Oct 1, 2026 21:00 UTC (Thu) by alx.manpages (subscriber, #145117) [Link] (10 responses)

I've seen a few implementations, but some were entirely vibe-coded, one was 4k+ lines of python, one was relatively nice at 100 lines of shell, but I thought I could do better, so I did.

Here's my own implementation:

$ cat cat src/bin/git-bisect-rebase
#!/bin/bash
# Copyright 2026, Alejandro Colomar <alx@kernel.org>
# SPDX-License-Identifier: GPL-3.0-or-later

set -Eeufo pipefail;
shopt -s lastpipe;

tgt="$(git rev-list -1 "$1")";

## Try a regular rebase.
if
	git rebase "$tgt" >/dev/null 2>/dev/null;
	test $? -eq 0;
then
	echo 'Successfully rebased.';
	exit 0;
else
	git rebase --abort >/dev/null 2>/dev/null;

	git merge-base HEAD "$tgt" \
	| xargs -I{} git rev-list {}.."$tgt" \
	| wc -l \
	| xargs -I{} printf '%s' "{} commits left to test in the target branch";
	printf '%s' " (trying $tgt)";
	echo ' [conflict]';
fi;

## Bisect.
while
	git merge-base HEAD "$tgt" \
	| xargs -I{} git rev-list {}.."$tgt" \
	| wc -l \
	| read -r n;

	test $n -gt 1;
do
	if
		git merge-base HEAD "$tgt" \
		| xargs -I{} git rev-list {}.."$tgt" \
		| sed "$(echo "($n + 2) / 2" | bc)!d" \
		| read -r mid;

		printf '%s' "$(expr $n - 1) commits left to test in the target branch";
		printf '%s' " (trying $mid)";

		git rebase $mid >/dev/null 2>/dev/null;

		test $? -eq 0;
	then
		echo ' [ok]';
	else
		echo ' [conflict]';

		tgt="$(git rev-list -1 "$mid")";
		git rebase --abort >/dev/null 2>/dev/null;
	fi;
done;

## Perform the conflicting rebase
echo "The conflict is at $tgt; about to rebase now.";
git rebase "$tgt";

Which behaves in this way:

$ git bisect-rebase agetpass
215 commits left to test in the target branch (trying fff90f9bbbf10e6d983b0a6b378d5da8ee967856) [conflict]
214 commits left to test in the target branch (trying 0482fd5f353c473268aff39e38513df2fb589a52) [conflict]
107 commits left to test in the target branch (trying b21a76f759492f88388840d69f85b8d27ad5dffe) [conflict]
53 commits left to test in the target branch (trying db3ca9f917efff9e2dab2fe36fa02fd6ef81ab08) [ok]
26 commits left to test in the target branch (trying 4df3f783c4d936e6985590981a2ddaf0377a3785) [ok]
12 commits left to test in the target branch (trying 93c675ef030e4eb226f60a317f3759755cd5bc5d) [ok]
5 commits left to test in the target branch (trying b4adbe6387ae8d95dbe0bd547a157492842bea12) [conflict]
2 commits left to test in the target branch (trying f7c712c3ade10c1d14916ea44408e42495fd1a8a) [conflict]
1 commits left to test in the target branch (trying 0bb39793716a2466aa1f477e4634e9d86532df80) [ok]
The conflict is at f7c712c3ade10c1d14916ea44408e42495fd1a8a; about to rebase now.
Auto-merging src/gpasswd.c
Auto-merging src/newgrp.c
CONFLICT (content): Merge conflict in src/newgrp.c
Auto-merging src/passwd.c
CONFLICT (content): Merge conflict in src/passwd.c
error: could not apply 6d0e130d5f3b... lib/, src/: Use getpassa()/passzero() instead of agetpass()/erase_pass()
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Could not apply 6d0e130d5f3b... # lib/, src/: Use getpassa()/passzero() instead of agetpass()/erase_pass()

I had written an iterative (one commit at a time) script last week, but it was quite slow, and this bisect-rebase script is amazing. I've been wanting to write it for some time already, so thanks for reminding me about this topic! :-)

(After posting it, I see should validate that the number of arguments is exactly one...) (I'll also run shellcheck(1) to find if there are any other issues with it...)

Bisecting a git-rebase

Posted Oct 2, 2026 18:39 UTC (Fri) by mathstuf (subscriber, #69389) [Link] (9 responses)

I had written an iterative (one commit at a time) script last week, but it was quite slow, and this bisect-rebase script is amazing. I've been wanting to write it for some time already, so thanks for reminding me about this topic! :-)

(After posting it, I see should validate that the number of arguments is exactly one...) (I'll also run shellcheck(1) to find if there are any other issues with it...)

Other things from my LWN-based review (most of which are feature requests :) ):

  • --first-parent would be nice.
  • Using git history might be able to do a lot of ops in memory and only drop working copy bits on conflicts and completion.
  • No support for skipping commits.
  • Is there a plumbing command to construct git bisect commits from bad/good/skip sets? If so, it might be able to just do the --first-parent bit automatically too

Bisecting a git-rebase

Posted Oct 2, 2026 19:11 UTC (Fri) by alx.manpages (subscriber, #145117) [Link] (8 responses)

> --first-parent would be nice.

Agree. I was wondering what was the right way to treat merge commits, but I didn't have a clear answer, so I just implemented the naive way. I guess allowing --first-parent would allow more control over that. I wonder how that could be implemented, though. Maybe I could use git-bisect(1) underneath for the bisection, instead of rolling my own. I'll think about it.

> Using git history might be able to do a lot of ops in memory and only drop working copy bits on conflicts and completion.

I've never used git-history(1), so I'm not familiar with it. I've heard about it in a conference, only. I'll see how it works and how I can use it for this.

> No support for skipping commits.

In what sense do you mean to skip commits? As in `git bisect skip`?

Since the binary search of the first conflicting commit in the target branch is automated, the user doesn't have a way to skip commits from being tested.

I've never used `git bisect run`, but I think the behavior of what I've implemented would be more or less like that. It seems `git bisect run` uses the special code `125` to skip a commit. If we had a `-x` flag in this script to execute a semantic test after the rebase, we could skip it there, but I'm not sure we could skip it before the rebase takes place (and thus a physical rebase conflict would be taken as bad).

> Is there a plumbing command to construct git bisect commits from bad/good/skip sets? If so, it might be able to just do the --first-parent bit automatically too

We could use `git bisect run`, where the command to run would be the rebase itself. That would allow inheriting `--first-parent` from git-bisect(1). I'm not sure if one can git-rebase(1) within a git-bisect(1) session, but this might be the time to use git-history(1), to do the whole thing in memory, so that it doesn't conflict with git-bisect(1). :)

Thanks for the feedback!! :-)

Bisecting a git-rebase

Posted Oct 2, 2026 19:26 UTC (Fri) by alx.manpages (subscriber, #145117) [Link]

Oh, well, it seems git-history(1) doesn't work with merge commits at all, so we'll have to stick to git-rebase(1). I'm not sure if git-rebase(1) is compatible with git-bisect(1). I'll try. :)

Bisecting a git-rebase

Posted Oct 2, 2026 19:41 UTC (Fri) by mathstuf (subscriber, #69389) [Link] (6 responses)

In what sense do you mean to skip commits? As in git bisect skip?

Yes.

If we had a -x flag in this script to execute a semantic test after the rebase, we could skip it there, but I'm not sure we could skip it before the rebase takes place (and thus a physical rebase conflict would be taken as bad).

If there was a pre-exec script, it could verify a build before rebasing and issue exit 125. No sense rebasing on top of an already-busted build.

We could use git bisect run, where the command to run would be the rebase itself. That would allow inheriting --first-parent from git-bisect(1). I'm not sure if one can git-rebase(1) within a git-bisect(1) session

Things get weird with git ops during a bisection. I'm sure there's a way to make things "happy", but I've never shuttled around more than a stash during a bisection successfully.

If git history isn't up to the job (which I'd be surprised about…though perhaps the required elements aren't quite there yet), take a look at the ops underlying git-workarea which is basically an up-to-date index and minimal on-disk files to make it happy (typically, just .gitmodules) while still giving a place for conflicts to dump files.

Bisecting a git-rebase

Posted Oct 2, 2026 21:09 UTC (Fri) by alx.manpages (subscriber, #145117) [Link] (5 responses)

> If there was a pre-exec script, it could verify a build before rebasing and issue exit 125. No sense rebasing on top of an already-busted build.

Sounds reasonable.

>Things get weird with git ops during a bisection.

Actually, I have changed the implementation to use git-rebase(1) within git-bisect(1) and it runs quite smooth. It took me just a few minutes (and a few iterations to get it right).

Now I'm struggling to do the last step, because while I'm able to print the commit with the first conflict, I'm unable to find a plumbing command to get it in the script (I could parse the stdout of a porcelain command, but I'd rather avoid that). I'll wait until the git@ maintainers reply to my question, and then publish the version using git-bisect(1).

Interestingly, re-implementing it with git-bisect(1) has made the script much smaller, since I don't need to implement the bisection code. It'll also make it trivial to add git-bisect(1) features that you were interested in, such as --first-parent.

Bisecting a git-rebase

Posted Oct 2, 2026 21:36 UTC (Fri) by alx.manpages (subscriber, #145117) [Link] (4 responses)

Well, while we wait for git@ to reply to what's the right way to plumb the bad commit, I've figured out a reasonable way of parsing it from stdout.

Here's the git-bisect(1)-based rewrite of the script (still without the fancy features, but they're now trivial to implement):

$ cat src/bin/git-brebase 
#!/bin/bash
# Copyright 2026, Alejandro Colomar <alx@kernel.org>
# SPDX-License-Identifier: GPL-3.0-or-later

set -Eeufo pipefail;
shopt -s lastpipe;

if test $# -lt 1; then
	>&2 printf '%s\n' "$(basename "$0"): error: Too few arguments."
	exit 1;
fi;

if test $# -gt 1; then
	>&2 printf '%s\n' "$(basename "$0"): error: Too many arguments."
	exit 1;
fi;

git rev-list -1 "$1" \
| read -r tgt;

git rev-parse --abbrev-ref HEAD \
| read -r b;

## Try a regular rebase.
if
	git rebase "$tgt" >/dev/null 2>/dev/null;
	test $? -eq 0;
then
	echo 'Successfully rebased.';
	exit 0;
else
	git rebase --abort >/dev/null 2>/dev/null;
fi;

## Bisect.
git rev-parse --abbrev-ref HEAD \
| read -r b;
git bisect start >/dev/null;
git bisect bad "$tgt" >/dev/null;
git merge-base HEAD "$tgt" \
| xargs -I{} git bisect good {};

while
	git rev-list -1 HEAD \
	| read -r tgt;

	git bisect visualize --oneline \
	| wc -l \
	| xargs -I{} test {} -gt 1;
do
	git switch "$b" >/dev/null 2>/dev/null;

	if
		git rebase "$tgt" >/dev/null 2>/dev/null;
		test $? -eq 0;
	then
		echo 'Rebase: success';
		git bisect good "$tgt";
	else
		echo 'Rebase: conflict';
		git rebase --abort >/dev/null 2>/dev/null;
		git bisect bad "$tgt";
	fi;
done;

git bisect next \
| sed '1!d' \
| cut -f1 -d' ' \
| read -r tgt;

git bisect reset >/dev/null;

## Perform the conflicting rebase
git rebase "$tgt";

And here's how it works:

$ git brebase c
Bisecting: 8 revisions left to test after this (roughly 3 steps)
[7a5303be9b6be4e9c00fa714e8e2a236f655e0c5] lib/: Use '(void)' to define a function with 0 parameters
Rebase: conflict
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[bb2d8fa5b96f32f900b1631170bd875e6f0b3c62] src/login.c: main(): Wrap uses of 'subroot' in ifndef USE_PAM
Rebase: conflict
Bisecting: 1 revision left to test after this (roughly 1 step)
[b4adbe6387ae8d95dbe0bd547a157492842bea12] lib/io/syslog.h: SYSLOGE[C](): Add macros
Rebase: conflict
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[f7c712c3ade10c1d14916ea44408e42495fd1a8a] lib/, tests/: Use fprinte() instead of its pattern
Rebase: conflict
f7c712c3ade10c1d14916ea44408e42495fd1a8a is the first 'bad' commit
commit f7c712c3ade10c1d14916ea44408e42495fd1a8a
Author: Alejandro Colomar <alx@kernel.org>
Date:   2025-07-23 17:39:19 +0200

    lib/, tests/: Use fprinte() instead of its pattern
    
    While we don't use it directly in tests/, exit_if_null_() uses it, so we
    need to link to it in its test.
    
    Signed-off-by: Alejandro Colomar <alx@kernel.org>

 lib/addgrps.c          |   4 +--
 lib/chowntty.c         |  10 ++++----
 lib/commonio.c         |  35 ++++++++++---------------
 lib/exit_if_null.h     |   4 +--
 lib/find_new_gid.c     |   9 +++----
 lib/find_new_uid.c     |   7 +++--
 lib/get_pid.c          |   8 +++---
 lib/gettime.c          |   8 +++---
 lib/idmapping.c        |  14 +++++-----
 lib/prefix_flag.c      |   7 +++--
 lib/root_flag.c        |  21 +++++++--------
 lib/run_part.c         |  12 ++++-----
 lib/selinux.c          |   6 ++---
 lib/spawn.c            |  14 +++++-----
 lib/tcbfuncs.c         | 134 ++++++++++++++++++++++++++++++++++++++---------------------------------------------------------
 src/chage.c            |   5 ++--
 src/chgpasswd.c        |   9 +++----
 src/chpasswd.c         |   9 +++----
 src/faillog.c          |  18 +++++--------
 src/free_subid_range.c |   4 +--
 src/get_subid_owners.c |   4 +--
 src/getsubids.c        |   4 +--
 src/gpasswd.c          |   6 ++---
 src/groupadd.c         |   7 +++--
 src/lastlog.c          |  10 +++-----
 src/login.c            |   4 +--
 src/new_subid_range.c  |   4 +--
 src/newgidmap.c        |  22 +++++-----------
 src/newgrp.c           |  11 ++++----
 src/newuidmap.c        |  10 +++-----
 src/newusers.c         |  43 +++++++++++++++----------------
 src/passwd.c           |  12 ++++-----
 src/useradd.c          |  63 ++++++++++++++++++---------------------------
 src/userdel.c          |  22 +++++-----------
 src/usermod.c          |  60 ++++++++++++++++++-------------------------
 src/vipw.c             |  20 ++++++---------
 36 files changed, 264 insertions(+), 376 deletions(-)
Already on 'opt'
Auto-merging lib/addgrps.c
CONFLICT (content): Merge conflict in lib/addgrps.c
Auto-merging lib/chowntty.c
Auto-merging lib/commonio.c
Auto-merging lib/find_new_gid.c
error: could not apply d5f8eeda09da... lib/: Propagate _Optional
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Could not apply d5f8eeda09da... # lib/: Propagate _Optional

It's a bit more verbose than my implementation, since git-bisect(1) uses two lines per iteration, and then I have to add one more for the result of the rebase (which is nice to have, to give an overview of where in the graph is the conflict).

Then, it prints the full commit message of the bad commit, which is acceptable. In my implementation, I only printed a --pretty=reference, but I could live with the full commit message, and since that's what git-bisect(1) does usually, I guess consistency makes it more okay.

Now I'll start adding features.

One I'm very interested in is --rebase-merges. Then, I'll also add --first-parent, since it seems quite important for non-trivial histories.

Bisecting a git-rebase

Posted Oct 2, 2026 21:45 UTC (Fri) by alx.manpages (subscriber, #145117) [Link]

Any name and email or URL you'd want to use to be credited as Suggested-by: Name <email or url>? Feel free to send it to me in private (to alx at kernel dot org if you don't want to post it here).

Bisecting a git-rebase

Posted Oct 3, 2026 10:23 UTC (Sat) by alx.manpages (subscriber, #145117) [Link] (2 responses)

Here's a version which handles --first-parent and arbitrary git-rebase(1) options.

I still have a couple of features pending:

A new --pre-exec option, to pass a command to be ran before each rebase operation. A new --post-exec option, to pass a command to be run after each rebase operation.

(The names are negotiable.)

Please test and let me know feedback:

$ cat src/bin/git-brebase 
#!/bin/bash
# Copyright 2026, Alejandro Colomar <alx@kernel.org>
# SPDX-License-Identifier: GPL-3.0-or-later

set -Eeufo pipefail;
shopt -s lastpipe;

err()
{
	>&2 printf '%s\n' "$(basename "$0"): error: $*";
	exit 1;
}

# Defaults:
fp='';
other='';

while test $# -ge 1; do
	case "$1" in
	--first-parent)
		fp='--first-parent';
		;;
	-*)
		other="$other $1";
		;;
	*)
		break;
		;;
	esac;
	shift;
done;
gbopts="$fp";
gropts="$other";

if test $# -lt 1; then
	err 'Missing target commit';
fi;
if test $# -gt 1; then
	err 'Too many arguments.';
fi;
git rev-list -1 "$1" \
| read -r tgt;

# Try a regular rebase.
if
	# shellcheck disable=SC2086  # gropts may hold multiple options
	git rebase $gropts "$tgt" 2>&1 >/dev/null\
	| read -r out;
	test $? -eq 0;
then
	echo "$out" >&2;
	exit 0;
else
	echo 'Rebase: conflict';
	git rebase --abort >/dev/null;
fi;

# Bisect.
# shellcheck disable=SC2248  # gbopts may hold multiple options
git bisect start --no-checkout $gbopts >/dev/null;
git bisect bad "$tgt" >/dev/null;
git merge-base HEAD "$tgt" \
| xargs -I{} git bisect good {};

while
	git bisect visualize --oneline \
	| wc -l \
	| xargs -I{} test {} -gt 1;
do
	if
		# shellcheck disable=SC2086  # gropts may hold multiple options
		git rebase $gropts BISECT_HEAD >/dev/null 2>/dev/null;
		test $? -eq 0;
	then
		echo 'Rebase: success';
		git bisect good BISECT_HEAD;
	else
		echo 'Rebase: conflict';
		git rebase --abort >/dev/null;
		git bisect bad BISECT_HEAD;
	fi;
done;

git rev-list -1 bisect/bad \
| read -r bad;
git bisect reset >/dev/null 2>/dev/null;

# Perform the conflicting rebase
# shellcheck disable=SC2086  # gropts may hold multiple options
git rebase $gropts "$bad";

Bisecting a git-rebase

Posted Oct 4, 2026 1:56 UTC (Sun) by mathstuf (subscriber, #69389) [Link] (1 responses)

It's probably best to set up a repo for this at this point :) . I can git-fetch-file[1] from a repo; not from LWN comments ;) .

[1] https://github.com/andrewmcwattersandco/git-fetch-file

Bisecting a git-rebase

Posted Oct 4, 2026 18:33 UTC (Sun) by alx.manpages (subscriber, #145117) [Link]

on leaving behind

Posted Sep 24, 2026 18:29 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link]

> The problem with committing the current state is that you've already moved the branch, and by moving the branch, you've destroyed the old branch. Which means you don't have it available anymore to help with the conflict resolution.

I think this can be solved by committing it to a new branch? We do that sometimes even now, we have a convention to use `conflict/` prefix for such branches. I think this can even be automated, to avoid losing the old branch's starting position.

on leaving behind

Posted Sep 25, 2026 9:29 UTC (Fri) by taladar (subscriber, #68407) [Link] (1 responses)

The reflog should still have the old positions of each branch for a while after you changed it in any way.

on leaving behind

Posted Sep 26, 2026 13:03 UTC (Sat) by alx.manpages (subscriber, #145117) [Link]

The reflog is the last thing one would like to be messing with while doing a regular rebase operation.

It's a "I've really fucked up; get me out of here" thing, not a regular tool.

on leaving behind

Posted Sep 30, 2026 11:17 UTC (Wed) by ras (subscriber, #33059) [Link]

> The problem with committing the current state is that you've already moved the branch

That would be a problem in git, if it supported committing conflicts. But it isn't a problem in JJ. Yes, the branch is still moved - but JJ won't let you push the branch while it's conflicted so it's never going to leave your machine in that state. Moreover, the state the repository was in before the rebase hasn't gone - it's still in the operation log, easily accessible. So you still can see the old branch position, and can restore it if you want. If you just need to see the state of a single file before the rebase it's in the evolution log.

Many of git's operations (like moving a branch) overwrite existing state, and thus destroy information. JJ never destroys information. That changes how you work with it.

on leaving behind

Posted Sep 27, 2026 4:20 UTC (Sun) by ras (subscriber, #33059) [Link] (30 responses)

> One problem with that is losing the original branches from which the conflict originated.

You've lost me. As far as I'm aware it's impossible to lose anything with a jj command [0] including branches. Had you said "the way jj insists on keeping all history, including the position of every branch, creating divergent change-id's at the most unexpected times", I would have sympathised. But it looks like you've said the reverse?

[0] Well, any jj command other than gc.

on leaving behind

Posted Sep 27, 2026 8:10 UTC (Sun) by alx.manpages (subscriber, #145117) [Link] (29 responses)

I have never used jj(1). I have only seen what git(1) developers have talked about jj(1), and what they're adding to git(1).

I'll try jj(1) to compare with what I've understood from what git(1) developers said, and come back with a better-formed opinion.

on leaving behind

Posted Sep 27, 2026 21:44 UTC (Sun) by ras (subscriber, #33059) [Link] (28 responses)

How well an interface works is a matter of taste, but to me all operations you can do in git became easier, faster and safer in jj. Faster because there is less typing, safer because jj has full undo/redo - so any operation (or sequence of them) can be easily reverted or repeated. The simpler mental model makes difficult operations in git almost trivial in jj - rebasing a stack of commits being a prime example. I firmly believe new DVCS users should start out with jj instead of git - safer and simpler means a kinder, gentler tool.

JJ has its own issues of course. I found my learning curve was far longer than I expected. Not steep as you are productive almost immediately, but it took me a surprising amount about of time to go from treating it as a better git porcelain to grokking the jj mental model, which you have to do to unlock its full potential. While the jj commands are a breath of fresh air compared to git's interface, lurking underneath it are three DSLs (revsets, file sets, and templates) which you need to learn if you start writing your own jj aliases. They aren't small. As I said it doesn't forget anything, but sometimes the various versions of the JJ change-id's can't be represented as a DAG, so JJ flags those change-id's as "divergent". You have to resolve that somehow, which can be every bit as hard as extracting yourself from some git messes (but undo is always available). The JJ UI is constantly evolving and they deprecate ruthlessly. That means it's relatively free of cruft, but it's possibly not something you would use where stability matters - like a CI pipeline.

JJ's imperfections do chafe at times, but a few weeks ago I had to manage a stack of commits with my old git methods and realised I had forgotten what it was like. After about the 3rd attempt at trying to knock a stack of commits into shape using "git rebase -i", I gave up and installed jj. The gulf between the two is so large, it's hard to put into words.

on leaving behind

Posted Sep 27, 2026 22:49 UTC (Sun) by intelfx (subscriber, #130118) [Link] (21 responses)

How well an interface works is a matter of taste, but to me all operations you can do in git became easier, faster and safer in jj.

Wake me up when jj(1) has support for named branches. Until that happens, I unfortunately cannot take it seriously (or much more seriously than "an internal product of Google").

on leaving behind

Posted Sep 28, 2026 9:33 UTC (Mon) by farnz (subscriber, #17727) [Link] (20 responses)

Can you explain what it is about the way bookmarks work that doesn't match your requirements for "named branches"?

on leaving behind

Posted Sep 28, 2026 12:49 UTC (Mon) by intelfx (subscriber, #130118) [Link] (19 responses)

Can you explain what it is about the way bookmarks work that doesn't match your requirements for "named branches"?

There is no concept of an active branch. Without it, bookmarks are more “weird mutable tags” than branches.

I trust that this isn’t much of a problem when you are a regular bigcorp dev linearly (or semi-linearly) working on a single feature at a time (or when you are doing Gerrit and your unit of work is always one commit, so you just squash all the time).

Named branches in `jj`

Posted Sep 28, 2026 13:46 UTC (Mon) by farnz (subscriber, #17727) [Link] (18 responses)

So basically, you want jj bookmark advance to automatically run after each commit, go back automatically if you abandon the commit it's pointing to, and to automatically rearrange the bookmark as you intend if you do more complex history manipulation?

I can see the use cases for this, although I would not use it myself; I would describe the active branch as a "weird self-mutating tag" :-)

Named branches in `jj`

Posted Sep 29, 2026 8:20 UTC (Tue) by intelfx (subscriber, #130118) [Link] (17 responses)

So basically, you want jj bookmark advance to automatically run after each commit

No, I want the concept of an active branch to be supported natively, with the same level of thought and consideration as jj authors claim goes into every other feature. It might not necessarily look exactly like git's branches, although that's the minimum requirement.

When you remove a feature, implement a manual hack in place to half-emulate that feature, and subsequently just automate that hack, obviously you end up with a half-assed solution and not a proper feature.

I can see the use cases for this, although I would not use it myself; I would describe the active branch as a "weird self-mutating tag" :-)

Do you really want to say that you never used (active) branches in Git? I do not really believe that.

Named branches in `jj`

Posted Sep 29, 2026 8:34 UTC (Tue) by intelfx (subscriber, #130118) [Link]

So basically, you want jj bookmark advance to automatically run after each commit

For starters, jj bookmark advance advances all closest bookmarks — it does not maintain the crucial piece of information that is the current active branch, and (imperfectly) tries to infer that information from the repo alone.

This would fail as soon as you have two bookmarks on the same commit, thus prohibiting the following workflow that I use daily:

$ git checkout <some-rev-in-the-middle>
$ git branch wip/newthing/base
$ git checkout -b wip/newthing/v1
/// ... hack hack hack, commit, hack hack hack, commit, send-email ...
$ git checkout -b wip/newthing/v2
$ git rebase -i wip/newthing/base
// ... reshuffle things, edit some commits, hack some more, range-diff, send-email ...
$ git checkout -b wip/newthing/v3
// ... rinse, repeat ...

Named branches in `jj`

Posted Sep 29, 2026 10:11 UTC (Tue) by farnz (subscriber, #17727) [Link] (15 responses)

No - I used active branches in git, and hated them. The number of ways that they caused things to go wrong on me unexpectedly, because the branch tip moved in ways I didn't expect when I did things like interactive rebases, losing work in the process. And no, I can't explain what happened, because from my point of view, git just decided to delete all my work.

I much prefer the way jj handles it, where I choose when the branch tip moves, and it doesn't lose commits that aren't named. There's a bit of a challenge setting up jj bookmark advance so that it doesn't move all the bookmarks, only the ones I want it to move, but it's not that hard to get right.

Named branches in `jj`

Posted Sep 29, 2026 10:41 UTC (Tue) by alx.manpages (subscriber, #145117) [Link] (3 responses)

You can keep track of your old version of a branch easily by tracking a remote branch. Don't push until you're happy with the change. The remote can even be a local copy of the repo.

Named branches in `jj`

Posted Sep 29, 2026 11:45 UTC (Tue) by farnz (subscriber, #17727) [Link] (2 responses)

How do I generalise that to having multiple versions of a branch without multiple remotes? This is something I care about, as I'm trying out different ways to build on top of a "stem the bleeding" firefighting fix so that this class of fires stops breaking out - I often want several different possible versions of the branch to consider, and the ability to jump back and forth between them.

Named branches in `jj`

Posted Sep 29, 2026 14:17 UTC (Tue) by alx.manpages (subscriber, #145117) [Link] (1 responses)

You could use tags for that (that's what I do when I need a temporary tag for the current state of a branch in more than one point in time).

Named branches in `jj`

Posted Sep 29, 2026 14:26 UTC (Tue) by farnz (subscriber, #17727) [Link]

I could, but then I'm rebuilding the workflow `jj` gives me natively, but with a lot more manual work to fight git's need for a name for every head.

Named branches in `jj`

Posted Sep 29, 2026 13:27 UTC (Tue) by intelfx (subscriber, #130118) [Link] (10 responses)

No - I used active branches in git, and hated them. The number of ways that they caused things to go wrong on me unexpectedly, because the branch tip moved in ways I didn't expect when I did things like interactive rebases, losing work in the process. And no, I can't explain what happened, because from my point of view, git just decided to delete all my work.

Well—this is an experience I absolutely cannot corroborate. I wonder though which experience is in the minority, and by how much.

There's a bit of a challenge setting up jj bookmark advance so that it doesn't move all the bookmarks, only the ones I want it to move, but it's not that hard to get right.

In my native language we have an idiom for this: "doable on a dare". Yes, many things can be done on a dare. But if a tool requires me to jump through hoops and solve "challenges" to achieve absolutely basic behavior, it's just a bad tool.

Named branches in `jj`

Posted Sep 29, 2026 14:25 UTC (Tue) by farnz (subscriber, #17727) [Link] (8 responses)

My experience teaching people who've never used a VCS at all, or if they have, they've used a GUI frontend for Perforce or Subversion, is that they find the concept of branches hard enough in and of itself, and the idea of named branches is often a stumbling block.

Usually, it's manageable to get them to learn about the idea of a DAG of commits instead of a straight-line graph, and from there, naming points in the DAG is a manageable concept, but the idea of a name that moves itself is hard to get to grips with, and people have questions like "if I give this commit two names, which name sticks with it when I do an interactive rebase" (which itself has huge conceptual issues, because a branch doesn't name a commit, except when it does).

Named branches in `jj`

Posted Sep 30, 2026 9:02 UTC (Wed) by taladar (subscriber, #68407) [Link] (7 responses)

I am not surprised people who have used Subversion are very confused about branches because Subversion's branch concept is deeply flawed but in general the concept should be quite intuitive "you work on something and your coworker works on something else so your work diverges" isn't exactly hard to grasp.

Named branches in `jj`

Posted Sep 30, 2026 9:11 UTC (Wed) by farnz (subscriber, #17727) [Link] (6 responses)

For some reason, the idea of a DAG of commits is not a problem, but the idea that they all have to belong to named branches is a stumbling block for people who've not used any VCS before. No problem with the idea of two commits with the same parent, big problems with the idea that there has to be a named branch for that commit ("what should I call it?", "how do I stop other people using my name?" etc).

Named branches in `jj`

Posted Sep 30, 2026 11:19 UTC (Wed) by mathstuf (subscriber, #69389) [Link] (5 responses)

Nothing stops anyone from making branches work-YYYYMMDD or whatever. Naming collisions are solved by everyone using their own fork instead of pushing to the same repository.

Named branches in `jj`

Posted Sep 30, 2026 12:14 UTC (Wed) by farnz (subscriber, #17727) [Link] (4 responses)

We're talking people who either used a GUI to access Perforce or Subversion (and didn't understand what it was doing - just follow the instructions), or did "version control" as "I made a ZIP file containing the project and copied it to a USB pen drive, then I put it in my drawer". Telling people to use work-YYYYMMDD as a branch name creates more questions - "why YYYYMMDD? Why can't I call it just 'work'? What happens if someone else calls their branch work-YYYYMMDD and then it causes my git problems?".

Similarly, "own fork" involves introducing a lot of concepts that someone who has only just grasped the idea that commits can form a DAG, not just a straight line without branches or merges, will also question to the Nth degree - "why is it called a fork, not a split? Why can't I share a server with someone else? Does this mean the company has to buy a new server just for me?" and more.

Named branches in `jj`

Posted Sep 30, 2026 12:36 UTC (Wed) by mathstuf (subscriber, #69389) [Link] (3 responses)

Yeah, if you're looking for zero time to usage, sure, Git's probably not the tool to expose directly to users. I don't think jj is either for that matter. But I don't think any of this is unfixable given:

  • training for new tools
  • Easy Mode tools for such users to just hit buttons in some custom UI to do what is needed under the hood

If the branch name really does matter, they can come up with something. If it doesn't, generate some diceware-like name.

Named branches in `jj`

Posted Sep 30, 2026 12:42 UTC (Wed) by farnz (subscriber, #17727) [Link] (2 responses)

Yeah - neither tool is good enough for inexperienced users.

But, IME, if things are named at all, either all names must be computer-generated "gibberish" (a git commit hash, for example), or inexperienced users care about the names being meaningful, and will get stuck for hours triggering a diceware-like generator again and again until they're happy with the name on offer. The concept of a name that can be meaningful but doesn't have to be is just something that doesn't stick - either names are optional, or you don't get a choice (which rules out git branch names, since those can be chosen), or they are meaningful and users get stuck on how to choose a meaningful name before they have enough context to decide what a meaningful name is.

Named branches in `jj`

Posted Sep 30, 2026 19:56 UTC (Wed) by mathstuf (subscriber, #69389) [Link] (1 responses)

I can't say I've ever run into anyone having chronic issues with branch naming. Not saying it doesn't exist, but maybe if they're so particular about the branch name being perfect, one can only hope that their code ends up getting the same level of scrutiny :) .

Named branches in `jj`

Posted Oct 1, 2026 8:52 UTC (Thu) by farnz (subscriber, #17727) [Link]

Their code does, yes - it's beautiful to read, just they're slow to write it. Tends to come with plenty of testbenches, too, that allow you to verify that it does what's intended in many important cases.

Named branches in `jj`

Posted Sep 30, 2026 10:55 UTC (Wed) by ras (subscriber, #33059) [Link]

For what it's worth I had the same frustration as you with having to manually move bookmarks in jj.

I wrote a "jj push" alias that provides a rough emulation of what git push does, including moving the bookmark. It pushes the current commit and its children. Before it does so it looks for the nearest tracked bookmark in a child comment, and moves it to the commit being pushed. Similar to jj advance - but only one bookmark. It syncs the upstream the git branch with that (now moved) bookmark, and syncs all the other git branches being tracked by @'s children. If you are following the usual git work flow of adding a few commits to the tip, or perhaps rebasing, then pushing, the end result looks and feels just like "git push".

I've developed a few aliases to cover the gaps, such as "jj submodule-update". You can find them here: https://drive.google.com/file/d/1NzFyJ59XdK-GiYKg4sawbNRK...

on leaving behind

Posted Sep 28, 2026 9:05 UTC (Mon) by taladar (subscriber, #68407) [Link] (5 responses)

A constantly evolving UI with a difficult to grasp mental model sounds like just about the opposite of what I want for a VCS. Git is successful precisely because it has a simple data model that makes it easy to understand what exactly the current state of your code and history is and that data model hasn't changed in decades.

on leaving behind

Posted Sep 28, 2026 11:24 UTC (Mon) by ras (subscriber, #33059) [Link] (4 responses)

> Git is successful precisely because it has a simple data model that makes it easy to understand what exactly the current state of your code and history is and that data model hasn't changed in decades.

The JJ's data model is binary compatible with git, to the extent you can intermingle git and JJ commands in the same repository. The protocols both use to talk to upstream are identical. Consequently all your knowledge and intuition about git's data model carries directly across to JJ.

I suspect you think about git in terms of its data model because as far as I can tell git does not have a discernable model for its CLI interface. I don't know how it got into the state it is - it feels like it grew organically on top of the underlying C API, with no guiding plan on how someone might use it.

JJ on the other hand put a fair bit of thought into their CLI. It has a clear model, the concepts mostly borrowed other DVCS's like mercurial and DARCS. Consequently getting the common tasks done needs less commands than git and those commands are far more regular - for example the same option does the same thing in every command that accepts it.

I don't know if I would be a good judge of whether JJ's CLI is easy to grasp as I've used a number of VCS's in my time, starting with SCCS and most of them, JJ included, follow the same pattern. The one exception to that is git, which seemingly went it's own way and has no recognisable pattern, so people think in terms of the underlying data structure instead.

on leaving behind

Posted Sep 28, 2026 11:29 UTC (Mon) by alx.manpages (subscriber, #145117) [Link]

I haven't used jj(1), but I have used mercurial when working at nginx, and I dislike it very much. I ended up figuring out how to use git(1) to interact with it, and stopped using it entirely. It was terribly uncomfortable.

on leaving behind

Posted Sep 28, 2026 13:04 UTC (Mon) by intelfx (subscriber, #130118) [Link] (1 responses)

I suspect you think about git in terms of its data model because as far as I can tell git does not have a discernable model for its CLI interface. I don't know how it got into the state it is - it feels like it grew organically on top of the underlying C API, with no guiding plan on how someone might use it.

That’s a pretty uncharitable interpretation.

We think in terms of Git’s underlying data model because it is the proper level of abstraction to do the thinking in. It is the same reason why programmers “think in terms of the tables rather than flowcharts”:

“Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won't usually need your flowcharts; it'll be obvious.”

When you visualize the operation on the graph, the proper incantation falls out of it automatically (even if git could’ve done with a bit less idiosyncrasy in its CLI language).

the concepts mostly borrowed other DVCS's like mercurial and DARCS. Consequently getting the common tasks done needs less commands than git

I beg to differ. I once had to work on the Mozilla codebase for a while (privately; pre-Git-migration). These were the most miserable months of my professional practice. Mercurial is emphatically not an example of “doing common tasks in less commands than git” (well, except if you consider a task that you give up on because it’s plain impossible to do as “0 commands”).

on leaving behind

Posted Oct 1, 2026 8:49 UTC (Thu) by ras (subscriber, #33059) [Link]

> Mercurial is emphatically not an example of “doing common tasks in less commands than git”

JJ didn't copy hg's CLI. It copied its revsets and filesets. It didn't copy DARCS CLI either. It borrowed its conflict resolution approach. If you are basing your opinion of JJ on what you've seen in either, you're misleading yourself.

JJ's CLI is its own invention from what I can make out. I haven't seen anything similar. There is no commit command, and no merge. What replaces merge feels very different from merging in any other VCS. Because of this interface most tasks in JJ end up needing fewer steps than git.

> We think in terms of Git’s underlying data model because it is the proper level of abstraction to do the thinking in.

It seems about as proper to me as thinking of a mouse moving across the screen as a series of XOR operations. It was done that way a long time ago. But no one thought of it that way - you were always moving pointer across the screen.

Similarly VCSs allow multiple people to work on a body of code. You want to combine that work into a single whole, split it apart again to find bugs, remove bad contributions, perhaps create a narrative of how you got from A to B (first a refactor, then added module M, then used M to do what I wanted) - and perhaps a lot more. None of those things involve staging areas, soft resets, 40-character SHAs or many of the other details git exposes. Those implementation details are just like those XORs - a necessary part of getting the job done under the hood, but it's best if the abstraction you present to the user doesn't leak them.

Git has got away doing the reverse for a very long time. Its CLI is a thin wrapper around the API for the content-addressable storage it's built on. Exposing the raw API like that does give you enormous flexibility in implementing new workflows; which is something I suspect git benefitted from for a while as there was a lot of experimentation going on. Hg for instance didn't quite nail it, and wasn't as flexible. But it's all sorted now. Code forges pretty much capture all the ways you operate with a VCS. I don't think it's too surprising there could be a UI that handles it better than exposing the raw data structure. It is surprising finding one took as long as it did, but to me it's clear JJ has found one.

It's a bit academic at this point. The vast bulk of pushes to github are not done by humans now, and the clankers won't give a rat's. I'm sure they would be just as happy to write shell scripts using the raw git plumbing.

on leaving behind

Posted Sep 29, 2026 7:55 UTC (Tue) by taladar (subscriber, #68407) [Link]

We had one of those, it was called Mercurial. It had a simple CLI and a complex model behind it and it clearly lost to Git because nobody cares about a beginner friendly CLI with a tool they use a dozen times every day for years.

I have also used a number of VCS in my time, CVS, SVN, Darcs, Mercurial, bzr, Git and I would rank jj somewhere in the complexity near Darcs, i.e. someone trying too hard to make things elegant at the cost of simplicity.

on leaving behind

Posted Sep 19, 2026 18:45 UTC (Sat) by magfr (subscriber, #16052) [Link] (2 responses)

I am happy hear that you exist!

Is there an inkling of a time plan for when the 1.12 branch will leave preview status and be declared stable?

/Waited 20 years.

Or am I looking at the wrong repository when I look at savannah.gnu.org?

on leaving behind

Posted Sep 19, 2026 19:27 UTC (Sat) by collinfunk (subscriber, #169873) [Link] (1 responses)

If I remember correctly, he never got access to the savannah stuff and just maintains it in Debian [1].

[1] https://tracker.debian.org/pkg/cvs

on leaving behind

Posted Sep 21, 2026 13:49 UTC (Mon) by mirabilos (subscriber, #84359) [Link]

Yeah, the Savannah stuff did not work out, and I couldn’t handle Windows anyway, so I’m maintaining it in MirBSD and Debian.

I’ve been on the 1.12 series since… 2004 or so? but did not merge the unstable changes past 1.12.13 because I got regressions when testing them.

(I should look around the other distros to see if they have worthwhile patches, again, some day.)

on leaving behind

Posted Sep 19, 2026 21:32 UTC (Sat) by linuxrocks123 (subscriber, #34648) [Link] (4 responses)

I salute you, cousin. I'm on Subversion and will stay on it forever.

on leaving behind

Posted Sep 21, 2026 13:50 UTC (Mon) by mirabilos (subscriber, #84359) [Link] (3 responses)

That… certainly takes dedication. But if it works for you…

on leaving behind

Posted Oct 1, 2026 7:07 UTC (Thu) by linuxrocks123 (subscriber, #34648) [Link] (2 responses)

My whole home directory is one giant Subversion repository, which both gives me some nice "Time Machine" abilities and also allows me to keep it synced across multiple machines. Subversion has worked very well for me for this use case, and I doubt either Git or CVS would have worked nearly as well.

I'm curious, what do you, as someone in the CVS camp, find lacking in Subversion?

on leaving behind

Posted Oct 1, 2026 8:53 UTC (Thu) by mirabilos (subscriber, #84359) [Link] (1 responses)

Mostly: performance and disc space usage. The heavy dependencies are also a factor.

Lack of real branches is also a concern, though not as large as for the git users I’d guess; I mostly use them as vendor branches, few release branches.

I also found the tooling in general awkward. It’s actually been more pleasurable to use with git and git-svn as a frontend, when I had to use it.

I’m particular to CVS and RCS, as a personal preference. I found bzr usable (with a learning curve), but the ecosystem… bzr and svn at least had global commit IDs that increment, unlike RCS (not global) and git (not incrementing). I can do git fine enough, though sometimes (as mentioned elsewhere in the thread) it’s by editing files under .git/ manually…

on leaving behind

Posted Oct 2, 2026 19:45 UTC (Fri) by linuxrocks123 (subscriber, #34648) [Link]

They are finally fixing the disk space usage thing:

https://issues.apache.org/jira/browse/SVN-525

I'd prefer they also have an option to use cp --reflink when making the pristine files, rather than just not download them at all. Maybe I'll add that feature when I have time.

I never knew that CVS branches worked differently from Subversion. I understand preferring as a matter of personal taste not to have branches be "just another directory." I have worked with branches in Subversion and they seemed to work well enough despite that admittedly odd quirk.

on leaving behind

Posted Sep 21, 2026 16:38 UTC (Mon) by iabervon (subscriber, #722) [Link] (1 responses)

I remember one day when I typed "cvs -d yesterday -d today", and CVS responded "I know nothing about yesterday. I know nothing about today." Quite a philosophical sentiment, and rather startled me before I realized what I'd done wrong.

on leaving behind - cvsps

Posted Sep 23, 2026 6:19 UTC (Wed) by cyperpunks (subscriber, #39406) [Link]

With cvsps, git style patchset for cvs, its in fact possible to use cvs in a sane way
https://cvsps.sourceforge.net/

git add --resolved

Posted Sep 19, 2026 7:51 UTC (Sat) by donald.buczek (subscriber, #112892) [Link] (2 responses)

From the announcement:

'git add' has been taught a new '--resolved' option to stage conflict-resolved paths, while leaving unrelated local changes unstaged. It scans the unmerged paths for leftover conflict markers and aborts if any are found.

Tha last sentence is what makes me happy.

git add --resolved

Posted Sep 20, 2026 0:10 UTC (Sun) by mathstuf (subscriber, #69389) [Link]

That's awesome to see. Requested for vim-fugitive here.

git add --resolved

Posted Sep 20, 2026 17:21 UTC (Sun) by hmh (subscriber, #3838) [Link]

I would love for this to be the default behavior for git add in 3.00...

And an opt-in default for now through a config option, please (hopefully it already has this, can't check right now)

Yellow cards

Posted Sep 19, 2026 14:04 UTC (Sat) by willy (subscriber, #9762) [Link] (2 responses)

I can't believe nobody's yet commented on the pun

> Slowing down development can be a yellow-card-level annoyance, leading to a desire to do something about the refs.

Usually we're limited to hurling abuse at them from the stands ... now we can send patches!

Yellow cards

Posted Sep 19, 2026 15:04 UTC (Sat) by csamuel (✭ supporter ✭, #2624) [Link] (1 responses)

Now there's an idea, `git refs yellow-card [rule]` to quickly make a hook to give a warning but allow the operation, `git refs red-card [rule]` to quickly make a hook to deny the operation...

Yellow cards

Posted Sep 19, 2026 16:21 UTC (Sat) by josh (subscriber, #17465) [Link]

Having those sub commands exist and do anything at all would be a hilarious Easter egg.

--delete-merged

Posted Sep 20, 2026 10:02 UTC (Sun) by josh (subscriber, #17465) [Link] (14 responses)

The new --delete-merged option to git branch will remove local branches that have been merged into their remote tracking branches.

I'm really looking forward to this, so I can drop my own local scripts for doing this.

--delete-merged

Posted Sep 22, 2026 10:06 UTC (Tue) by ralfj (subscriber, #172874) [Link] (13 responses)

Usually I want to delete branches that have been merged into master/main. Am I misunderstanding what "remote tracking branch" means? I understood it to mean "their upstream branch", i.e., "what push/pull interact with". That seems pretty pointless though. Does git have a concept that somehow makes it understand the relationship between a local feature branch (whose upstream will, for me, usually be the same feature branch in my fork), and master/main?

--delete-merged

Posted Sep 23, 2026 12:44 UTC (Wed) by mathstuf (subscriber, #69389) [Link] (12 responses)

You can do git branch --set-upstream-to=origin/main [branch] (defaulting to setting it for the current branch) which will let tooling know the "target" for the branch. In vim-fugitive, it shows the set of commits symmetrically missing from this "target" as well as my fork's (git config remote.pushdefault $remote) branch of the same name.

--delete-merged

Posted Sep 23, 2026 17:35 UTC (Wed) by ralfj (subscriber, #172874) [Link] (11 responses)

Yeah I know that. That's the branch `git push` pushes to.

But my local feature branches will have `myfork/feature` as their upstream, not `origin/main`. So "delete when merged into corresponding upstream branch" seems entirely useless; all my branches are basically always merged into their corresponding upstream branch.

--delete-merged

Posted Sep 23, 2026 19:21 UTC (Wed) by mathstuf (subscriber, #69389) [Link] (10 responses)

That's not correct. When I do git push and foo is checked out, it goes to gh/mathstuf/foo. But I still do git branch --set-upstream-to=origin/main for foo because that is its ultimate "destination". fugitive also shows the symmetric diff against (the local state of) gh/mathstuf/foo. The terminology sucks here, but I think this is what you want.

--delete-merged

Posted Sep 23, 2026 19:28 UTC (Wed) by ralfj (subscriber, #172874) [Link] (9 responses)

Interesting. I thought upstream is where it pushes stuff. I always use `--set-upstream-to` to configure where to push and that works fine for me. I guess there must be some other configuration difference. Turns out git is even more complicated than I though, apparently. ;-)

I have `push.default = upstream` as none of the other values made sense to me. Matching branches by name seemed just odd? For example I have 3 branches that all track `origin/main` because my 3 worktrees need different default branches... so if I matched by name I couldn't pull any more. Also it would often be the wrong remote; I did not know about `remote.pushdefault`.

--delete-merged

Posted Sep 23, 2026 20:59 UTC (Wed) by mathstuf (subscriber, #69389) [Link] (8 responses)

I use push.default = simple here. I changed from upstream back in 2015, but my dotfiles commit message is just (essentially) "change to use 'simple'"; there are remote.pushdefault-flavored changes around it, so it could be that it is what makes "simple" Just Work™. If I git pull on a branch with --set-upstream-to=, it rebases (because of pull.rebase = merges) on top of that ref. If I git push, it pushes to my fork (with the same branch name by default; I don't usually rename branches like that). If I'm pushing to a contributor's fork (probably the most complicated remote-involving work I do), I tend to just use:

git fetch origin refs/merge-requests/ID/head
git checkout FETCH_HEAD
# Make fixes
git remote add gl/$username gitlab:$username/$project.git # With the hope they show up again :)
git push gl/$username --force-with-lease HEAD:$upstream_name

--delete-merged

Posted Sep 24, 2026 5:58 UTC (Thu) by ralfj (subscriber, #172874) [Link] (7 responses)

Wait, pull and push can use different branches? Wild. I thought that's always the same... somehow I managed to miss all that when reading the git docs for push.default.

But I do need to pull from the feature branches as well, someone else may have pushed a commit there (e.g. via a suggestion that was accepted in the web view for the PR). So it sounds like I must set their upstream to indeed be the branch on my fork. (I don't have such renames for branches I push, just for the main branch where I usually only pull.)

If I'm pushing to a contributor's fork (probably the most complicated remote-involving work I do), I tend to just use:

Here I just use git push <url> HEAD:<branch>. I love that you can push (and fetch) without adding a remote first.

--delete-merged

Posted Sep 24, 2026 12:40 UTC (Thu) by mathstuf (subscriber, #69389) [Link]

But I do need to pull from the feature branches as well

That was with a feature branch (hosted on my fork).

someone else may have pushed a commit there (e.g. via a suggestion that was accepted in the web view for the PR).

Usually here, I fetch from my fork, git reset gitlab/$branch and then git checkout -p to accept diffs into my local checkout. But such edits for us are usually rebase/rewrites than just additional commits (e.g., our reformatting tooling rewrites all history to be formatted instead of adding useless "fix formatting" commits).

Here I just use git push <url> HEAD:<branch>. I love that you can push (and fetch) without adding a remote first.

I do too; adding the remote is the "hope that they're a repeat contributor" :) .

push & pull

Posted Sep 25, 2026 6:28 UTC (Fri) by peniblec (subscriber, #111147) [Link]

Wait, pull and push can use different branches? Wild. I thought that's always the same... somehow I managed to miss all that when reading the git docs for push.default.

Throwing in some more docs that I believe are worth pouring over: gitrevisions(7) § @{upstream} and @{push}. I find it incredibly handy to have these CLI shorthands; I've based a couple aliases around them:

  • "Dusting off an old worktree; oh no, unpushed commits, what did you do Past Me"

    # Get a patch-id for arbitrary diffs.
    diff-id = "!f() { git diff \"$@\" | git patch-id ; } && f"
    
    cmp-push = "!f () { git diff-id @{u}... ; git diff-id @{u}...@{push} ; } && f"
    

    If the two IDs are the same, these "unpushed commits" were merely an unpushed rebase; if not, need to take a closer look.

    (Also answers "will GitLab drop approvals if I push this?" since GitLab relies on patch-id to determine "has this MR changed enough that it needs re-approval")

  • "Ah, reviewee pushed changes; what's up then"

    cmp-series = "!_f () { git range-diff \"$1\"@{upstream}...\"$1\" \"$1\"@{upstream}...\"$1\"@{push} ; } ; _f"
    

    range-diff output takes some getting used to, but I haven't found a better representation of "how did these ranges evolve" so grateful for it.

    (… I see I put a $1 in there but in practice I run this with no arguments in the worktree where I started my review, so this is all HEAD)

    (Speaking of Past Me… why use ... "symmetric difference" ranges here? I might have gotten confused with git-diff's interpretation of ...; diff A...B means "changes in B since fork point with A", but log A...B means "every commit in either A or B but not both". So I should use .. here. I think?

    I mean for the purposes of range-diff, @{upstream}... @{upstream}...@{push} just adds a bunch of uninteresting = commits from @{upstream}, so easy to skim over, and marginally useful to gauge how big the rebase was, I suppose)

TBH not sure how easily the git CLI lets you make @{upstream} ≠ @{push}. I'm happy magit (& its companion forge) DTRT/DWIM for me: when I start a topic branch, @{upstream} is origin/trunk and @{push} is <remote.pushdefault>/topic/foo; when I start reviewing a merge request, @{upstream} is set to the target branch and @{push} to wherever the reviewee will land new revisions.

--delete-merged

Posted Sep 26, 2026 17:23 UTC (Sat) by sionescu (subscriber, #59410) [Link] (4 responses)

> But I do need to pull from the feature branches as well, someone else may have pushed a commit there

Best to never do that. If a commit is not on the master branch, it does not exist.

--delete-merged

Posted Sep 26, 2026 17:29 UTC (Sat) by ralfj (subscriber, #172874) [Link] (3 responses)

It is pretty standard in a GH workflow for new commits to be added to your feature branch by the reviewer or another maintainer. IMO that's great, e.g. when fixing typos in comments (which can be done fully from the web UI). So pulling from feature branches is not something I'd like to miss.

--delete-merged

Posted Sep 26, 2026 17:32 UTC (Sat) by sionescu (subscriber, #59410) [Link] (2 responses)

It's definitely not standard. The tooling allows it, but it doesn't mean it's a good idea. The reviewers should treat it as read-only and let the author deal with the changes, as a matter of responsibility.

--delete-merged

Posted Sep 26, 2026 17:54 UTC (Sat) by ralfj (subscriber, #172874) [Link]

> It's definitely not standard.

It is in my experience. It seems your experience is different. That's fine. I don't know which one is more representative. But the fact that it's part of the GH UI by default is IMO a sign that it's probably pretty widely used.

> The reviewers should treat it as read-only

I disagree, and so does the project I do most of my open-source work on. Having reviewers mutate branches works very well for us.

--delete-merged

Posted Sep 27, 2026 1:00 UTC (Sun) by mathstuf (subscriber, #69389) [Link]

Eh. Some things just aren't worth the author's time. For example, we enforce formatting. On every commit because "fix formatting" commit noise is atrocious. Our robot can do this (on demand; not automatically like some CI setups do…) and will push back to the source branch with the new history (using --force-with-lease and the commit it started with to avoid nuking author work). Asking the author (or the reviewer to even have to look at formatting concerns) to do such rote tasks is just a waste of limited time if you ask me.

Merge commits can't be that hard to edit

Posted Sep 20, 2026 11:01 UTC (Sun) by jengelh (subscriber, #33263) [Link]

[git history drop] still refuses to work if the history contains merge commits

I thought “How hard could it be?” and wrote a concept (link not guaranteed to exist in the future):

#!/bin/sh -e
# tree filtering script
export rewritten_parent="$1"
set -- $(git rev-list -1 --parents "$GIT_COMMIT")
if test "$#" -ge 3; then
        if ! git revert -n "$offending"; then
                echo "stopped at merge $GIT_COMMIT" >&2
                bash </dev/tty >/dev/tty 2>/dev/tty
        fi
else
        GIT_ALLOW_NULL_SHA1=1 git read-tree -i -m $rewritten_parent
        git checkout-index -f -u -a || exit 1
        git clean -dqfx
        if test "$GIT_COMMIT" != "$offending"; then
                if ! git cherry-pick -n ${GIT_COMMIT}^..${GIT_COMMIT}; then
                        echo "stopped at $GIT_COMMIT" >&2
                        bash </dev/tty >/dev/tty 2>/dev/tty
                fi
        fi
fi
#!/bin/sh
export offending="$(git rev-parse "$1")"
FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch \
        --tree-filter '/tmp/ghd/tf $(map $(git rev-parse $GIT_COMMIT^))' \
        --msg-filter /tmp/ghd/mf $offending^..master

I probably just added another pitfall on top of what git-filter-branch already has, but whatever!

What is OURS is not THEIRS

Posted Sep 22, 2026 7:00 UTC (Tue) by tchernobog (subscriber, #73595) [Link] (2 responses)

Still hoping somebody will rename THEIRS and OURS when doing rebases for the next version of git (or make it configurable).

20 years and my brain still is confused about which one is which... Especially when I rebase my changes on top of a branch ("OURS" changes? :-))

What is OURS is not THEIRS

Posted Sep 22, 2026 10:04 UTC (Tue) by ralfj (subscriber, #172874) [Link] (1 responses)

Yeah the fact that this is effectively swapped between "git merge" and "git rebase" is very confusing.

What is OURS is not THEIRS

Posted Sep 24, 2026 1:33 UTC (Thu) by NYKevin (subscriber, #129325) [Link]

How I remember it (caveat: I don't actually use Git, but Mercurial does largely the same thing modulo wording differences): "Ours" is always "whatever is currently checked out" and "theirs" is always "the other thing." When you are rebasing, this is internally implemented as checking out ("rewinding to") the destination and then copying ("replaying") the source on top, so "ours" is the destination and "theirs" is the source.

Is it good that I have to remember internal implementation details of something I don't even work on? No, of course not! But at least I can remember which side is which.

Release plans

Posted Sep 22, 2026 16:04 UTC (Tue) by corbet (editor, #1) [Link]

For those who have already read the article, I have added this update to the end:

At Kernel Recipes, Steinhardt described the plan that had been agreed to regarding releases. The December 2026 release will be called 2.98, as a signal that the 3.0 transition is coming. The 2.99 release will follow in April 2027; that will be a long-term-support release. Git 3.0 will also be released at the same time, and will be the base for all subsequent releases.

Choice of sha-256?

Posted Sep 23, 2026 3:10 UTC (Wed) by nhippi (subscriber, #34640) [Link] (1 responses)

The choice of sha-256 in 2026 seems a bit odd as sha-3 family has already existed since 2015.

I dont think anyone is depreciating sha-256 yet. But since changing the hash in git is such a hard break...

Choice of sha-256?

Posted Sep 23, 2026 5:57 UTC (Wed) by corbet (editor, #1) [Link]

The choice of hash was made in 2018, some of the reasoning is described in this commit.

Platforms without Rust support

Posted Sep 23, 2026 6:18 UTC (Wed) by jnareb (subscriber, #46500) [Link]

What about the NonStop architecture support: https://lwn.net/Articles/998115/ ?

Has this changed?


Copyright © 2026, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds