comparison src/hgext3rd/hggit_serve/_export.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 f630d9904ea7
children 959ef686193f
comparison
equal deleted inserted replaced
12:f630d9904ea7 13:00bdfac5416c
46 when we're done. It's not just a bool in case somebody sets up some crazy 46 when we're done. It's not just a bool in case somebody sets up some crazy
47 recursive hook situation where we start importing inside another import. 47 recursive hook situation where we start importing inside another import.
48 """ 48 """
49 49
50 50
51 def importing_enter(repo: hgrepo.IRepo) -> None: 51 def _importing_enter(repo: hgrepo.IRepo) -> None:
52 """Call this before you start importing from Git.""" 52 """Call this before you start importing from Git."""
53 level = getattr(repo, _ILEVEL_ATTR, 0) + 1 53 level = getattr(repo, _ILEVEL_ATTR, 0) + 1
54 setattr(repo, _ILEVEL_ATTR, level) 54 setattr(repo, _ILEVEL_ATTR, level)
55 55
56 56
57 def is_importing(repo: hgrepo.IRepo) -> bool: 57 def _is_importing(repo: hgrepo.IRepo) -> bool:
58 """Call this to check if you're currently importing.""" 58 """Call this to check if you're currently importing."""
59 return hasattr(repo, _ILEVEL_ATTR) 59 return hasattr(repo, _ILEVEL_ATTR)
60 60
61 61
62 def importing_exit(repo: hgrepo.IRepo) -> None: 62 def _importing_exit(repo: hgrepo.IRepo) -> None:
63 """Call this after you finish importing from Git.""" 63 """Call this after you finish importing from Git."""
64 level = getattr(repo, _ILEVEL_ATTR) - 1 64 level = getattr(repo, _ILEVEL_ATTR) - 1
65 if level: 65 if level:
66 setattr(repo, _ILEVEL_ATTR, level) 66 setattr(repo, _ILEVEL_ATTR, level)
67 else: 67 else:
68 delattr(repo, _ILEVEL_ATTR) 68 delattr(repo, _ILEVEL_ATTR)
69 69
70 70
71 def import_all(repo: GittyRepo, command: bytes = b'(unknown)') -> None:
72 _importing_enter(repo)
73 try:
74 gh = repo.githandler
75 gh.import_git_objects(
76 command, remote_names=(), refs=gh.git.refs.as_dict()
77 )
78 finally:
79 _importing_exit(repo)
80
81
71 # 82 #
72 # Export handling. 83 # Export handling.
73 # 84 #
74 85
75 86
76 def clean_all_refs(refs: dulwich.refs.RefsContainer) -> None: 87 def _clean_all_refs(refs: dulwich.refs.RefsContainer) -> None:
77 """Removes all refs from the Git repository.""" 88 """Removes all refs from the Git repository."""
78 89 # dump to allkeys so we explicitly are iterating over a snapshot
79 90 # and not over something while we mutate
80 def set_head(ui: hgui.ui, repo: GittyRepo, at_name: bytes) -> None: 91 for ref in refs.allkeys():
92 refs.remove_if_equals(ref, None)
93
94
95 def _set_head(ui: hgui.ui, repo: GittyRepo, at_name: bytes) -> None:
81 """Creates a HEAD reference in Git referring to the current HEAD.""" 96 """Creates a HEAD reference in Git referring to the current HEAD."""
82 # By default, we use '@', since that's what will be auto checked out. 97 # By default, we use '@', since that's what will be auto checked out.
83 current = b'@' 98 current = b'@'
84 if current not in repo._bookmarks: 99 if current not in repo._bookmarks:
85 current = repo._bookmarks.active or current 100 current = repo._bookmarks.active or current
115 refs = repo.githandler.git.refs 130 refs = repo.githandler.git.refs
116 refs.add_packed_refs({git_branch: gitsha}) 131 refs.add_packed_refs({git_branch: gitsha})
117 refs.set_symbolic_ref(b'HEAD', git_branch) 132 refs.set_symbolic_ref(b'HEAD', git_branch)
118 133
119 134
120 def fix_refs(ui: hgui.ui, repo: GittyRepo) -> None: 135 def _fix_refs(ui: hgui.ui, repo: GittyRepo) -> None:
121 """After a git export, fix up the refs. 136 """After a git export, fix up the refs.
122 137
123 This ensures that there are no leftover refs from older, removed bookmarks 138 This ensures that there are no leftover refs from older, removed bookmarks
124 and that there is a proper HEAD set so that cloning works. 139 and that there is a proper HEAD set so that cloning works.
125 """ 140 """
126 refs = repo.githandler.git.refs 141 _clean_all_refs(repo.githandler.git.refs)
127 # dump to allkeys so we explicitly are iterating over a snapshot
128 # and not over something while we mutate
129 for ref in refs.allkeys():
130 refs.remove_if_equals(ref, None)
131 repo.githandler.export_hg_tags() 142 repo.githandler.export_hg_tags()
132 repo.githandler.update_references() 143 repo.githandler.update_references()
133 default_branch_name = ui.config( 144 default_branch_name = ui.config(
134 b'hggit-serve', b'default-branch', b'default' 145 b'hggit-serve', b'default-branch', b'default'
135 ) 146 )
136 set_head(ui, repo, default_branch_name) 147 _set_head(ui, repo, default_branch_name)
137 148
138 149
139 # 150 #
140 # Hooks 151 # Hooks
141 # 152 #
147 return 158 return
148 auto_export = ui.config(b'hggit-serve', b'auto-export') 159 auto_export = ui.config(b'hggit-serve', b'auto-export')
149 if auto_export == b'never': 160 if auto_export == b'never':
150 return 161 return
151 if auto_export == b'always' or git_handler.has_gitrepo(repo): 162 if auto_export == b'always' or git_handler.has_gitrepo(repo):
152 if is_importing(repo): 163 if _is_importing(repo):
153 ui.note(b'currently importing revs from git; not exporting\n') 164 ui.note(b'currently importing revs from git; not exporting\n')
154 return 165 return
155 repo.githandler.export_commits() 166 repo.githandler.export_commits()
156 fix_refs(ui, repo) 167 _fix_refs(ui, repo)
157 168
158 169
159 def _fix_refs_hook(ui: hgui.ui, repo: hgrepo.IRepo, **__: object) -> None: 170 def _fix_refs_hook(ui: hgui.ui, repo: hgrepo.IRepo, **__: object) -> None:
160 """Exports to Git and sets up for serving. See ``_fix_refs``.""" 171 """Exports to Git and sets up for serving. See ``_fix_refs``."""
161 if not is_gitty(repo): 172 if not is_gitty(repo):
162 return 173 return
163 fix_refs(ui, repo) 174 _fix_refs(ui, repo)
164 175
165 176
166 # 177 #
167 # Interfacing with Mercurial 178 # Interfacing with Mercurial
168 # 179 #
169 180
170 181
171 def uipopulate(ui: hgui.ui) -> None: 182 def uipopulate(ui: hgui.ui) -> None:
172 # Fix up our tags after a Git export. 183 # Fix up our tags after a Git export.
173 ui.setconfig( 184 ui.setconfig(
174 b'hooks', b'post-git-export.__gitserve_add_tag__', _fix_refs_hook 185 b'hooks',
186 b'post-git-export.__gitserve_add_tag__',
187 _fix_refs_hook,
188 source=b'hggit_serve',
175 ) 189 )
176 # Whenever we get new revisions, export them to the Git repository. 190 # Whenever we get new revisions, export them to the Git repository.
177 ui.setconfig(b'hooks', b'txnclose.__gitserve_export__', _export_hook) 191 ui.setconfig(
192 b'hooks',
193 b'txnclose.__gitserve_export__',
194 _export_hook,
195 source=b'hggit_serve',
196 )
178 # Don't step on ourselves when importing data from Git. 197 # Don't step on ourselves when importing data from Git.
179 ui.setconfig( 198 ui.setconfig(
180 b'hooks', 199 b'hooks',
181 b'pre-git-import.__gitserve_suppress_export__', 200 b'pre-git-import.__gitserve_suppress_export__',
182 lambda _, repo, **__: importing_enter(repo), 201 lambda _, repo, **__: _importing_enter(repo),
202 source=b'hggit_serve',
183 ) 203 )
184 ui.setconfig( 204 ui.setconfig(
185 b'hooks', 205 b'hooks',
186 b'post-git-import.__gitserve_suppress_export__', 206 b'post-git-import.__gitserve_suppress_export__',
187 lambda _, repo, **__: importing_exit(repo), 207 lambda _, repo, **__: _importing_exit(repo),
188 ) 208 source=b'hggit-serve',
209 )