comparison src/git_serve/__init__.py @ 6:7113e0ac3662

fix refs on git-export; clean up how gitserve export works.
author Paul Fisher <paul@pfish.zone>
date Sun, 15 Feb 2026 01:31:53 -0500
parents c43ce246240b
children
comparison
equal deleted inserted replaced
5:c43ce246240b 6:7113e0ac3662
139 """Removes all refs from the Git repository.""" 139 """Removes all refs from the Git repository."""
140 for ref in refs.allkeys(): 140 for ref in refs.allkeys():
141 refs.remove_if_equals(ref, None) 141 refs.remove_if_equals(ref, None)
142 142
143 143
144 def _set_head(repo: GittyRepo) -> None: 144 def _set_head(ui: hgui.ui, repo: GittyRepo, at_name: bytes) -> None:
145 """Creates a HEAD reference in Git referring to the current HEAD.""" 145 """Creates a HEAD reference in Git referring to the current HEAD."""
146 # By default, we use '@', since that's what will be auto checked out. 146 # By default, we use '@', since that's what will be auto checked out.
147 current = b'@' 147 current = b'@'
148 if current not in repo._bookmarks: 148 if current not in repo._bookmarks:
149 current = repo._bookmarks.active or current 149 current = repo._bookmarks.active or current
150 150
151 # We'll be moving this (possibly fake) bookmark into Git. 151 # We'll be moving this (possibly fake) bookmark into Git.
152 git_current = current 152 git_current = current
153 if current == b'@': 153 if current == b'@':
154 # @ is a special keyword in Git, so we can't use it as a bookmark. 154 # @ is a special keyword in Git, so we can't use it as a bookmark.
155 git_current = b'__default__' 155 git_current = at_name
156 git_branch = dulwich.refs.LOCAL_BRANCH_PREFIX + git_current 156 git_branch = dulwich.refs.LOCAL_BRANCH_PREFIX + git_current
157 if not dulwich.refs.check_ref_format(git_branch): 157 if not dulwich.refs.check_ref_format(git_branch):
158 # We can't export this ref to Git. Give up. 158 # We can't export this ref to Git. Give up.
159 ui.warn(f'{git_branch!r} is not a valid branch name for Git.'.encode())
160 return
161 try:
162 # Maybe this is a real bookmark?
163 hgsha = repo._bookmarks[current]
164 except KeyError:
165 # Not a real bookmark. Assume we want the tip of the current branch.
166 branch = repo.dirstate.branch()
167 try:
168 tip = repo.branchtip(branch)
169 except hgerr.RepoLookupError:
170 # This branch somehow doesn't exist???
171 ui.warn(f"{branch} doesn't seem to exist?".encode())
172 return
173 hgsha = binascii.hexlify(tip)
174 gitsha = repo.githandler.map_git_get(hgsha)
175 if not gitsha:
176 # No Git SHA to match this Hg sha. Give up.
177 ui.warn(f'revision {hgsha} was not exported to Git'.encode())
159 return 178 return
160 refs = repo.githandler.git.refs 179 refs = repo.githandler.git.refs
161 if git_branch not in refs: 180 refs.add_packed_refs({git_branch: gitsha})
162 # This means our bookmark isn't actually in Git (usually because
163 # there's no real bookmark called '@'). We need to fake it.
164 try:
165 # Maybe this is a real bookmark?
166 hgsha = repo._bookmarks[current]
167 except KeyError:
168 # Not a real bookmark. Assume we want the tip of the current branch.
169 branch = repo.dirstate.branch()
170 try:
171 tip = repo.branchtip(branch)
172 except hgerr.RepoLookupError:
173 # This branch somehow doesn't exist???
174 return
175 hgsha = binascii.hexlify(tip)
176 gitsha = repo.githandler.map_git_get(hgsha)
177 if not gitsha:
178 # No Git SHA to match this Hg sha. Give up.
179 return
180 refs.add_packed_refs({git_branch: gitsha})
181 refs.set_symbolic_ref(b'HEAD', git_branch) 181 refs.set_symbolic_ref(b'HEAD', git_branch)
182 182
183 183
184 def _export_hook(ui: hgui.ui, repo: GittyRepo, **__: object) -> None: 184 def fix_refs_hook(ui: hgui.ui, repo: hgrepo.IRepo, **__: object) -> None:
185 """Exports to Git and sets up for serving.""" 185 """Exports to Git and sets up for serving."""
186 never_export = ui.configbool(b'git-serve', b'never-export') 186 if not _is_gitty(repo):
187 if never_export: 187 return
188 return 188 _fix_refs(ui, repo)
189 always_export = ui.configbool(b'git-serve', b'always-export', False) 189
190 if always_export or os.path.isdir(repo.githandler.gitdir): 190
191 _export_repo(repo) 191 def _fix_refs(ui: hgui.ui, repo: GittyRepo) -> None:
192 192 """After a git export, fix up the refs."""
193
194 def _export_repo(repo: GittyRepo) -> None:
195 """Do the actual exporting."""
196 _clean_all_refs(repo.githandler.git.refs) 193 _clean_all_refs(repo.githandler.git.refs)
197 repo.githandler.export_commits() 194 repo.githandler.export_hg_tags()
198 _set_head(repo) 195 repo.githandler.update_references()
196 default_branch_name = ui.config(
197 b'hggit-serve', b'default-branch', b'default'
198 )
199 _set_head(ui, repo, default_branch_name)
200
201
202 def export_hook(ui: hgui.ui, repo: hgrepo.IRepo, **__: object) -> None:
203 if not _is_gitty(repo):
204 return
205 auto_export = ui.config(b'hggit-serve', b'auto-export')
206 if auto_export == b'never':
207 return
208 if auto_export == b'always' or os.path.isdir(repo.githandler.gitdir):
209 repo.githandler.export_commits()
210 _fix_refs(ui, repo)
199 211
200 212
201 # Interfacing with Mercurial 213 # Interfacing with Mercurial
202 214
203 __version__ = '0.1.3' 215 __version__ = '0.1.4'
216 testedwith = b'7.1 7.2'
204 217
205 cmdtable: dict[bytes, object] = {} 218 cmdtable: dict[bytes, object] = {}
206 219
207 command = registrar.command(cmdtable) 220 command = registrar.command(cmdtable)
208
209
210 @command(b'git-serve-export')
211 def git_serve_export(_: hgui.ui, repo: hgrepo.IRepo, **__: object) -> None:
212 if not _is_gitty(repo):
213 raise hgerr.Abort(b'this extension depends on the `hggit` extension')
214 _export_repo(repo)
215 221
216 222
217 def uisetup(_: hgui.ui) -> None: 223 def uisetup(_: hgui.ui) -> None:
218 extensions.wrapfunction( 224 extensions.wrapfunction(
219 wireprotoserver, 'handlewsgirequest', _handle_git_protocol 225 wireprotoserver, 'handlewsgirequest', _handle_git_protocol
220 ) 226 )
221 227
222 228
223 def reposetup(ui: hgui.ui, _: hgrepo.IRepo) -> None: 229 def uipopulate(ui: hgui.ui) -> None:
224 ui.setconfig(b'hooks', b'txnclose.__gitserve_internal__', _export_hook) 230 ui.setconfig(
225 231 b'hooks', b'post-git-export.__gitserve_add_tag__', fix_refs_hook
226 232 )
227 __all__ = ('__version__', 'cmdtable', 'command', 'uisetup', 'reposetup') 233 ui.setconfig(b'hooks', b'txnclose.__gitserve_export__', export_hook)
234
235
236 __all__ = (
237 '__version__',
238 'cmdtable',
239 'command',
240 'testedwith',
241 'uipopulate',
242 'uisetup',
243 )