Ubuntu Server Gazette – Issue 19: Automatic Debian merges with `git ubuntu deltarebase`

,

Ubuntu is built on Debian, and hundreds of packages in Ubuntu carry an Ubuntu delta – changes on top of the Debian version.
When Debian ships a new package version, we want to offer it in Ubuntu, but without losing our delta.
This process is known as “merging”.

But don’t confuse this with a git merge.
As the Ubuntu project docs point out, the Ubuntu merge process “(counter-intuitively) does not directly involve a git merge”.
What actually happens is this: you take the Ubuntu changes made on top of the previous Debian version and replay them on top of the new one.
In git terms, that is a rebase.

Changes that have been upstreamed to Debian or Upstream (1.1-4ubuntu2 above) disappear during the rebase.
The remaining delta changes are replayed on top of the new Debian version.
Every upstreamed change is one less delta to carry into the next cycle, which is the whole point of upstreaming.

So the new git-ubuntu command is named for what it does: git ubuntu deltarebase.
It automates the repetitive process to perform an Ubuntu merge with optional AI help for the parts that need judgement and fixing.

The old way

Before git-ubuntu, a merge was an exercise in tarballs and debdiffs:

  1. Fetch the current Ubuntu source package (apt source, pull-lp-source, …).
  2. Fetch the new Debian source package and its orig tarball.
  3. Pick the Ubuntu changes out of the old package and copy/merge them over by hand.
  4. Write a new changelog entry and splice in the old Ubuntu entries.
  5. Refresh, fix or drop patches until quilt push -a is happy.
  6. Build, test and fix the package as needed.
  7. Attach a debdiff to a bug and wait for review.

History lived in changelog prose, and “which of these changes are still needed?” was answered by archaeology.

The current way

In our current flow, with git-ubuntu, every package has a git repository, and the delta is a series of commits.
The merging guide and its cheat sheet describe the process well, and it is long:

  1. Check Launchpad for the current Ubuntu and Debian versions.
  2. Run git ubuntu merge start to reconstruct the previous delta as commit copies.
  3. Rebase interactively to split mixed commits without changing the resulting tree, then tag split/….
  4. Drop changelog and update-maintainer noise, squash and reorder related changes into logical commits, then tag logical/….
  5. Run git rebase --onto new/debian old/debian and resolve conflicts, to move the delta onto the new Debian version.
  6. Check that each patch in debian/patches is still needed and still applies, then refresh, fix or drop it.
  7. Run git ubuntu merge finish to create the merged changelog.
  8. Fetch the Debian orig tarball.
  9. Build, test, fix problems, then push the branch and all the tags.

This is a big step up from tarballs, since the process is focused on overcoming former quality issues, where too often something was forgotten or handled badly - hence it needed to be strict and detailed, and noe of the steps is hard on its own.

But together they add up to lots of manual work per package, mostly relaying refs from one command to the next, and one wrong ref or a stray tree change silently corrupts the result.
Get interrupted halfway through and you have to remember exactly where you were.
Now multiply that by the length of Merge-o-Matic’s to-do list, and you can see why merge season calls for coffee.

The new way

Some packages have a quite nuanced or massive complex delta that needs guidance, but the majority of merges are not that hard, just time-consuming and tedious.

So why should a human type all of it, when we can deduce most of the information automatically?
You can do so with this one command:

git ubuntu deltarebase start --bug 2158806

It runs the whole workflow as named steps:

prepare → reconstruct → split → logical → rebase → patches → build → finalize → submit

This is very much and intentionally the automated version of what you have read above in “The current way”, and therefore quite similar:

  • prepare: checks Launchpad versions against the imported tags, refuses downgrades and a dirty tree, and names the submission branch (e.g. lp2158806-merge-stonking).
  • reconstruct: recreates the delta, moving changelog and update-maintainer changes into trailing commits so the real changes stay clean.
  • split and logical: steps to separate and unite the commits to clean up the previous delta.
  • rebase: moves the delta onto the new Debian version with a plain git rebase --onto (this is the step that gives the command its name).
  • patches: checks the quilt series with gbp pq import, again using git to resolve conflicts.
  • build: (optional) builds the package in a throwaway container.
  • finalize: generates the merged changelog and maintainer metadata update.
  • submit: hands over a branch ready for review.

Each step must pass its check before continuing with the next step.

If a step needs a human (a rebase conflict, a patch that no longer applies, a build that fails), deltarebase stops, explains what to do, and saves its state in .git/.
You can then fix the problem with plain git:

git ubuntu deltarebase status    # where am I, why did it stop?
git ubuntu deltarebase continue  # verify my fix and carry on
git ubuntu deltarebase abort     # back to where I started, all refs removed

Want to stop early and inspect?
Use --target-step split (or any other step) to stop there.
Want to try again?
Use start --restart, or --reversion 2 for a second submission branch next to the first one.

And if nothing is left of the delta after rebasing, it tells you to request a sync instead of a merge.
Deleting the whole delta is the best merge there is.

Invariants, not trust

The core idea: every step comes with a check that does not depend on who did the work.

  • split must not change the tree compared to the reconstruct tag.
  • logical may reorder and squash commits, but must not change the tree.
  • rebase must actually contain the new Debian base in its history.
  • patches must pass gbp pq import.
  • build only passes when the build & test succeeds.
  • finalize may only change debian/changelog.

That is what makes delegating safe, whether to a colleague, to future you, or to a machine:

Letting the machine do the boring parts

git ubuntu deltarebase start --bug 2158806 --ai

With --ai, those steps that need judgement (split, logical, rebase, patches, build and finalize) go to an AI agent, which can use any compatible endpoint via --ai-url.
Each step comes with a skill file that teaches the agent the Ubuntu way of doing it, so it knows, for example, that a split must not change a single byte of the tree.

Since this just automates the “conflict resolution” via ai, the process is identicall to the pure-deterministic version without --ai.

The agent never touches your checkout:

  • It works in a LXD container on a fresh clone of your repository.
  • Data is transferred via git, through a ai-review/<step>/<branch> branch, and only after it passes that step’s check.
  • Build fixes only count if the agent’s final commit was built successfully in the trusted build container.
  • If the agent gives up, you get its report (what it tried, what it suspects, what it suggests) plus its work-in-progress branch, and the step is handed over to you.
    git ubuntu deltarebase continue then checks your fix instead of running the agent again.

The machine does the drudgery, and never gets to touch the archive.
The human stays the reviewer and the uploader.

Why it matters

These are rough estimates, but the direction is clear: simple and intermediate merges become review work, and humans spend their time on the complex 20%, where it matters.

  • You are faster, because the steps are driven automatically instead of you following a cheatsheet and remembering state in your head.
  • Since steps are automatically validated, you’ll make fewer mistakes.
  • The workflow state is stored in the repository (like in the middle of git rebase), so you can abort, continue or start over.
  • The clean, checked pipeline allows us to do semi-automated deltarebases across many packages automatically.

Try it

git-ubuntu deltarebase is available on the snap edge channel, and still experimental to incorporate your feedback.

sudo snap refresh --channel latest/edge git-ubuntu

Run it inside an imported package repository:

git ubuntu clone openldap && cd openldap
git ubuntu deltarebase start --bug 2158806

To use it with AI, set up access in ~/.gitconfig:

[gitubuntu]
    lpuser = jj
    aiurl = https://openrouter.ai/api/v1/                                                                                                                                          
    aimodel = z-ai/glm-5.3-flash@high
    aitoken = <token>

And run:

git ubuntu deltarebase start --bug <id> --ai --ai-show-thinking

Once you have reviewed the result branch, open a merge proposal as usual.
Please report your feedback and bug reports below this post or on Launchpad!

References

  • Merges & syncs: why Ubuntu carries a delta at all
  • Merging: the “delta rebasing” overview, with a diagram comparing it to a git merge
  • Merge process and cheat sheet: the manual workflow that deltarebase automates
  • git-ubuntu: the importer and the repository layout that make all of this possible
  • Merge-o-Matic: packages waiting to be merged, a.k.a. your to-do list
7 Likes

Thanks for working on this. I’ll give it a go on my next merge. :+1:

1 Like