comparison src/hgext3rd/hggit_serve/_ssh.py @ 13:00bdfac5416c

Create Git SSH commands and add some documentation. Also cleanup. - Adds git-upload-pack and git-receive-pack as hg subcommands, to be run on the server side by git push/pull. - Starts on documentation. - Cleans up a lot of stuff.
author Paul Fisher <paul@pfish.zone>
date Thu, 19 Feb 2026 01:13:56 -0500
parents
children b65d5922b8ee
comparison
equal deleted inserted replaced
12:f630d9904ea7 13:00bdfac5416c
1 from __future__ import annotations
2
3 import subprocess
4 import typing as t
5
6 from mercurial import error as hgerr
7 from mercurial import registrar
8
9 from . import _export as xp
10
11 if t.TYPE_CHECKING:
12 import mercurial.interfaces.repository as hgrepo
13 import mercurial.ui as hgui
14
15 cmdtable: dict[bytes, object] = {}
16 _command = registrar.command(cmdtable)
17
18
19 def _not_git() -> hgerr.StateError:
20 return hgerr.StateError(
21 b'Git is not enabled for this repository.',
22 hint=b'The server administrator should enable the ``hggit`` extension '
23 b'and run ``hg git-export`` to enable Git access.',
24 )
25
26
27 def _maybe(flag: bytes, include: bool) -> tuple[bytes, ...]:
28 return (flag,) if include else ()
29
30
31 @_command(
32 b'git-upload-pack',
33 [
34 *(
35 (b'', opt, False, b'flag passed to git upload-pack')
36 for opt in (
37 b'strict',
38 b'no-strict',
39 b'stateless-rpc',
40 b'advertise-refs',
41 )
42 ),
43 (b'', b'timeout', -1, b'flag passed to git upload-pack'),
44 ],
45 helpcategory=b'import',
46 intents=(b'readonly',),
47 )
48 def _git_upload_pack(
49 ui: hgui.ui,
50 repo: hgrepo.IRepo,
51 *,
52 strict: bool,
53 no_strict: bool,
54 stateless_rpc: bool,
55 advertise_refs: bool,
56 timeout: int,
57 ) -> int:
58 """Server-side handler for ``git pull``/``git clone``."""
59 if not xp.is_gitty(repo):
60 raise _not_git()
61 timeout_flag = (
62 (b'--timeout=' + str(timeout).encode(),) if timeout != -1 else ()
63 )
64 upload_pack = ui.configlist(
65 b'hggit-serve', b'upload-pack', default=(b'git', b'upload-pack')
66 )
67 return subprocess.call(
68 (
69 *upload_pack,
70 *_maybe(b'--strict', strict),
71 *_maybe(b'--no-strict', no_strict),
72 *_maybe(b'--stateless-rpc', stateless_rpc),
73 *_maybe(b'--advertise_refs', advertise_refs),
74 *timeout_flag,
75 repo.githandler.gitdir,
76 ),
77 close_fds=True,
78 )
79
80
81 @_command(
82 b'git-receive-pack',
83 [
84 (b'', b'skip-connectivity-check', False, b'passed to git receive-pack'),
85 ],
86 helpcategory=b'import',
87 )
88 def _git_receive_pack(
89 ui: hgui.ui, repo: hgrepo.IRepo, *, skip_connectivity_check: bool
90 ) -> int:
91 """Server-side handler for ``git push``."""
92 if not xp.is_gitty(repo):
93 raise _not_git()
94 receive_pack = ui.configlist(
95 b'hggit-serve', b'receive-pack', default=(b'git', b'receive-pack')
96 )
97 try:
98 subprocess.check_call(
99 (
100 *receive_pack,
101 *_maybe(b'--skip-connectivity-check', skip_connectivity_check),
102 repo.githandler.gitdir,
103 ),
104 close_fds=True,
105 )
106 except subprocess.CalledProcessError as cpe:
107 return cpe.returncode
108 xp.import_all(repo, b'git-receive-pack')
109 return 0
110
111
112 __all__ = ('cmdtable',)