Mercurial > hg-git-serve
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 ) |
