diff --git a/hgext3rd/heptapod/git.py b/hgext3rd/heptapod/git.py index c0860aa1c0bb9a54df0c84d3431a4d6e701d0bbf_aGdleHQzcmQvaGVwdGFwb2QvZ2l0LnB5..619676e43dc4f241bfcd4d6a55d0f376fd7f3255_aGdleHQzcmQvaGVwdGFwb2QvZ2l0LnB5 100644 --- a/hgext3rd/heptapod/git.py +++ b/hgext3rd/heptapod/git.py @@ -11,6 +11,7 @@ """ from __future__ import absolute_import +from mercurial import error try: from hgext3rd.hggit.git_handler import GitHandler except ImportError: # pragma: no cover (fallback for hg-git < 0.11) @@ -96,6 +97,41 @@ if os.path.exists(old_gitdir) and not os.path.exists(self.gitdir): os.rename(old_gitdir, self.gitdir) + self.fixup_git_pool() + + def fixup_git_pool(self): + alternates_path = os.path.join(self.gitdir, + b'objects/info/alternates') + if not os.path.exists(alternates_path): + return + + with open(alternates_path, 'rb') as alternates: + lines = alternates.readlines() + + new_lines = [] + for line in lines: + line = line.strip() + tgt_path = os.path.join(self.gitdir, line) + if os.path.exists(tgt_path): + new_lines.append(line) + else: + repositories_root = self.repo.ui.config(b'heptapod', + b'repositories-root') + idx = tgt_path.find(b'@pools/') + if idx == -1: + raise error.Abort( + b"Unreachable Git alternates path that is not " + b"under the expected GitLab pools: %r" % tgt_path) + abs_tgt = os.path.join(repositories_root, tgt_path[idx:]) + if not os.path.exists(abs_tgt): + raise error.Abort( + b"After fix, path to shared Git inside GitLab pools " + b"does not exist: %r" % abs_tgt) + new_lines.append(os.path.relpath(abs_tgt, + self.gitdir + b'/objects')) + with open(alternates_path, 'wb') as alternates: + alternates.write(b'\n'.join(new_lines)) + @property def gitlab_refs(self): return self.git.refs diff --git a/hgext3rd/heptapod/tests/git/test_inner.py b/hgext3rd/heptapod/tests/git/test_inner.py index c0860aa1c0bb9a54df0c84d3431a4d6e701d0bbf_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvZ2l0L3Rlc3RfaW5uZXIucHk=..619676e43dc4f241bfcd4d6a55d0f376fd7f3255_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvZ2l0L3Rlc3RfaW5uZXIucHk= 100644 --- a/hgext3rd/heptapod/tests/git/test_inner.py +++ b/hgext3rd/heptapod/tests/git/test_inner.py @@ -15,6 +15,7 @@ from mercurial import ( error, ) +from pathlib import Path import pytest import re @@ -22,6 +23,7 @@ from heptapod.testhelpers import ( RepoWrapper, ) +from heptapod.testhelpers.git import GitRepo from heptapod.gitlab import prune_reasons from heptapod.gitlab.branch import gitlab_branch_ref as git_branch_ref from heptapod.gitlab.change import GitLabRefChange as RefChange @@ -39,6 +41,8 @@ # TODO move to helper module from .test_integration import patch_gitlab_hooks +parametrize = pytest.mark.parametrize + @pytest.fixture def wrapper(tmpdir): @@ -449,3 +453,36 @@ fallback=REF_TARGET_UNKNOWN_RAISE) ) assert exc_info.value.args == (unknown_git_sha, ) + + +@parametrize('special', + ('abspath-not-found', 'relpath-not-found', 'other-alt')) +def test_fixup_git_pool_special_cases(wrapper, special, tmpdir): + wrapper.repo.ui.setconfig(b'heptapod', b'native', True) + git_repo = GitRepo.init(wrapper.path / '../repo.git') + if special == 'abspath-not-found': + alt_path = b'/no/such/path' + elif special == 'relpath-not-found': + alt_path = b'../@pools/does/not/exist/either' + else: + alt_path = tmpdir / 'existing' + alt_path.mkdir() + alt_path = str(alt_path).encode('utf-8') + conf_relpath = 'objects/info/alternates' + (git_repo.path / conf_relpath).write_binary(alt_path) + + handler = HeptapodGitHandler(wrapper.repo, wrapper.repo.ui) + if special == 'other-alt': + handler.native_project_ensure_set_git_dir() + moved_path = Path(handler.gitdir.decode()) + assert moved_path != git_repo.path + assert (moved_path / conf_relpath).read_bytes() == alt_path + return + + with pytest.raises(error.Abort) as exc_info: + handler.native_project_ensure_set_git_dir() + if special == 'abspath-not-found': + expected_msg = b'Unreachable Git alternates' + else: + expected_msg = b'path to shared Git inside GitLab pools' + assert expected_msg in exc_info.value.args[0] diff --git a/hgext3rd/heptapod/tests/git/test_integration.py b/hgext3rd/heptapod/tests/git/test_integration.py index c0860aa1c0bb9a54df0c84d3431a4d6e701d0bbf_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvZ2l0L3Rlc3RfaW50ZWdyYXRpb24ucHk=..619676e43dc4f241bfcd4d6a55d0f376fd7f3255_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvZ2l0L3Rlc3RfaW50ZWdyYXRpb24ucHk= 100644 --- a/hgext3rd/heptapod/tests/git/test_integration.py +++ b/hgext3rd/heptapod/tests/git/test_integration.py @@ -16,6 +16,7 @@ import pytest import re import shutil +import subprocess from heptapod.gitlab import hooks from heptapod.gitlab.prune_reasons import ( @@ -154,9 +155,11 @@ } -def test_moved_out_of_gitaly_reach(main_fixture): +@parametrize('pool', ('with-pool', 'without_pool')) +def test_moved_out_of_gitaly_reach(main_fixture, pool): + with_pool = pool == 'with-pool' fixture = main_fixture wrapper = fixture.hg_repo_wrapper wrapper.command('gitlab-mirror') orig_git_path = fixture.git_repo.path assert fixture.git_repo.branch_titles() == {b'branch/default': b'default1'} @@ -158,10 +161,26 @@ fixture = main_fixture wrapper = fixture.hg_repo_wrapper wrapper.command('gitlab-mirror') orig_git_path = fixture.git_repo.path assert fixture.git_repo.branch_titles() == {b'branch/default': b'default1'} + alternates_relpath = 'objects/info/alternates' + if with_pool: + pools_path = orig_git_path / '../@pools' + pools_path.mkdir() + in_pool_path = pools_path / 'shared.git' + orig_git_path.rename(in_pool_path) + # git clone --shared normalizes the 'alternates' with absolute paths. + # There is now point trying to give it relative paths, we will + # edit the alternates later on to match what GitLab does + pool_rel_path = b'../../@pools/shared.git/objects' + subprocess.check_call(('git', 'clone', '--bare', '--shared', + in_pool_path, orig_git_path)) + (orig_git_path / alternates_relpath).write_binary(pool_rel_path) + # we did not break it by setting up the alternate objects store. + assert fixture.git_repo.branch_titles() == { + b'branch/default': b'default1'} wrapper.set_config('heptapod', 'native', True) fixture.reload_git_repo() assert fixture.git_repo.path != orig_git_path @@ -163,11 +182,12 @@ wrapper.set_config('heptapod', 'native', True) fixture.reload_git_repo() assert fixture.git_repo.path != orig_git_path + # this performs the move wrapper.commit_file('foo', content='foo2', message="default2") wrapper.command('gitlab-mirror') wrapper.command('hpd-export-native-to-git') assert fixture.git_repo.branch_titles() == {b'branch/default': b'default2'} @@ -168,9 +188,21 @@ wrapper.commit_file('foo', content='foo2', message="default2") wrapper.command('gitlab-mirror') wrapper.command('hpd-export-native-to-git') assert fixture.git_repo.branch_titles() == {b'branch/default': b'default2'} + # subsequent updates (not going through the moving code) also work + wrapper.commit_file('foo', + content='foo3', + message="default3") + wrapper.command('gitlab-mirror') + wrapper.command('hpd-export-native-to-git') + assert fixture.git_repo.branch_titles() == {b'branch/default': b'default3'} + + if with_pool: + # just to be sure + assert (fixture.git_repo.path / alternates_relpath + ).read_binary() != pool_rel_path @parametrize('pushvar', ('skip-ci', 'mr-create'))