user [email protected]
usertags 1140463 python3.15
tags 1140463 patch
thanks
Hi!
While rebuilding the python related packages against the Python 3.15rc2 version I ran into this FTBFS [1].
To make hg-git work with a newer dulwich, I had to add upstream patches to
support dulwich 1.2 and mercurial 7.2 (17 patches in total)
I also added a patch to handle ChecksumMismatch raised by dulwich >= 1.2.7.
I've applied these fixes in the sandbox [2] to verify that it builds
successfully, these should be applied in Debian to get the package back to a
healthy state.
Happy hacking,
[1]: https://debusine.debian.net/debian/r-python-python3.15/artifact/4855744/
[2]: https://debusine.debian.net/debian/r-python-python3.15/
--
"Can you imagine what I would do if I could do all I can?" -- Sun Tzu
Saludos /\/\ /\ >< `/
diff --git a/debian/changelog b/debian/changelog
index 73b29420..43a3002c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,13 @@
+hg-git (1.2.0-3) UNRELEASED; urgency=medium
+
+ * Backport upstream fixes for Dulwich 1.2 and Mercurial 7.2
+ compatibility.
+ * Add patch dulwich-checksum-mismatch: fix gverify --fsck with Dulwich
+ >= 1.2.7.
+ * Drop from dulwich-compat the changes replaced by upstream backports.
+
+ -- Maximiliano Curia <[email protected]> Mon, 28 Sep 2026 18:54:44 +0200
+
hg-git (1.2.0-2) unstable; urgency=medium
* Add patch dulwich-compat: fixes compatibility with Dulwich 0.24.
diff --git a/debian/control b/debian/control
index 36ddb865..af358c83 100644
--- a/debian/control
+++ b/debian/control
@@ -10,8 +10,8 @@ Build-Depends:
git,
openssh-client,
python3,
- python3-dulwich (>= 0.24),
- python3-dulwich (<< 0.25),
+ python3-dulwich (>= 1.2.7),
+ python3-dulwich (<< 2),
python3-mercurial,
python3-setuptools,
python3-setuptools-scm,
@@ -27,7 +27,7 @@ Package: mercurial-git
Architecture: all
Depends:
python3-dulwich (>= 0.24),
- python3-dulwich (<< 0.25),
+ python3-dulwich (<< 2),
python3-mercurial,
${misc:Depends},
${python3:Depends},
diff --git a/debian/patches/bundle-fix-test-bundle.t.-be-backward-compatible-in-a-ugly-w.patch b/debian/patches/bundle-fix-test-bundle.t.-be-backward-compatible-in-a-ugly-w.patch
new file mode 100644
index 00000000..56bf1457
--- /dev/null
+++ b/debian/patches/bundle-fix-test-bundle.t.-be-backward-compatible-in-a-ugly-w.patch
@@ -0,0 +1,78 @@
+From 1ec663f3767b625e3d80d79b20cd6b9aeca14a87 Mon Sep 17 00:00:00 2001
+From: Antonio Muci <[email protected]>
+Date: Thu, 14 May 2026 00:57:06 +0200
+Subject: [PATCH] bundle: fix test-bundle.t. be backward compatible in a ugly
+ way
+
+hg 7.2 removed the "ui" argument from _addpartsfromopts() in 63c4874dacb6
+("bundle: remove unused argument")
+
+diff --git a/hggit/bundle.py b/hggit/bundle.py
+index fc8d0392..b6ba2ebd 100644
+--- a/hggit/bundle.py
++++ b/hggit/bundle.py
+@@ -1,3 +1,4 @@
++import inspect
+ import io
+
+ from mercurial import bundle2
+@@ -57,19 +58,46 @@ def addpartrevgittags(repo, bundler, outgoing):
+ bundler.newpart(BUNDLEPART_TAGS, data=chunks, mandatory=False)
+
+
[email protected](bundle2, '_addpartsfromopts')
+-def _addpartsfromopts(orig, ui, repo, bundler, source, outgoing, opts):
+- orig(ui, repo, bundler, source, outgoing, opts)
+-
+- if opts.get(CAPABILITY_MAP, False) or ui.configbool(
+- b'experimental', b'hg-git-bundle'
+- ):
+- addpartrevgitmap(repo, bundler, outgoing)
+-
+- if opts.get(CAPABILITY_TAGS, False) or ui.configbool(
+- b'experimental', b'hg-git-bundle'
+- ):
+- addpartrevgittags(repo, bundler, outgoing)
++if inspect.getfullargspec(bundle2._addpartsfromopts).args == [
++ 'repo',
++ 'bundler',
++ 'source',
++ 'outgoing',
++ 'opts',
++]:
++ # hg >= 7.2
++ #
++ # hg 7.2 removed the "ui" argument from _addpartsfromopts() in
++ # 63c4874dacb6 ("bundle: remove unused argument").
++ @eh.wrapfunction(bundle2, '_addpartsfromopts')
++ def _addpartsfromopts(orig, repo, bundler, source, outgoing, opts):
++ orig(repo, bundler, source, outgoing, opts)
++
++ if opts.get(CAPABILITY_MAP, False) or repo.ui.configbool(
++ b'experimental', b'hg-git-bundle'
++ ):
++ addpartrevgitmap(repo, bundler, outgoing)
++
++ if opts.get(CAPABILITY_TAGS, False) or repo.ui.configbool(
++ b'experimental', b'hg-git-bundle'
++ ):
++ addpartrevgittags(repo, bundler, outgoing)
++
++else:
++ # hg <= 7.1.2 still has the "ui" argument
++ @eh.wrapfunction(bundle2, '_addpartsfromopts')
++ def _addpartsfromopts(orig, ui, repo, bundler, source, outgoing, opts):
++ orig(ui, repo, bundler, source, outgoing, opts)
++
++ if opts.get(CAPABILITY_MAP, False) or ui.configbool(
++ b'experimental', b'hg-git-bundle'
++ ):
++ addpartrevgitmap(repo, bundler, outgoing)
++
++ if opts.get(CAPABILITY_TAGS, False) or ui.configbool(
++ b'experimental', b'hg-git-bundle'
++ ):
++ addpartrevgittags(repo, bundler, outgoing)
+
+
+ @eh.extsetup
diff --git a/debian/patches/bundle-fixe-test-hg-clone.t-and-test-serve-hg.t-with-hggit-o.patch b/debian/patches/bundle-fixe-test-hg-clone.t-and-test-serve-hg.t-with-hggit-o.patch
new file mode 100644
index 00000000..f8562a36
--- /dev/null
+++ b/debian/patches/bundle-fixe-test-hg-clone.t-and-test-serve-hg.t-with-hggit-o.patch
@@ -0,0 +1,45 @@
+From 61d2a1c2d127a16c744894537ee19d506c804379 Mon Sep 17 00:00:00 2001
+From: Antonio Muci <[email protected]>
+Date: Thu, 14 May 2026 09:58:01 +0200
+Subject: [PATCH] bundle: fixe test-hg-clone.t and test-serve-hg.t#with-hggit
+ on hg >= 7.2
+
+bundle2.getrepocaps() was deprecated and will be removed in 7.3. The deprecation
+happened via util.deprecated(), which apparently breaks hggit wrapping.
+
+This change is backwards compatible (tested with hg 7.1.2 and 7.2.1).
+
+diff --git a/hggit/bundle.py b/hggit/bundle.py
+index b6ba2ebd..eceece8c 100644
+--- a/hggit/bundle.py
++++ b/hggit/bundle.py
+@@ -17,8 +17,8 @@ BUNDLEPART_MAP = b'exp-hg-git-map'
+ BUNDLEPART_TAGS = b'exp-hg-git-tags'
+
+
[email protected](bundle2, 'getrepocaps')
+-def getrepocaps(orig, repo, **kwargs):
[email protected](compat.bundle_caps_mod, compat.get_repo_caps_function_name)
++def get_repo_caps(orig, repo, **kwargs):
+ caps = orig(repo, **kwargs)
+
+ if repo.ui.configbool(b'experimental', b'hg-git-serve'):
+diff --git a/hggit/compat.py b/hggit/compat.py
+index a625ef16..a3ba9617 100644
+--- a/hggit/compat.py
++++ b/hggit/compat.py
+@@ -73,3 +73,14 @@ else:
+ from mercurial.commands import init as commands_init
+
+ assert commands_init # silence pyflakes
++
++try:
++ # hg >= 7.2
++ from mercurial.exchanges import bundle_caps as bundle_caps_mod
++
++ get_repo_caps_function_name = 'get_repo_caps'
++except ImportError:
++ import mercurial.bundle2 as bundle_caps_mod
++
++ get_repo_caps_function_name = 'getrepocaps'
++ assert bundle_caps_mod # silence pyflakes
diff --git a/debian/patches/compat-add-support-for-dulwich-0.23.2.patch b/debian/patches/compat-add-support-for-dulwich-0.23.2.patch
new file mode 100644
index 00000000..42a176b8
--- /dev/null
+++ b/debian/patches/compat-add-support-for-dulwich-0.23.2.patch
@@ -0,0 +1,46 @@
+From ed2cd8c4205c01c257810aa01c4b17777b0e133c Mon Sep 17 00:00:00 2001
+From: Dan Villiom Podlaski Christiansen <[email protected]>
+Date: Tue, 8 Jul 2025 17:53:27 +0200
+Subject: [PATCH] compat: add support for dulwich 0.23.2
+
+
+diff --git a/hggit/compat.py b/hggit/compat.py
+new file mode 100644
+index 00000000..8106212d
+--- /dev/null
++++ b/hggit/compat.py
+@@ -0,0 +1,12 @@
++from dulwich.object_store import PackBasedObjectStore
++
++
++if hasattr(PackBasedObjectStore, 'delete_loose_object'):
++
++ def delete_loose_object(object_store: PackBasedObjectStore, sha: bytes):
++ object_store.delete_loose_object(sha)
++
++else:
++
++ def delete_loose_object(object_store: PackBasedObjectStore, sha: bytes):
++ object_store._remove_loose_object(sha)
+diff --git a/hggit/gc.py b/hggit/gc.py
+index d1fd31ac..530a373b 100644
+--- a/hggit/gc.py
++++ b/hggit/gc.py
+@@ -8,6 +8,8 @@ import typing
+ from dulwich.object_store import PackBasedObjectStore
+ from mercurial import ui as uimod
+
++from . import compat
++
+
+ class Worker(threading.Thread):
+ """Worker thread that we can stop.
+@@ -56,7 +58,7 @@ def _process_batch(ui, object_store, shas):
+ object_store.add_objects(list(objects))
+
+ for obj, path in objects:
+- object_store._remove_loose_object(obj.id)
++ compat.delete_loose_object(object_store, obj.id)
+
+ ui.debug(b'packed %d loose objects!\n' % len(shas))
+
diff --git a/debian/patches/compat-fix-test-url-parsing.py.patch b/debian/patches/compat-fix-test-url-parsing.py.patch
new file mode 100644
index 00000000..702a8507
--- /dev/null
+++ b/debian/patches/compat-fix-test-url-parsing.py.patch
@@ -0,0 +1,85 @@
+From 718c72efb61ffea4a53628ab9e45a5a18516c174 Mon Sep 17 00:00:00 2001
+From: Antonio Muci <[email protected]>
+Date: Wed, 13 May 2026 13:15:02 +0200
+Subject: [PATCH] compat: fix test-url-parsing.py
+
+Before this commit, test-url-parsing.py was failing with the following error:
+
+```
+$ tests/run-tests.py --with-hg=<BASE>/venv3.14-hg721-dulwich025/bin/hg tests/test-url-parsing.py
+
+Traceback (most recent call last):
+ File "<BASE>/tests/test-url-parsing.py", line 113, in <module>
+ tc.setUp()
+ ~~~~~~~~^^
+ File "<BASE>/tests/test-url-parsing.py", line 21, in setUp
+ commands.init(ui.ui(), self.tmpdir)
+ ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
+TypeError: init() takes 0 positional arguments but 2 were given
+
+ERROR: test-url-parsing.py output changed
+!
+Failed test-url-parsing.py: output changed and returned error code 1
+```
+
+The change was introduced in mercurial commit 43a67798b33a ("init: introduce a
+new initialization system to explicitly load some module").
+
+TODO:
+- we could move the compat code in the test, since it is used only there
+- we could use hasattr(mercurial.commands, "_init") instead of inspect
+
+diff --git a/hggit/compat.py b/hggit/compat.py
+index aa4e6da5..a625ef16 100644
+--- a/hggit/compat.py
++++ b/hggit/compat.py
+@@ -1,3 +1,6 @@
++import inspect
++
++import mercurial.commands # TODO: do not re-export mercurial.commands (use "as _commands"?)
+ from dulwich.object_store import PackBasedObjectStore
+
+
+@@ -57,3 +60,16 @@ except ImportError:
+ from mercurial.hg import defaultdest as default_dest
+
+ assert default_dest # silence pyflakes
++
++if inspect.getfullargspec(mercurial.commands.init).args == []:
++ # hg >= 7.2
++ #
++ # this accesses the private function _init(), and assumes that:
++ #
++ # >>> inspect.getfullargspec(mercurial.commands._init)
++ # FullArgSpec(args=['ui', 'dest'], varargs=None, varkw='opts', defaults=(b'.',), kwonlyargs=[], kwonlydefaults=None, annotations={})
++ from mercurial.commands import _init as commands_init
++else:
++ from mercurial.commands import init as commands_init
++
++ assert commands_init # silence pyflakes
+diff --git a/tests/test-url-parsing.py b/tests/test-url-parsing.py
+index b69f9eff..376271d0 100755
+--- a/tests/test-url-parsing.py
++++ b/tests/test-url-parsing.py
+@@ -7,10 +7,11 @@ except ImportError:
+ sys.exit(80)
+
+ import os, tempfile, unittest, shutil
+-from mercurial import ui, hg, commands, pycompat
++from mercurial import ui, hg, pycompat
+
+ sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir))
+
++from hggit import compat
+ from hggit.git_handler import GitHandler
+
+
+@@ -18,7 +19,7 @@ class TestUrlParsing(object):
+ def setUp(self):
+ # create a test repo location.
+ self.tmpdir = tempfile.mkdtemp('hg-git_url-test').encode('utf-8')
+- commands.init(ui.ui(), self.tmpdir)
++ compat.commands_init(ui.ui(), self.tmpdir)
+ repo = hg.repository(ui.ui(), self.tmpdir)
+ self.handler = GitHandler(repo, ui.ui())
+
diff --git a/debian/patches/compat-hg-7.2-moved-parthandler-from-bundle2-to-bundle2_part.patch b/debian/patches/compat-hg-7.2-moved-parthandler-from-bundle2-to-bundle2_part.patch
new file mode 100644
index 00000000..63308f3b
--- /dev/null
+++ b/debian/patches/compat-hg-7.2-moved-parthandler-from-bundle2-to-bundle2_part.patch
@@ -0,0 +1,56 @@
+From adf77111b5e3feef27f61204858ec88df563a108 Mon Sep 17 00:00:00 2001
+From: Antonio Muci <[email protected]>
+Date: Sat, 2 May 2026 17:05:59 +0200
+Subject: [PATCH] compat: hg 7.2 moved parthandler from bundle2 to
+ bundle2_part_handlers
+
+The change happened on 2025-08-07 in mercurial 503d63ff9b1a ("cycle-breaking:
+register most bundle2 part handler in their own modules"), and was integrated in
+mercurial since 7.2.
+
+diff --git a/hggit/bundle.py b/hggit/bundle.py
+index 7d5bdd75..fc8d0392 100644
+--- a/hggit/bundle.py
++++ b/hggit/bundle.py
+@@ -6,6 +6,7 @@ from mercurial import exthelper
+
+ from mercurial.node import bin, hex
+
++from . import compat
+
+ eh = exthelper.exthelper()
+
+@@ -73,7 +74,7 @@ def _addpartsfromopts(orig, ui, repo, bundler, source, outgoing, opts):
+
+ @eh.extsetup
+ def install_server_support(ui):
+- @bundle2.parthandler(BUNDLEPART_MAP)
++ @compat.parthandler(BUNDLEPART_MAP)
+ def handlebundlemap(op, inpart):
+ ui.debug(b'unbundling git map\n')
+
+@@ -105,7 +106,7 @@ def install_server_support(ui):
+
+ addpartrevgitmap(repo, bundler, outgoing)
+
+- @bundle2.parthandler(BUNDLEPART_TAGS)
++ @compat.parthandler(BUNDLEPART_TAGS)
+ def handlebundletags(op, inpart):
+ with io.BytesIO() as buf:
+ while not inpart.consumed:
+diff --git a/hggit/compat.py b/hggit/compat.py
+index 8ca4e727..a0072f5e 100644
+--- a/hggit/compat.py
++++ b/hggit/compat.py
+@@ -20,3 +20,11 @@ except ImportError:
+ from dulwich.refs import PEELED_TAG_SUFFIX
+
+ assert PEELED_TAG_SUFFIX # silence pyflakes
++
++try:
++ # hg >= 7.2
++ from mercurial.bundle2_part_handlers import parthandler
++except ImportError:
++ from mercurial.bundle2 import parthandler
++
++ assert parthandler # silence pyflakes
diff --git a/debian/patches/compat-hg-7.2-removed-repository.peer-and-introduced-exchang.patch b/debian/patches/compat-hg-7.2-removed-repository.peer-and-introduced-exchang.patch
new file mode 100644
index 00000000..ec84749b
--- /dev/null
+++ b/debian/patches/compat-hg-7.2-removed-repository.peer-and-introduced-exchang.patch
@@ -0,0 +1,89 @@
+From 8988531739f832a933db8ef1efcbbebe4a39780d Mon Sep 17 00:00:00 2001
+From: Antonio Muci <[email protected]>
+Date: Wed, 13 May 2026 00:59:38 +0200
+Subject: [PATCH] compat: hg 7.2 removed repository.peer and introduced
+ exchanges.peer.Peer
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The change happened on 2025-10-16 in 15e04d999f43 ("interface: extract the
+non-typing code for the peer"), and was integrated in mercurial since 7.2.
+
+
+exchanges.peer.Peer is still an abstract class, but we can add trivial
+implementations for clonebundles() and get_cached_bundle_inline().
+
+The two concrete classes in hg >=7.2 are:
+1. mercurial.localrepo.localpeer
+2. mercurial.wireprotov1peer.wirepeer
+
+They will be used later in the series.
+
+Albeit not exact, we give the old "peer" class the new name "Peer", in order to
+smooth the change when we will get rid of the compatibility code.
+
+Co-authored-by: Jordi GutiƩrrez Hermoso <[email protected]>
+
+diff --git a/hggit/compat.py b/hggit/compat.py
+index a0072f5e..cedfc5ed 100644
+--- a/hggit/compat.py
++++ b/hggit/compat.py
+@@ -28,3 +28,11 @@ except ImportError:
+ from mercurial.bundle2 import parthandler
+
+ assert parthandler # silence pyflakes
++
++try:
++ # hg >= 7.2
++ from mercurial.exchanges.peer import Peer
++except ImportError:
++ from mercurial.interfaces.repository import peer as Peer
++
++ assert Peer # silence pyflakes
+diff --git a/hggit/gitrepo.py b/hggit/gitrepo.py
+index dab22ce4..990eb300 100644
+--- a/hggit/gitrepo.py
++++ b/hggit/gitrepo.py
+@@ -9,15 +9,17 @@ from mercurial import (
+ pycompat,
+ wireprotov1peer,
+ )
+-from mercurial.interfaces import repository
+ from mercurial.utils import urlutil
+
+-from . import util
++from . import (
++ compat,
++ util,
++)
+
+ eh = exthelper.exthelper()
+
+
+-class gitrepo(repository.peer):
++class gitrepo(compat.Peer):
+ def __init__(self, ui, path=None, create=False, intents=None, **kwargs):
+ if create: # pragma: no cover
+ raise error.Abort(b'Cannot create a git repository.')
+@@ -121,6 +123,20 @@ class gitrepo(repository.peer):
+ def _submitone(self, op, args):
+ return None
+
++ def clonebundles(self):
++ """Trivial implementation of abstract hg >= 7.2 method.
++
++ No harm for hg <= 7.1.2
++ """
++ pass
++
++ def get_cached_bundle_inline(self, path):
++ """Trivial implementation of abstract hg >= 7.2 method.
++
++ No harm for hg <= 7.1.2
++ """
++ pass
++
+
+ instance = gitrepo
+
diff --git a/debian/patches/compat-support-dulwich-0.25.patch b/debian/patches/compat-support-dulwich-0.25.patch
new file mode 100644
index 00000000..e6dc0a4e
--- /dev/null
+++ b/debian/patches/compat-support-dulwich-0.25.patch
@@ -0,0 +1,122 @@
+From 7a0fed39bebc3fe0e50dad69ee5ae6bcf44c4534 Mon Sep 17 00:00:00 2001
+From: Antonio Muci <[email protected]>
+Date: Fri, 19 Dec 2025 23:03:17 +0100
+Subject: [PATCH] compat: support dulwich 0.25
+
+In dulwich 0.24.10 ANNOTATED_TAG_SUFFIX was already an alias for
+PEELED_TAG_SUFFIX. We stopped relying on the alias in the previous commit.
+
+Dulwich 0.25.0 removed the alias, and moved PEELED_TAG_SUFFIX from dulwich.refs
+to dulwich.protocol.
+
+This commit centralizes in hggit.compat a backward compatiblity fix for
+supporting PEELED_TAG_SUFFIX in dulwich <=0.24.10 and >=0.25.10.
+
+The tests that broke on dulwich 0.25.0 and that are now fixed are
+test-url-parsing.py and test-doctest.py.
+
+The reason for not importing compat.PEELED_TAG_SUFFIX is explained at
+https://foss.heptapod.net/mercurial/hg-git/-/merge_requests/269#note_512928
+
+> Normally, in Mercurial and hg-git, we never import attributes from a module,
+> we only ever import modules. This ensures that lazy importing works.
+
+diff --git a/hggit/compat.py b/hggit/compat.py
+index 8106212d..8ca4e727 100644
+--- a/hggit/compat.py
++++ b/hggit/compat.py
+@@ -10,3 +10,13 @@ else:
+
+ def delete_loose_object(object_store: PackBasedObjectStore, sha: bytes):
+ object_store._remove_loose_object(sha)
++
++
++try:
++ # dulwich >= 0.25.0
++ from dulwich.protocol import PEELED_TAG_SUFFIX
++except ImportError:
++ # dulwich <= 0.24.10
++ from dulwich.refs import PEELED_TAG_SUFFIX
++
++ assert PEELED_TAG_SUFFIX # silence pyflakes
+diff --git a/hggit/git2hg.py b/hggit/git2hg.py
+index 7b2dcad0..a83c3437 100644
+--- a/hggit/git2hg.py
++++ b/hggit/git2hg.py
+@@ -8,7 +8,6 @@ from dulwich.objects import Commit, Tag
+ from dulwich.refs import (
+ LOCAL_BRANCH_PREFIX,
+ LOCAL_TAG_PREFIX,
+- PEELED_TAG_SUFFIX,
+ )
+ from mercurial.i18n import _
+
+@@ -16,6 +15,7 @@ from mercurial.node import bin, short
+ from mercurial import error, util as hgutil
+ from mercurial import phases
+
++from . import compat
+ from . import config
+
+
+@@ -306,7 +306,7 @@ def filter_refs(refs, heads):
+ raise error.RepoLookupError(msg)
+ else:
+ for ref, sha in refs.items():
+- if not ref.endswith(PEELED_TAG_SUFFIX) and (
++ if not ref.endswith(compat.PEELED_TAG_SUFFIX) and (
+ ref.startswith(LOCAL_BRANCH_PREFIX)
+ or ref.startswith(LOCAL_TAG_PREFIX)
+ or ref == b'HEAD'
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+index 59e19673..458c6c34 100644
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -13,7 +13,6 @@ from dulwich.pack import apply_delta
+ from dulwich.refs import (
+ LOCAL_BRANCH_PREFIX,
+ LOCAL_TAG_PREFIX,
+- PEELED_TAG_SUFFIX,
+ )
+ from dulwich.repo import Repo, check_ref_format
+ from dulwich import client
+@@ -40,6 +39,7 @@ from mercurial import (
+ )
+
+ from . import _ssh
++from . import compat
+ from . import gc
+ from . import git2hg
+ from . import hg2git
+@@ -1640,8 +1640,11 @@ class GitHandler(object):
+ # pull tags pointing to known revisions, including
+ # annotated tags
+ for ref, sha in refs.items():
+- if ref.endswith(PEELED_TAG_SUFFIX) and sha in self._map_git:
+- actual_ref = ref[: -len(PEELED_TAG_SUFFIX)]
++ if (
++ ref.endswith(compat.PEELED_TAG_SUFFIX)
++ and sha in self._map_git
++ ):
++ actual_ref = ref[: -len(compat.PEELED_TAG_SUFFIX)]
+ filteredrefs.setdefault(actual_ref, refs[actual_ref])
+
+ return [x for x in filteredrefs.values() if x not in self.git]
+@@ -1857,7 +1860,7 @@ class GitHandler(object):
+ repotags = self.repo.tags()
+ new_refs = {}
+ for k in refs:
+- if k.endswith(PEELED_TAG_SUFFIX) or not k.startswith(
++ if k.endswith(compat.PEELED_TAG_SUFFIX) or not k.startswith(
+ LOCAL_TAG_PREFIX
+ ):
+ continue
+@@ -2038,7 +2041,7 @@ class GitHandler(object):
+
+ for ref_name, sha in refs.items():
+ if (
+- ref_name.endswith(PEELED_TAG_SUFFIX)
++ ref_name.endswith(compat.PEELED_TAG_SUFFIX)
+ or sha not in self.git.object_store
+ ):
+ # the sha points to a peeled tag; we should either
diff --git a/debian/patches/compat-support-dulwich-1.2.x.patch b/debian/patches/compat-support-dulwich-1.2.x.patch
new file mode 100644
index 00000000..f1d64d85
--- /dev/null
+++ b/debian/patches/compat-support-dulwich-1.2.x.patch
@@ -0,0 +1,43 @@
+From 4b907287d35c1c828aeeac5ba0270cb5be9b8786 Mon Sep 17 00:00:00 2001
+From: Antonio Muci <[email protected]>
+Date: Wed, 20 May 2026 10:04:08 +0200
+Subject: [PATCH] compat: support dulwich 1.2.x
+
+Dulwich deprecated Commit.extra in 0.21.3, and removed it in 1.0.0. The relevant
+file in Dulwich is dulwich/objects.py.
+
+The private member _extra has been present since before our earliest supported
+version, and it is still present in Dulwich 1.2.x.
+
+To keep compatibility with all Dulwich versions (albeit in a fragile way), we
+will rely on _extra, since the construct is only used in test-extra.t and not in
+any other hg-git code.
+
+Apart from this commit, no other changes are needed to support Dulwich 1.2.x
+(and 1.0.0 before that).
+
+diff --git a/tests/test-extra.t b/tests/test-extra.t
+index 23d4637c..5bc7836c 100644
+--- a/tests/test-extra.t
++++ b/tests/test-extra.t
+@@ -158,9 +158,19 @@ lets you do that, though.
+ >>> c.parents = [b'$commit_sha']
+ >>> c.tree = b'$tree_sha'
+ >>> c.message = b'extra commit\n'
+- >>> c.extra.extend([(b'zzz:zzz', b'data:zzz'), (b'aaa:aaa', b'data:aaa'),
++ >>> c._extra.extend([(b'zzz:zzz', b'data:zzz'), (b'aaa:aaa', b'data:aaa'),
+ ... (b'HG:extra', b'hgaaa:dataaaa'),
+ ... (b'HG:extra', b'hgzzz:datazzz')])
++ ... # In the above line, please note that Dulwich deprecated Commit.extra in
++ ... # 0.21.3, and removed it in 1.0.0 (the relevant file there is
++ ... # dulwich/objects.py).
++ ... #
++ ... # The private member _extra has been present since before our earliest
++ ... # supported version, and it is still present in Dulwich 1.2.x.
++ ... #
++ ... # To keep compatibility with all Dulwich versions (albeit in a fragile
++ ... # way), we will rely on _extra, since the construct is only used in this
++ ... # test and not in any other hg-git code.
+ >>> repo.object_store.add_object(c)
+ >>> repo.refs.set_if_equals(b'refs/heads/master', None, c.id)
+ True
diff --git a/debian/patches/compat-use-PEELED_TAG_SUFFIX-instead-of-ANNOTATED_TAG_SUFFIX.patch b/debian/patches/compat-use-PEELED_TAG_SUFFIX-instead-of-ANNOTATED_TAG_SUFFIX.patch
new file mode 100644
index 00000000..0572a1bc
--- /dev/null
+++ b/debian/patches/compat-use-PEELED_TAG_SUFFIX-instead-of-ANNOTATED_TAG_SUFFIX.patch
@@ -0,0 +1,88 @@
+From d5cba79c7ba00ca024da4482da2eef988d6f49a9 Mon Sep 17 00:00:00 2001
+From: Antonio Muci <[email protected]>
+Date: Fri, 19 Dec 2025 23:14:44 +0100
+Subject: [PATCH] compat: use PEELED_TAG_SUFFIX instead of ANNOTATED_TAG_SUFFIX
+
+Starting from our oldest supported dulwich (0.22.1), and until 0.24.10,
+ANNOTATED_TAG_SUFFIX has been an alias for PEELED_TAG_SUFFIX (see sources later
+on).
+
+Dulwich 0.25.0 is going to remove support for the alias and move
+PEELED_TAG_SUFFIX in another module. Let's address the aliasing in this first
+commit. The next one will add compatibility with both old and recent dulwich
+(>= 0.25.0).
+
+No functional changes.
+
+Source:
+- https://github.com/jelmer/dulwich/blob/dulwich-0.22.1/dulwich/refs.py#L45
+- https://github.com/jelmer/dulwich/blob/dulwich-0.24.10/dulwich/refs.py#L62
+
+diff --git a/hggit/git2hg.py b/hggit/git2hg.py
+index 6f19c8af..7b2dcad0 100644
+--- a/hggit/git2hg.py
++++ b/hggit/git2hg.py
+@@ -6,9 +6,9 @@ import itertools
+ from dulwich import config as dul_config
+ from dulwich.objects import Commit, Tag
+ from dulwich.refs import (
+- ANNOTATED_TAG_SUFFIX,
+ LOCAL_BRANCH_PREFIX,
+ LOCAL_TAG_PREFIX,
++ PEELED_TAG_SUFFIX,
+ )
+ from mercurial.i18n import _
+
+@@ -306,7 +306,7 @@ def filter_refs(refs, heads):
+ raise error.RepoLookupError(msg)
+ else:
+ for ref, sha in refs.items():
+- if not ref.endswith(ANNOTATED_TAG_SUFFIX) and (
++ if not ref.endswith(PEELED_TAG_SUFFIX) and (
+ ref.startswith(LOCAL_BRANCH_PREFIX)
+ or ref.startswith(LOCAL_TAG_PREFIX)
+ or ref == b'HEAD'
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+index 73457cdb..59e19673 100644
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -11,9 +11,9 @@ from dulwich.errors import HangupException, GitProtocolError, ApplyDeltaError
+ from dulwich.objects import Blob, Commit, Tag, Tree, parse_timezone
+ from dulwich.pack import apply_delta
+ from dulwich.refs import (
+- ANNOTATED_TAG_SUFFIX,
+ LOCAL_BRANCH_PREFIX,
+ LOCAL_TAG_PREFIX,
++ PEELED_TAG_SUFFIX,
+ )
+ from dulwich.repo import Repo, check_ref_format
+ from dulwich import client
+@@ -1640,8 +1640,8 @@ class GitHandler(object):
+ # pull tags pointing to known revisions, including
+ # annotated tags
+ for ref, sha in refs.items():
+- if ref.endswith(ANNOTATED_TAG_SUFFIX) and sha in self._map_git:
+- actual_ref = ref[: -len(ANNOTATED_TAG_SUFFIX)]
++ if ref.endswith(PEELED_TAG_SUFFIX) and sha in self._map_git:
++ actual_ref = ref[: -len(PEELED_TAG_SUFFIX)]
+ filteredrefs.setdefault(actual_ref, refs[actual_ref])
+
+ return [x for x in filteredrefs.values() if x not in self.git]
+@@ -1857,7 +1857,7 @@ class GitHandler(object):
+ repotags = self.repo.tags()
+ new_refs = {}
+ for k in refs:
+- if k.endswith(ANNOTATED_TAG_SUFFIX) or not k.startswith(
++ if k.endswith(PEELED_TAG_SUFFIX) or not k.startswith(
+ LOCAL_TAG_PREFIX
+ ):
+ continue
+@@ -2038,7 +2038,7 @@ class GitHandler(object):
+
+ for ref_name, sha in refs.items():
+ if (
+- ref_name.endswith(ANNOTATED_TAG_SUFFIX)
++ ref_name.endswith(PEELED_TAG_SUFFIX)
+ or sha not in self.git.object_store
+ ):
+ # the sha points to a peeled tag; we should either
diff --git a/debian/patches/compat-wrap-correct-functions-after-hg-7.2-moved-them.patch b/debian/patches/compat-wrap-correct-functions-after-hg-7.2-moved-them.patch
new file mode 100644
index 00000000..105ec9e1
--- /dev/null
+++ b/debian/patches/compat-wrap-correct-functions-after-hg-7.2-moved-them.patch
@@ -0,0 +1,84 @@
+From cc438f2038bfa4d51e05752e56c07221b94c2fb2 Mon Sep 17 00:00:00 2001
+From: Manuel Jacob <[email protected]>
+Date: Thu, 20 Aug 2026 17:20:07 +0200
+Subject: [PATCH] compat: wrap correct functions after hg 7.2 moved them
+
+
+diff --git a/hggit/compat.py b/hggit/compat.py
+index a3ba9617..abc6ca2f 100644
+--- a/hggit/compat.py
++++ b/hggit/compat.py
+@@ -1,3 +1,4 @@
++import importlib
+ import inspect
+
+ import mercurial.commands # TODO: do not re-export mercurial.commands (use "as _commands"?)
+@@ -84,3 +85,19 @@ except ImportError:
+
+ get_repo_caps_function_name = 'getrepocaps'
+ assert bundle_caps_mod # silence pyflakes
++
++
++def wrap_first_function(eh, function_refs):
++ for function_ref in function_refs:
++ module_name, function_name = function_ref.split(':')
++ try:
++ module = importlib.import_module(module_name)
++ except ImportError:
++ continue
++ try:
++ getattr(module, function_name)
++ except AttributeError:
++ continue
++ return eh.wrapfunction(module, function_name)
++ else:
++ assert False
+diff --git a/hggit/schemes.py b/hggit/schemes.py
+index 5b40eab0..4953e4b2 100644
+--- a/hggit/schemes.py
++++ b/hggit/schemes.py
+@@ -103,7 +103,13 @@ def default_dest(orig, source):
+ return orig(source)
+
+
[email protected](hg, 'peer')
[email protected]_first_function(
++ eh,
++ [
++ 'mercurial.repo.factory:peer', # hg >= 7.2
++ 'mercurial.hg:peer',
++ ],
++)
+ def peer(orig, uiorrepo, *args, **opts):
+ newpeer = orig(uiorrepo, *args, **opts)
+ if isinstance(newpeer, gitrepo.gitrepo):
+@@ -112,7 +118,13 @@ def peer(orig, uiorrepo, *args, **opts):
+ return newpeer
+
+
[email protected](hg, 'clone')
[email protected]_first_function(
++ eh,
++ [
++ 'mercurial.cmd_impls.clone:clone', # hg >= 7.2
++ 'mercurial.hg:clone',
++ ],
++)
+ def clone(orig, *args, **opts):
+ srcpeer, destpeer = orig(*args, **opts)
+
+@@ -134,7 +146,13 @@ def isurllocal(orig, path):
+ return orig(path) and not util.isgitsshuri(path._origpath)
+
+
[email protected](hg, 'islocal')
[email protected]_first_function(
++ eh,
++ [
++ 'mercurial.repo.factory:is_local', # hg >= 7.2
++ 'mercurial.hg:islocal',
++ ],
++)
+ def islocal(orig, path):
+ # recognise git scp-style paths when cloning
+ return orig(path) and not util.isgitsshuri(path)
diff --git a/debian/patches/dulwich-checksum-mismatch b/debian/patches/dulwich-checksum-mismatch
new file mode 100644
index 00000000..cc6a50c3
--- /dev/null
+++ b/debian/patches/dulwich-checksum-mismatch
@@ -0,0 +1,40 @@
+Description: Handle ChecksumMismatch raised by dulwich >= 1.2.7 in gverify --fsck
+Forwarded: no
+Index: hg-git/hggit/commands.py
+===================================================================
+--- hg-git.orig/hggit/commands.py
++++ hg-git/hggit/commands.py
+@@ -10,6 +10,7 @@
+ import os
+
+ from dulwich import porcelain
++from dulwich.errors import ChecksumMismatch
+
+ from mercurial.node import hex, nullhex
+ from mercurial.i18n import _
+@@ -83,7 +84,10 @@ def gverify(ui, repo, **opts):
+ '''
+
+ if opts.get('fsck'):
+- for badsha, e in porcelain.fsck(repo.githandler.git):
++ try:
++ for badsha, e in porcelain.fsck(repo.githandler.git):
++ raise error.Abort(b'git repository is corrupt!')
++ except ChecksumMismatch:
+ raise error.Abort(b'git repository is corrupt!')
+
+ ctx = scmutil.revsingle(repo, opts.get('rev'), b'.')
+Index: hg-git/tests/test-verify-fail.t
+===================================================================
+--- hg-git.orig/tests/test-verify-fail.t
++++ hg-git/tests/test-verify-fail.t
+@@ -84,7 +84,8 @@ corrupt git repository
+ abort: git repository is corrupt!
+ [255]
+ $ hg gverify
+- abort: git equivalent 6dff77b710b6f0961ac0b6d91d85902195133d74 for rev 5c2eb98af3e2 is not a commit!
++ abort: git equivalent 6dff77b710b6f0961ac0b6d91d85902195133d74 for rev 5c2eb98af3e2 is corrupt!
++ (re-run with --traceback for details)
+ [255]
+ $ chmod +w .hg/git/objects/6d/ff77b710b6f0961ac0b6d91d85902195133d74
+ $ echo 42 > .hg/git/objects/6d/ff77b710b6f0961ac0b6d91d85902195133d74
diff --git a/debian/patches/dulwich-compat b/debian/patches/dulwich-compat
index 6979feff..75c7e6ea 100644
--- a/debian/patches/dulwich-compat
+++ b/debian/patches/dulwich-compat
@@ -1,37 +1,3 @@
-Index: hg-git/hggit/gc.py
-===================================================================
---- hg-git.orig/hggit/gc.py
-+++ hg-git/hggit/gc.py
-@@ -56,7 +56,7 @@ def _process_batch(ui, object_store, sha
- object_store.add_objects(list(objects))
-
- for obj, path in objects:
-- object_store._remove_loose_object(obj.id)
-+ object_store.delete_loose_object(obj.id)
-
- ui.debug(b'packed %d loose objects!\n' % len(shas))
-
-Index: hg-git/hggit/git_handler.py
-===================================================================
---- hg-git.orig/hggit/git_handler.py
-+++ hg-git/hggit/git_handler.py
-@@ -2127,8 +2127,14 @@ class GitHandler(object):
- )
-
- for change in changes:
-- oldfile, oldmode, oldsha = change.old
-- newfile, newmode, newsha = change.new
-+ if change.old is not None:
-+ oldfile, oldmode, oldsha = change.old
-+ else:
-+ oldfile, oldmode, oldsha = None, None, None
-+ if change.new is not None:
-+ newfile, newmode, newsha = change.new
-+ else:
-+ newfile, newmode, newsha = None, None, None
- # actions are described by the following table ('no' means 'does
- # not exist'):
- # old new | action
Index: hg-git/setup.cfg
===================================================================
--- hg-git.orig/setup.cfg
@@ -41,7 +7,7 @@ Index: hg-git/setup.cfg
hggit.helptext
install_requires=
- dulwich>=0.21.6,<0.23.0
-+ dulwich>=0.24.0,<0.25
++ dulwich>=0.24.0,<2.0.0
[options.package_data]
* = *.txt, *.rst
diff --git a/debian/patches/git_handler-fix-a-compatibility-issue-with-dulwich-0.24.0.patch b/debian/patches/git_handler-fix-a-compatibility-issue-with-dulwich-0.24.0.patch
new file mode 100644
index 00000000..ab1ec8b2
--- /dev/null
+++ b/debian/patches/git_handler-fix-a-compatibility-issue-with-dulwich-0.24.0.patch
@@ -0,0 +1,35 @@
+From e295ae14f049a1cf560147b1de0538d69669a24a Mon Sep 17 00:00:00 2001
+From: reconzo-abhijith <[email protected]>
+Date: Mon, 24 Nov 2025 14:44:46 +0530
+Subject: [PATCH] git_handler: fix a compatibility issue with dulwich >= 0.24.0
+
+This fixes errors when a pull operation involves new or deleted files.
+
+The error was:
+ TypeError: cannot unpack non-iterable NoneType object
+in git_handler.py, get_files_changed().
+
+Original proposal by @kebianizao and @muxator at
+https://foss.heptapod.net/mercurial/hg-git/-/issues/451
+
+Fixes #451.
+
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+index 30f0fae3..2d6f6624 100644
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -2131,8 +2131,12 @@ class GitHandler(object):
+ )
+
+ for change in changes:
+- oldfile, oldmode, oldsha = change.old
+- newfile, newmode, newsha = change.new
++ oldfile, oldmode, oldsha = (None, None, None)
++ newfile, newmode, newsha = (None, None, None)
++ if change.old:
++ oldfile, oldmode, oldsha = change.old
++ if change.new:
++ newfile, newmode, newsha = change.new
+ # actions are described by the following table ('no' means 'does
+ # not exist'):
+ # old new | action
diff --git a/debian/patches/git_handler-fix-a-dulwich-0.24.3-incompatibility-in-test-pus.patch b/debian/patches/git_handler-fix-a-dulwich-0.24.3-incompatibility-in-test-pus.patch
new file mode 100644
index 00000000..3fe125dc
--- /dev/null
+++ b/debian/patches/git_handler-fix-a-dulwich-0.24.3-incompatibility-in-test-pus.patch
@@ -0,0 +1,36 @@
+From 44103425cd23f330623fd2669e23a489777819b1 Mon Sep 17 00:00:00 2001
+From: muxator <[email protected]>
+Date: Wed, 26 Nov 2025 17:53:04 +0100
+Subject: [PATCH] git_handler: fix a dulwich 0.24.3 incompatibility in
+ test-push.t
+
+In Dulwich 0.24.3, the determine_wants() function started accepting an
+additional "depth" parameter.
+
+Without this change, test-push.t would break with Dulwich >=0.24.3.
+
+This change should be compatible with both the old and the new Dulwich version.
+
+Compare the following:
+- 0.24.2 (without "depth"): https://github.com/jelmer/dulwich/blob/dulwich-0.24.2/dulwich/repo.py#L590
+- 0.24.3 (with "depth"): https://github.com/jelmer/dulwich/blob/dulwich-0.24.3/dulwich/repo.py#L601
+
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+index 2d6f6624..6aee91bc 100644
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -1622,7 +1622,13 @@ class GitHandler(object):
+ haveheads.extend(self.git.refs.as_dict(LOCAL_BRANCH_PREFIX).values())
+ graphwalker = self.git.get_graph_walker(heads=haveheads)
+
+- def determine_wants(refs):
++ # Starting with dulwich 0.24.3, determine_wants() needs to accept an
++ # additional parameter "depth".
++ #
++ # Compare the following:
++ # - 0.24.2 (without "depth"): https://github.com/jelmer/dulwich/blob/dulwich-0.24.2/dulwich/repo.py#L590
++ # - 0.24.3 (with "depth") : https://github.com/jelmer/dulwich/blob/dulwich-0.24.3/dulwich/repo.py#L601
++ def determine_wants(refs, depth: int | None = None):
+ if refs is None:
+ return None
+ filteredrefs = git2hg.filter_refs(refs, heads)
diff --git a/debian/patches/git_handler-fix-a-dulwich-0.24.3-incompatibility.patch b/debian/patches/git_handler-fix-a-dulwich-0.24.3-incompatibility.patch
new file mode 100644
index 00000000..5b39e586
--- /dev/null
+++ b/debian/patches/git_handler-fix-a-dulwich-0.24.3-incompatibility.patch
@@ -0,0 +1,39 @@
+From da4efae81e1e5814a23c74a0e283d103f172cc4b Mon Sep 17 00:00:00 2001
+From: reconzo-abhijith <[email protected]>
+Date: Mon, 24 Nov 2025 14:44:46 +0530
+Subject: [PATCH] git_handler: fix a dulwich 0.24.3 incompatibility
+
+In Dulwich 0.24.3, get_credentials_from_store() started returning an Iterator of
+tuples instead of a single tuple. This change covers all the cases.
+
+see: https://foss.heptapod.net/mercurial/hg-git/-/merge_requests/260#note_493621
+
+Definition of get_credentials_from_store() in dulwich 0.24.2 (without Iterator):
+ https://github.com/jelmer/dulwich/blob/dulwich-0.24.2/dulwich/client.py#L3685
+Definition of get_credentials_from_store() in dulwich 0.24.3 (changed to Iterator):
+ https://github.com/jelmer/dulwich/blob/dulwich-0.24.3/dulwich/client.py#L4275
+
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+index 6aee91bc..11ad2203 100644
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -3,6 +3,7 @@ import itertools
+ import os
+ import re
+ import shutil
++import types
+
+ from dulwich.client import HTTPUnauthorized
+ from dulwich.errors import HangupException, GitProtocolError, ApplyDeltaError
+@@ -2396,6 +2397,11 @@ class GitHandler(object):
+ urlobj.user,
+ )
+
++ # fix for dulwich >= 0.24.3 compatibility
++ # see: https://foss.heptapod.net/mercurial/hg-git/-/merge_requests/260#note_493621
++ if isinstance(auth, types.GeneratorType):
++ auth = next(auth, None)
++
+ if self._http_auth_realm:
+ # since we've tried an unauthenticated request, and
+ # obtain a realm, we can do a "full" search, including
diff --git a/debian/patches/gitrepo-add_branch_revs-was-moved-in-a-different-module-in-h.patch b/debian/patches/gitrepo-add_branch_revs-was-moved-in-a-different-module-in-h.patch
new file mode 100644
index 00000000..d090e223
--- /dev/null
+++ b/debian/patches/gitrepo-add_branch_revs-was-moved-in-a-different-module-in-h.patch
@@ -0,0 +1,55 @@
+From 6b74e944291e2a09518fe5cba6b966945c073d5a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jordi=20Guti=C3=A9rrez=20Hermoso?= <[email protected]>
+Date: Wed, 13 May 2026 01:12:24 +0200
+Subject: [PATCH] gitrepo: add_branch_revs() was moved in a different module in
+ hg >= 7.2
+
+The change happened in mercurial commit:
+33a40f393800 ("cycle-breaking: move `add_branch_revs` from `hg` to `urlutil`")
+
+Co-authored-by: Antonio Muci <[email protected]>
+
+diff --git a/hggit/compat.py b/hggit/compat.py
+index cedfc5ed..8addccc1 100644
+--- a/hggit/compat.py
++++ b/hggit/compat.py
+@@ -36,3 +36,16 @@ except ImportError:
+ from mercurial.interfaces.repository import peer as Peer
+
+ assert Peer # silence pyflakes
++
++import mercurial.utils.urlutil as _urlutil # do not re-export urlutil
++
++if hasattr(_urlutil, 'add_branch_revs'):
++ # hg >= 7.2
++ from mercurial.utils import urlutil as add_branch_revs_mod
++
++ add_branch_revs_function_name = 'add_branch_revs'
++else:
++ from mercurial import hg as add_branch_revs_mod
++
++ add_branch_revs_function_name = 'addbranchrevs'
++ assert add_branch_revs_mod # silence pyflakes
+diff --git a/hggit/gitrepo.py b/hggit/gitrepo.py
+index 990eb300..c988b117 100644
+--- a/hggit/gitrepo.py
++++ b/hggit/gitrepo.py
+@@ -5,7 +5,6 @@ from mercurial import (
+ error,
+ exchange,
+ exthelper,
+- hg,
+ pycompat,
+ wireprotov1peer,
+ )
+@@ -150,7 +149,9 @@ def islocal(path):
+
+
+ # defend against tracebacks if we specify -r in 'hg pull'
[email protected](hg, 'addbranchrevs')
[email protected](
++ compat.add_branch_revs_mod, compat.add_branch_revs_function_name
++)
+ def safebranchrevs(orig, lrepo, otherrepo, branches, revs, **kwargs):
+ revs, co = orig(lrepo, otherrepo, branches, revs, **kwargs)
+ if isinstance(otherrepo, gitrepo):
diff --git a/debian/patches/gitrepo-default_dest-was-moved-in-a-different-module-in-hg-7.patch b/debian/patches/gitrepo-default_dest-was-moved-in-a-different-module-in-hg-7.patch
new file mode 100644
index 00000000..9647efc7
--- /dev/null
+++ b/debian/patches/gitrepo-default_dest-was-moved-in-a-different-module-in-hg-7.patch
@@ -0,0 +1,50 @@
+From 2e29e9b2a45914905caaa6ddbc03455f62c742b4 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jordi=20Guti=C3=A9rrez=20Hermoso?= <[email protected]>
+Date: Wed, 13 May 2026 01:21:11 +0200
+Subject: [PATCH] gitrepo: default_dest() was moved in a different module in hg
+ >= 7.2
+
+The change happened in mercurial commit:
+a3b891ff921e ("cycle-breaking: move `default_dest` from `hg` to a `clone` module")
+
+Co-authored-by: Antonio Muci <[email protected]>
+
+diff --git a/hggit/compat.py b/hggit/compat.py
+index 8addccc1..aa4e6da5 100644
+--- a/hggit/compat.py
++++ b/hggit/compat.py
+@@ -49,3 +49,11 @@ else:
+
+ add_branch_revs_function_name = 'addbranchrevs'
+ assert add_branch_revs_mod # silence pyflakes
++
++try:
++ # hg >= 7.2
++ from mercurial.cmd_impls.clone import default_dest
++except ImportError:
++ from mercurial.hg import defaultdest as default_dest
++
++ assert default_dest # silence pyflakes
+diff --git a/hggit/schemes.py b/hggit/schemes.py
+index bd5f7e29..5b40eab0 100644
+--- a/hggit/schemes.py
++++ b/hggit/schemes.py
+@@ -18,6 +18,7 @@ from mercurial import (
+ from mercurial.utils import urlutil
+
+ # local modules
++from . import compat
+ from . import gitrepo
+ from . import util
+
+@@ -94,8 +95,8 @@ class RepoFactory:
+ return fn(ui, path, *args, **kwargs)
+
+
[email protected](hg, 'defaultdest')
+-def defaultdest(orig, source):
[email protected](compat, 'default_dest')
++def default_dest(orig, source):
+ if source.endswith(b'.git'):
+ return orig(source[:-4])
+
diff --git a/debian/patches/gitrepo-selectively-define-instance-function.patch b/debian/patches/gitrepo-selectively-define-instance-function.patch
new file mode 100644
index 00000000..d78ca710
--- /dev/null
+++ b/debian/patches/gitrepo-selectively-define-instance-function.patch
@@ -0,0 +1,29 @@
+From ea35d410148553d9ff633a4b0ba49a7e7c6465b5 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jordi=20Guti=C3=A9rrez=20Hermoso?= <[email protected]>
+Date: Sat, 28 Mar 2026 16:44:43 -0600
+Subject: [PATCH] gitrepo: selectively define `instance` function
+
+Because of how Mercurial now instantiates repos, we can no longer be
+unconditionally a gitrepo. Instead, we return a localrepo for local
+creation.
+
+This is so that `hg clone some_git_repo` works.
+
+diff --git a/hggit/gitrepo.py b/hggit/gitrepo.py
+index c988b117..5b8ec595 100644
+--- a/hggit/gitrepo.py
++++ b/hggit/gitrepo.py
+@@ -137,7 +137,12 @@ class gitrepo(compat.Peer):
+ pass
+
+
+-instance = gitrepo
++def instance(ui, path, create=False, *args, **kwargs):
++ if create and islocal(path):
++ from mercurial import localrepo
++
++ return localrepo.instance(ui, path, create, *args, **kwargs)
++ return gitrepo(ui, path, create, *args, **kwargs)
+
+
+ def islocal(path):
diff --git a/debian/patches/hggit-fix-an-incompatibility-with-dulwich-0.24.0.patch b/debian/patches/hggit-fix-an-incompatibility-with-dulwich-0.24.0.patch
new file mode 100644
index 00000000..fac21e2e
--- /dev/null
+++ b/debian/patches/hggit-fix-an-incompatibility-with-dulwich-0.24.0.patch
@@ -0,0 +1,28 @@
+From 2971dfab4ed43653cb870818f126985266d4f2bb Mon Sep 17 00:00:00 2001
+From: Dan Villiom Podlaski Christiansen <[email protected]>
+Date: Tue, 25 Nov 2025 19:51:14 +0100
+Subject: [PATCH] hggit: fix an incompatibility with dulwich >= 0.24.0
+
+On dulwich >= 0.24, ssh commands arrive as undecoded bytes, and not strings.
+This would trigger an AssertionError when doing network operations.
+
+Modified form https://foss.heptapod.net/mercurial/hg-git/-/merge_requests/260/
+by Abhijith Nayak (@reconzo-abhijith).
+
+diff --git a/hggit/_ssh.py b/hggit/_ssh.py
+index d3bdc947..1ae55b2a 100644
+--- a/hggit/_ssh.py
++++ b/hggit/_ssh.py
+@@ -20,8 +20,10 @@ def generate_ssh_vendor(ui):
+ def run_command(
+ self, host, command, username=None, port=None, **kwargs
+ ):
+- assert isinstance(command, str)
+- command = command.encode(SSHGitClient.DEFAULT_ENCODING)
++ # on dulwich >= 0.24, command is bytes already
++ # on dulwich <= 0.23, command is a string
++ if isinstance(command, str):
++ command = command.encode(SSHGitClient.DEFAULT_ENCODING)
+ sshcmd = ui.config(b"ui", b"ssh", b"ssh")
+ args = procutil.sshargs(
+ sshcmd, pycompat.bytesurl(host), username, port
diff --git a/debian/patches/series b/debian/patches/series
index b1b6fc68..f0529dde 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +1,19 @@
+compat-add-support-for-dulwich-0.23.2.patch
+hggit-fix-an-incompatibility-with-dulwich-0.24.0.patch
+git_handler-fix-a-compatibility-issue-with-dulwich-0.24.0.patch
+git_handler-fix-a-dulwich-0.24.3-incompatibility-in-test-pus.patch
+git_handler-fix-a-dulwich-0.24.3-incompatibility.patch
+compat-use-PEELED_TAG_SUFFIX-instead-of-ANNOTATED_TAG_SUFFIX.patch
+compat-support-dulwich-0.25.patch
+compat-support-dulwich-1.2.x.patch
+compat-hg-7.2-moved-parthandler-from-bundle2-to-bundle2_part.patch
+compat-hg-7.2-removed-repository.peer-and-introduced-exchang.patch
+gitrepo-add_branch_revs-was-moved-in-a-different-module-in-h.patch
+gitrepo-default_dest-was-moved-in-a-different-module-in-hg-7.patch
+gitrepo-selectively-define-instance-function.patch
+compat-fix-test-url-parsing.py.patch
+bundle-fix-test-bundle.t.-be-backward-compatible-in-a-ugly-w.patch
+bundle-fixe-test-hg-clone.t-and-test-serve-hg.t-with-hggit-o.patch
+compat-wrap-correct-functions-after-hg-7.2-moved-them.patch
dulwich-compat
+dulwich-checksum-mismatch