diff --git a/heptapod/VERSION b/heptapod/VERSION index e298d64aee8df4e18938af5a744687e70bb65dd4_aGVwdGFwb2QvVkVSU0lPTg==..6ae2947cdd94cf337a5bc69ab0222a93ceac67b9_aGVwdGFwb2QvVkVSU0lPTg== 100644 --- a/heptapod/VERSION +++ b/heptapod/VERSION @@ -1,1 +1,1 @@ -4.8.1dev0 +5.0.0dev0 diff --git a/heptapod/testhelpers/git.py b/heptapod/testhelpers/git.py index e298d64aee8df4e18938af5a744687e70bb65dd4_aGVwdGFwb2QvdGVzdGhlbHBlcnMvZ2l0LnB5..6ae2947cdd94cf337a5bc69ab0222a93ceac67b9_aGVwdGFwb2QvdGVzdGhlbHBlcnMvZ2l0LnB5 100644 --- a/heptapod/testhelpers/git.py +++ b/heptapod/testhelpers/git.py @@ -52,7 +52,10 @@ def git(self, *args): return subprocess.check_output(('git', ) + args, cwd=str(self.path)) - def delete(self): + def delete(self): # pragma no cover (see below) + # We just removed the unique caller of this method. It was a direct + # call from a test, not something deeply buried in fixture code or + # similar (coverage drop would have been suspicious in that case). if os.path.exists(self.path): shutil.rmtree(self.path) diff --git a/heptapod/wsgi.py b/heptapod/wsgi.py index e298d64aee8df4e18938af5a744687e70bb65dd4_aGVwdGFwb2Qvd3NnaS5weQ==..6ae2947cdd94cf337a5bc69ab0222a93ceac67b9_aGVwdGFwb2Qvd3NnaS5weQ== 100644 --- a/heptapod/wsgi.py +++ b/heptapod/wsgi.py @@ -187,11 +187,6 @@ if native: repo.ui.setconfig(b'heptapod', b'native', pycompat.sysbytes(native)) - if repo.ui.configbool(b'heptapod', b'native'): - no_git = env.get('HTTP_X_HEPTAPOD_NO_GIT') - if no_git is not None: - repo.ui.setconfig(b'heptapod', b'no-git', - pycompat.sysbytes(no_git)) return repo def _runwsgi(self, env, respond): diff --git a/hgext3rd/heptapod/__init__.py b/hgext3rd/heptapod/__init__.py index e298d64aee8df4e18938af5a744687e70bb65dd4_aGdleHQzcmQvaGVwdGFwb2QvX19pbml0X18ucHk=..6ae2947cdd94cf337a5bc69ab0222a93ceac67b9_aGdleHQzcmQvaGVwdGFwb2QvX19pbml0X18ucHk= 100644 --- a/hgext3rd/heptapod/__init__.py +++ b/hgext3rd/heptapod/__init__.py @@ -86,7 +86,6 @@ configitem(b'heptapod', b'allow-multiple-heads') configitem(b'heptapod', b'allow-bookmarks') configitem(b'heptapod', b'native', False) - configitem(b'heptapod', b'no-git', False) cmdtable = {} @@ -130,9 +129,6 @@ env_native = ui.environ.get(b'HEPTAPOD_HG_NATIVE', None) if env_native is not None: ui.setconfig(b'heptapod', b'native', env_native) - env_no_git = ui.environ.get(b'HEPTAPOD_NO_GIT', None) - if env_no_git is not None and ui.configbool(b'heptapod', b'native'): - ui.setconfig(b'heptapod', b'no-git', env_no_git) @command( @@ -171,7 +167,7 @@ @command(b'gitlab-mirror') def gitlab_mirror(ui, repo): """Export changesets as Git commits in the GitLab repository.""" - if ui.configbool(b'heptapod', b'no-git'): + if ui.configbool(b'heptapod', b'native'): repo.ui.note(b'GitLab sync without Git export') no_git.NoGitStateMaintainer(ui, repo).sync() else: @@ -182,11 +178,7 @@ @command(b'move-hg-git-repo-out-of-gitaly-reach') def move_hg_git_repo_out_of_gitaly_reach(ui, repo): """Export changesets as Git commits in the GitLab repository.""" - if ui.configbool(b'heptapod', b'no-git'): - raise error.Abort(b"This should not be run on a native repository " - b"without Git conversion") - if not ui.configbool(b'heptapod', b'native'): raise error.Abort(b"This should not be run on a repository based " b"on hg-git: the point is to expose them to Gitaly") @@ -189,8 +181,9 @@ if not ui.configbool(b'heptapod', b'native'): raise error.Abort(b"This should not be run on a repository based " b"on hg-git: the point is to expose them to Gitaly") - git.HeptapodGitHandler(repo, repo.ui) # instantiation is enough + handler = git.HeptapodGitHandler(repo, repo.ui) + handler.native_project_ensure_set_git_dir() return 0 @@ -393,7 +386,7 @@ tarf.extractall(os.fsdecode(repo.path)) -@command(b'hpd-git-resync', [], ) +@command(b'hpd-git-resync|hpd-export-native-to-git', [], ) def git_resync(ui, repo): """Silently resynchronize the Git repo. @@ -408,13 +401,10 @@ No permissions are involved: all the refs changes are already presented by the Mercurial repository. - The main use case is to restart the Git sysnchronization if it has been - interrupted (e.g to try running without it) and it turns out to be - necessary, either to push to external Git services (use-case of - heptapod#125) or to workaround bugs while the mode without Git is - experimental. Except for the direct references to SHAs in the database - (and the time to convert to Git), in theory, a project could go all the - way back from being native without Git to being hg-git based. + The main use case used to be to switch back a Project to hg-git after it + has been migrated to native. + Nowadays, it is to update converted Git repositories that are being used + for remote pushes. """ handler = git.HeptapodGitHandler(repo, repo.ui) with repo.wlock(), repo.lock(): @@ -418,6 +408,7 @@ """ handler = git.HeptapodGitHandler(repo, repo.ui) with repo.wlock(), repo.lock(): + handler.native_project_ensure_set_git_dir() handler.export_git_objects() handler.save_map(handler.map_file) handler.force_git_refs_from_gitlab_files() diff --git a/hgext3rd/heptapod/git.py b/hgext3rd/heptapod/git.py index e298d64aee8df4e18938af5a744687e70bb65dd4_aGdleHQzcmQvaGVwdGFwb2QvZ2l0LnB5..6ae2947cdd94cf337a5bc69ab0222a93ceac67b9_aGdleHQzcmQvaGVwdGFwb2QvZ2l0LnB5 100644 --- a/hgext3rd/heptapod/git.py +++ b/hgext3rd/heptapod/git.py @@ -63,8 +63,21 @@ if main_repo is None: main_repo = self.repo - old_gitdir = re.sub(br'\.hg$', b'', main_repo.root) + b'.git' + self.gitdir = re.sub(br'\.hg$', b'', main_repo.root) + b'.git' + self.unfiltered_repo = self.repo.unfiltered() + self._default_git_ref = None + + def native_project_ensure_set_git_dir(self): + """Ensure existence of the auxiliary git repo for native Projects. + + Even native Mercurial projects can have auxiliary Git repositories, + for mirroring to Git, although they are not alongside the Mercurial + repository and are managed in a different way (see heptapod#1963). + + This makes sure that any Git repository for a native project is moved + over to the proper location. + """ storages_root = self.repo.ui.config(b'heptapod', b'repositories-root') git_repos_root = os.path.join(storages_root, b'+hgitaly', b'hg-git') os.makedirs(git_repos_root, mode=0o700, exist_ok=True) @@ -67,16 +80,9 @@ storages_root = self.repo.ui.config(b'heptapod', b'repositories-root') git_repos_root = os.path.join(storages_root, b'+hgitaly', b'hg-git') os.makedirs(git_repos_root, mode=0o700, exist_ok=True) - if self.repo.ui.configbool(b'heptapod', b'native'): - rel_path = os.path.relpath(old_gitdir, storages_root) - self.gitdir = os.path.join(git_repos_root, rel_path) - os.makedirs(os.path.dirname(self.gitdir), - mode=0o700, - exist_ok=True) - if ( - os.path.exists(old_gitdir) - and not os.path.exists(self.gitdir) - ): - os.rename(old_gitdir, self.gitdir) + + # After instantiation, `self.gitdir` is the proper one for + # legacy (hg-git based) projects + old_gitdir = self.gitdir @@ -82,4 +88,4 @@ - else: - self.gitdir = old_gitdir + rel_path = os.path.relpath(old_gitdir, storages_root) + self.gitdir = os.path.join(git_repos_root, rel_path) @@ -85,6 +91,10 @@ - self.unfiltered_repo = self.repo.unfiltered() - self._default_git_ref = None + os.makedirs(os.path.dirname(self.gitdir), + mode=0o700, + exist_ok=True) + + if os.path.exists(old_gitdir) and not os.path.exists(self.gitdir): + os.rename(old_gitdir, self.gitdir) @property def gitlab_refs(self): diff --git a/hgext3rd/heptapod/state_maintainer.py b/hgext3rd/heptapod/state_maintainer.py index e298d64aee8df4e18938af5a744687e70bb65dd4_aGdleHQzcmQvaGVwdGFwb2Qvc3RhdGVfbWFpbnRhaW5lci5weQ==..6ae2947cdd94cf337a5bc69ab0222a93ceac67b9_aGdleHQzcmQvaGVwdGFwb2Qvc3RhdGVfbWFpbnRhaW5lci5weQ== 100644 --- a/hgext3rd/heptapod/state_maintainer.py +++ b/hgext3rd/heptapod/state_maintainer.py @@ -815,7 +815,6 @@ prune_default_branch = to_prune.pop(default_git_branch, None) if ( prune_default_branch - and ui.configbool(b'heptapod', b'native') and default_git_branch.startswith(b'branch/') ): msg = (b"These changes close or prune the Heptapod default " diff --git a/hgext3rd/heptapod/tests/git/test_inner.py b/hgext3rd/heptapod/tests/git/test_inner.py index e298d64aee8df4e18938af5a744687e70bb65dd4_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvZ2l0L3Rlc3RfaW5uZXIucHk=..6ae2947cdd94cf337a5bc69ab0222a93ceac67b9_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvZ2l0L3Rlc3RfaW5uZXIucHk= 100644 --- a/hgext3rd/heptapod/tests/git/test_inner.py +++ b/hgext3rd/heptapod/tests/git/test_inner.py @@ -372,7 +372,7 @@ patch_gitlab_hooks(monkeypatch, notifs) config = common_config(tmpdir) - config['heptapod']['native'] = True + config['heptapod']['native'] = False wrapper = RepoWrapper.init(tmpdir.join('repo'), config=config) wrapper.write_commit('foo') diff --git a/hgext3rd/heptapod/tests/git/test_integration.py b/hgext3rd/heptapod/tests/git/test_integration.py index e298d64aee8df4e18938af5a744687e70bb65dd4_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvZ2l0L3Rlc3RfaW50ZWdyYXRpb24ucHk=..6ae2947cdd94cf337a5bc69ab0222a93ceac67b9_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvZ2l0L3Rlc3RfaW50ZWdyYXRpb24ucHk= 100644 --- a/hgext3rd/heptapod/tests/git/test_integration.py +++ b/hgext3rd/heptapod/tests/git/test_integration.py @@ -169,6 +169,7 @@ 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'} @@ -870,8 +871,12 @@ set_prune_closed_branches(wrapper, True) wrapper.command('commit', message=b"closing default!", close_branch=True) - # On non-native repos, this is ignored - wrapper.repo.ui.setconfig(b'heptapod', b'native', False) - wrapper.command('gitlab-mirror') + # On non-native repos, this is a user error + wrapper.repo.ui.setconfig(b'heptapod', b'native', False) # making sure + with pytest.raises(error.Abort) as exc_info: + wrapper.command('gitlab-mirror') + + assert re.search(br'prune.*default branch', exc_info.value.args[0]) + assert git_repo.branch_titles() == {b'branch/default': b'default1'} @@ -876,4 +881,6 @@ assert git_repo.branch_titles() == {b'branch/default': b'default1'} - # On native repos, this is a user error + # On native repos, this is a user error as well, but still it will move + # the repo out of Gitaly reach (this sounds weird in this "git" test, but + # we need the Git repo to have been created and this test just did it. wrapper.repo.ui.setconfig(b'heptapod', b'native', True) @@ -879,6 +886,5 @@ wrapper.repo.ui.setconfig(b'heptapod', b'native', True) - # But still it will move the repo out of Gitaly reach fixture.reload_git_repo() git_repo = fixture.git_repo fixture.clear_gitlab_notifs() @@ -881,11 +887,8 @@ fixture.reload_git_repo() git_repo = fixture.git_repo fixture.clear_gitlab_notifs() - with pytest.raises(error.Abort) as exc_info: - wrapper.command('gitlab-mirror') - - assert re.search(br'prune.*default branch', exc_info.value.args[0]) + wrapper.command('hpd-export-native-to-git') assert not notifs assert git_repo.branch_titles() == {b'branch/default': b'default1'} diff --git a/hgext3rd/heptapod/tests/no_git/test_integration.py b/hgext3rd/heptapod/tests/no_git/test_integration.py index e298d64aee8df4e18938af5a744687e70bb65dd4_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvbm9fZ2l0L3Rlc3RfaW50ZWdyYXRpb24ucHk=..6ae2947cdd94cf337a5bc69ab0222a93ceac67b9_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvbm9fZ2l0L3Rlc3RfaW50ZWdyYXRpb24ucHk= 100644 --- a/hgext3rd/heptapod/tests/no_git/test_integration.py +++ b/hgext3rd/heptapod/tests/no_git/test_integration.py @@ -77,7 +77,6 @@ tmpdir, monkeypatch, hg_config=common_config(tmpdir)) as fixture: fixture.hg_repo_wrapper.set_config('heptapod.native', True) - fixture.hg_repo_wrapper.set_config('heptapod.no-git', True) yield fixture @@ -1197,8 +1196,8 @@ # instead of about branches/refs being out of sync -def test_heptapod_notify_gitlab(tmpdir, monkeypatch): - wrapper = RepoWrapper.init(tmpdir.join('repo')) +def test_heptapod_notify_gitlab(empty_fixture, monkeypatch): + wrapper = empty_fixture.hg_repo_wrapper handler = NoGitStateMaintainer(wrapper.repo.ui, wrapper.repo) notifs = [] diff --git a/hgext3rd/heptapod/tests/test_commands_misc.py b/hgext3rd/heptapod/tests/test_commands_misc.py index e298d64aee8df4e18938af5a744687e70bb65dd4_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvdGVzdF9jb21tYW5kc19taXNjLnB5..6ae2947cdd94cf337a5bc69ab0222a93ceac67b9_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvdGVzdF9jb21tYW5kc19taXNjLnB5 100644 --- a/hgext3rd/heptapod/tests/test_commands_misc.py +++ b/hgext3rd/heptapod/tests/test_commands_misc.py @@ -20,4 +20,5 @@ ) from heptapod.testhelpers.gitlab import ( GitLabMirrorFixture, + GitLabStateMaintainerFixture, ) @@ -23,4 +24,5 @@ ) +from heptapod.testhelpers.git import GitRepo from .utils import common_config from .. import ( @@ -163,7 +165,7 @@ yield fixture -def test_git_resync(git_mirror_fixture): +def test_git_resync_existing_git_repo(git_mirror_fixture): fixture = git_mirror_fixture wrapper = fixture.hg_repo_wrapper hg_repo = wrapper.repo @@ -167,10 +169,9 @@ fixture = git_mirror_fixture wrapper = fixture.hg_repo_wrapper hg_repo = wrapper.repo - git_repo = fixture.git_repo base_ctx = wrapper.commit_file('foo', message='Commit 0') # Adding various repo content wrapper.command('tag', b'v1.2.3', rev=base_ctx.hex()) wrapper.commit_file('foo', message='Commit 1') @@ -171,9 +172,8 @@ base_ctx = wrapper.commit_file('foo', message='Commit 0') # Adding various repo content wrapper.command('tag', b'v1.2.3', rev=base_ctx.hex()) wrapper.commit_file('foo', message='Commit 1') - # TODO special ref - # TODO keep around + # special refs and keep-arounds are added later in this test wrapper.command('gitlab-mirror') @@ -179,2 +179,3 @@ wrapper.command('gitlab-mirror') + git_repo = fixture.git_repo @@ -180,4 +181,3 @@ - git_repo.delete() - (wrapper.path / '.hg' / 'git-mapfile').remove() + wrapper.set_config('heptapod', 'native', 'yes') @@ -183,2 +183,14 @@ + def assert_initial_git_state(): + assert git_repo.branch_titles() == {b'branch/default': b'Commit 1'} + assert git_repo.tags() == {b'v1.2.3'} + # checking ref targets, we don't care about the hashes + base_git_sha, base_git_title = git_repo.commit_hash_title('v1.2.3') + assert base_git_title == b'Commit 0' + return base_git_sha + + base_git_sha = assert_initial_git_state() + + # first call does not break anything, and moves Git repo to the dedicated + # location wrapper.command('hpd-git-resync') @@ -184,12 +196,11 @@ wrapper.command('hpd-git-resync') - assert git_repo.branch_titles() == {b'branch/default': b'Commit 1'} - assert git_repo.tags() == {b'v1.2.3'} - # checking ref targets, we don't care about the hashes - base_git_sha, base_git_title = git_repo.commit_hash_title('v1.2.3') - assert base_git_title == b'Commit 0' + fixture.reload_git_repo() + assert fixture.git_repo.path != git_repo.path + git_repo = fixture.git_repo + assert_initial_git_state() # now adding a new commit to check basic incrementality # (adding another branch just to spice it a bit) wrapper.commit_file('foo', message='Commit 2') other_ctx = wrapper.commit_file('bar', parent=base_ctx, branch='other', message='Commit 3') @@ -190,9 +201,8 @@ # now adding a new commit to check basic incrementality # (adding another branch just to spice it a bit) wrapper.commit_file('foo', message='Commit 2') other_ctx = wrapper.commit_file('bar', parent=base_ctx, branch='other', message='Commit 3') - wrapper.set_config('heptapod', 'no-git', 'on') wrapper.command('gitlab-mirror') @@ -197,6 +207,6 @@ wrapper.command('gitlab-mirror') - # Disabling of mirrorring to Git did work (validity of test hypothesis) + # Mirroring to Git did not happen (validity of test hypothesis) assert git_repo.branch_titles() == {b'branch/default': b'Commit 1'} wrapper.command('hpd-git-resync') @@ -214,6 +224,40 @@ assert git_repo.commit_hash_title(git_keep_around)[1] == b'Commit 0' +@pytest.fixture() +def native_fixture(tmpdir, monkeypatch): + with GitLabStateMaintainerFixture.init( + tmpdir, monkeypatch, + hg_config=common_config(tmpdir)) as fixture: + fixture.hg_repo_wrapper.set_config('heptapod.native', True) + yield fixture + + +def test_git_resync_creates_git_repo(native_fixture): + fixture = native_fixture + wrapper = fixture.hg_repo_wrapper + + base_ctx = wrapper.commit_file('foo', message='Commit 0') + + # Adding various repo content + wrapper.command('tag', b'v1.2.3', rev=base_ctx.hex()) + wrapper.commit_file('foo', message='Commit 1') + # TODO special ref + # TODO keep around + wrapper.command('gitlab-mirror') + wrapper.command('hpd-export-native-to-git') + git_repo = GitRepo( + fixture.base_path / '+hgitaly/hg-git' + / wrapper.path.basename.replace('.hg', '.git') + ) + assert git_repo.branch_titles() == {b'branch/default': b'Commit 1'} + assert git_repo.tags() == {b'v1.2.3'} + # checking ref targets, we don't care about the hashes + base_git_sha, base_git_title = git_repo.commit_hash_title('v1.2.3') + assert base_git_title == b'Commit 0' + return base_git_sha + + def test_move_out_of_gitaly_reach(git_mirror_fixture): fixture = git_mirror_fixture wrapper = fixture.hg_repo_wrapper @@ -232,7 +276,3 @@ assert os.path.exists(git_repo.path) # would not work if it were not a Git repo assert git_repo.branches() == {} - - wrapper.set_config('heptapod', 'no-git', True) - with pytest.raises(error.Abort): - wrapper.command('move-hg-git-repo-out-of-gitaly-reach') diff --git a/hgext3rd/heptapod/tests/test_config.py b/hgext3rd/heptapod/tests/test_config.py index e298d64aee8df4e18938af5a744687e70bb65dd4_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvdGVzdF9jb25maWcucHk=..6ae2947cdd94cf337a5bc69ab0222a93ceac67b9_aGdleHQzcmQvaGVwdGFwb2QvdGVzdHMvdGVzdF9jb25maWcucHk= 100644 --- a/hgext3rd/heptapod/tests/test_config.py +++ b/hgext3rd/heptapod/tests/test_config.py @@ -75,13 +75,8 @@ pycompat.sysbytes(item)) == value -@parametrize('env_value,no_git_value,expected,expected_no_git', - ((None, None, False, False), - (None, 'yes', False, False), - (None, 'no', False, False), - ('no', 'yes', False, False), - ('no', 'no', False, False), - ('yes', None, True, False), - ('yes', 'no', True, False), - ('yes', 'yes', True, True), +@parametrize('env_value,expected', + ((None, False), + ('no', False), + ('yes', True), )) @@ -87,5 +82,5 @@ )) -def test_native_from_env(env_value, no_git_value, expected, expected_no_git, +def test_native_from_env(env_value, expected, tmpdir, monkeypatch): env = dict(encoding.environ) env_key = b'HEPTAPOD_HG_NATIVE' @@ -89,7 +84,6 @@ tmpdir, monkeypatch): env = dict(encoding.environ) env_key = b'HEPTAPOD_HG_NATIVE' - no_git_key = b'HEPTAPOD_NO_GIT' if env_value is not None: env_value = env_value.encode() env[env_key] = env_value @@ -93,9 +87,6 @@ if env_value is not None: env_value = env_value.encode() env[env_key] = env_value - if no_git_value is not None: - no_git_value = no_git_value.encode() - env[no_git_key] = no_git_value monkeypatch.setattr(encoding, 'environ', env) @@ -105,4 +96,3 @@ assert ui.environ.get(env_key) == env_value # actual test assertions assert ui.configbool(b'heptapod', b'native') is expected - assert ui.configbool(b'heptapod', b'no-git') is expected_no_git