Skip to content
Snippets Groups Projects
Commit 687c6dd60738 authored by Georges Racinet's avatar Georges Racinet :squid:
Browse files

started to rewrap hg-git

This is done by subclassing `GitHandler`, for now
adding `update_exportable_for_named_branches`, almost
identically.

The only functional difference is that named branches and
topics are always exported, i.e., not depending
on the value of `hg-git.export-named-branches`
parent e79710251b38
No related branches found
No related tags found
1 merge request!15Rewrapping of hg-git, writing directly in GitLab's Git repository
......@@ -22,6 +22,7 @@
from . import (
topic as topicmod,
git,
)
eh = exthelper.exthelper()
......@@ -111,6 +112,11 @@
force=force, **opts)
@command('gitlab-mirror')
def gitlab_mirror(ui, repo):
git.gitlab_mirror(ui, repo)
def runsystem(orig, ui, cmd, environ, cwd, out):
heptapod_env = {k: v for k, v in ui.environ.items()
if k.startswith('HEPTAPOD_')}
......
"""Interaction with Git repos.
It is not expected that the rise of the HGitaly project would remove
all interesting things to do with Git.
"""
from dulwich.protocol import ZERO_SHA
from hggit.git_handler import GitHandler
from mercurial.i18n import _
from mercurial.node import hex
from mercurial import (
error,
)
import re
class HeptapodGitHandler(GitHandler):
def get_exportable(self):
"""Heptapod version, including named branches and topics.
This rewraps :meth:`GitHandler.get_exportable` to add named branches
and topics to the returned Git refs
"""
git_refs = super(HeptapodGitHandler, self).get_exportable()
logprefix = 'HeptapodGitHandler.get_exportable '
repo = self.repo.filtered('served')
ui = repo.ui
# "exporting" the ZERO_SHA will mean by convention a request to prune
# the corresponding heads -- only a request, we can't really decide
# at this stage.
# The value is a dict mapping refs to reasons (just strings)
to_prune = git_refs[ZERO_SHA] = {}
txn = repo.currenttransaction()
allow_bookmarks = ui.configbool('experimental',
'hg-git.bookmarks-on-named-branches')
bm_changes = txn.changes.get('bookmarks') if txn is not None else None
if bm_changes:
ui.note(
"HeptapodGitHandler bookmark changes=%r" % bm_changes)
new_bookmarks = []
for name, change in bm_changes.items():
if change[0] is None:
new_bookmarks.append(name)
elif change[1] is None:
# prune Git branch for removed bookmark
to_prune['refs/heads/' + name] = 'deleted-bookmark'
if new_bookmarks and not allow_bookmarks:
raise error.Abort(_(
"Creating bookmarks is forbidden by default in Heptapod "
"(got these new ones: %r). "
"See https://heptapod.net/pages/faq.html#bookmarks to "
"learn why and how to partially lift "
"that restriction" % new_bookmarks))
# TODO py3 compat (iteritems)
for new_bm_name, (_ign, new_bm_node) in bm_changes.iteritems():
new_bm_ctx = repo[new_bm_node]
new_bm_topic = new_bm_ctx.topic()
if new_bm_topic:
raise error.Abort(_(
"Creating or updating bookmarks on topic "
"changesets is forbidden"),
hint="topic %r and bookmark %r for changeset %s" % (
new_bm_topic, new_bm_name, new_bm_ctx.hex()))
all_bookmarks = self.repo._bookmarks
# currently, self_filter_for_bookmarks() only does some renaming,
# and doesn't discard any, so this is actually just equivalent to
# hgshas of all bookmarks, but that may change in future hg-git
hgshas_with_bookmark_git_ref = {
hex(all_bookmarks[bm])
for _, bm in self._filter_for_bookmarks(all_bookmarks)}
for branch, hg_nodes in repo.branchmap().iteritems():
gb = self.git_branch_for_branchmap_branch(branch)
if ui.configbool('experimental',
'hg-git.prune-newly-closed-branches'):
hg_nodes = [n for n in hg_nodes if not repo[n].closesbranch()]
if not hg_nodes:
to_prune['refs/heads/' + gb] = 'closed'
hg_shas = {hex(n) for n in hg_nodes}
# We ignore bookmarked changesets because:
# - we don't want them in the 'wild' namespace
# - no other Git ref would be relevant for them
hg_shas.difference_update(hgshas_with_bookmark_git_ref)
if not hg_shas:
if hg_nodes and allow_bookmarks:
# after removal of bookmarks, but not before,
# there is no hg head on this named branch:
# schedule potential removal. We don't want to do
# this if bookmarks aren't explicitely allowed because
# this must be a side effect, potentially disturbing, of
# an implicit bookmark move
to_prune['refs/heads/' + gb] = 'only-bookmarks'
continue
if 1 < len(hg_shas):
ui.note(logprefix, "Multiple heads for branch %r: %r" % (
branch, hg_shas))
for hg_sha in hg_shas:
git_refs[hg_sha].heads.add('refs/heads/wild/' + hg_sha)
gca = self.multiple_heads_choose(hg_shas, branch)
if gca is None:
# giving up in order to avoid confusing situations
continue
gca_sha = self.repo[gca].hex()
ui.note(logprefix,
"Chose %r out of multiple heads %r "
"for forwarding branch %r" % (
gca_sha, hg_shas, branch))
hg_shas = [gca_sha]
hg_sha = hg_shas.pop()
gb = self.git_branch_for_branchmap_branch(branch)
git_refs[hg_sha].heads.add('refs/heads/' + gb)
return git_refs
def gitlab_mirror(ui, repo):
"""Export changesets as Git commits in the repo expected by GitLab."""
handler = HeptapodGitHandler(repo, repo.ui)
hgdir = re.sub(r'\.hg$', '', repo.root)
handler.gitdir = hgdir + '.git'
handler.export_commits()
from __future__ import absolute_import
import subprocess
from heptapod.testhelpers import (
LocalRepoWrapper,
)
from .utils import common_config
class GitRepo(object):
def __init__(self, path):
self.path = path
@classmethod
def init(cls, path):
subprocess.call(('git', 'init', str(path)))
return cls(path)
def branch_titles(self):
out = subprocess.check_output(('git', 'branch', '-v'),
cwd=str(self.path))
split_lines = (l.split(None, 2) for l in out.splitlines())
return {sp[0]: sp[2] for sp in split_lines}
def make_main_repo(path):
"""Make repo with 2 public revs; return wrapper, ctx of rev 0
The returned ctx is for the first changeset because we'll use it as
a branching point, hence more often than the second.
"""
config = common_config()
config['extensions']['hggit'] = ''
config['phases'] = dict(publish=False)
wrapper = LocalRepoWrapper.init(path, config=config)
ctx = wrapper.write_commit('foo', content='foo0', message="default0",
return_ctx=True)
wrapper.write_commit('foo', content='foo1', message="default1")
wrapper.set_phase('public', ['.'])
return wrapper, ctx
def test_basic(tmpdir):
repo_path = tmpdir.join('repo.hg')
repo, base_ctx = make_main_repo(repo_path)
git_repo = GitRepo.init(tmpdir.join('repo.git'))
repo.command('gitlab-mirror')
assert git_repo.branch_titles() == {'branch/default': 'default1'}
Mercurial>=5.2
hg-evolve>=9.2.1
hg-git>=0.8.13
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment