launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #25333
[Merge] ~pappacena/launchpad:mp-refs-privacy-change into launchpad:master
Thiago F. Pappacena has proposed merging ~pappacena/launchpad:mp-refs-privacy-change into launchpad:master with ~pappacena/launchpad:create-mp-refs as a prerequisite.
Commit message:
Background job to add/remove merge proposal's virtual refs when a repository has its privacy changed
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/390940
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~pappacena/launchpad:mp-refs-privacy-change into launchpad:master.
diff --git a/lib/lp/code/configure.zcml b/lib/lp/code/configure.zcml
index 898e645..4adc8d0 100644
--- a/lib/lp/code/configure.zcml
+++ b/lib/lp/code/configure.zcml
@@ -1111,6 +1111,11 @@
provides="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJobSource">
<allow interface="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJobSource" />
</securedutility>
+ <securedutility
+ component="lp.code.model.gitjob.GitRepositoryVirtualRefsSyncJob"
+ provides="lp.code.interfaces.gitjob.IGitRepositoryVirtualRefsSyncJobSource">
+ <allow interface="lp.code.interfaces.gitjob.IGitRepositoryVirtualRefsSyncJobSource" />
+ </securedutility>
<class class="lp.code.model.gitjob.GitRefScanJob">
<allow interface="lp.code.interfaces.gitjob.IGitJob" />
<allow interface="lp.code.interfaces.gitjob.IGitRefScanJob" />
@@ -1123,6 +1128,10 @@
<allow interface="lp.code.interfaces.gitjob.IGitJob" />
<allow interface="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJob" />
</class>
+ <class class="lp.code.model.gitjob.GitRepositoryVirtualRefsSyncJob">
+ <allow interface="lp.code.interfaces.gitjob.IGitJob" />
+ <allow interface="lp.code.interfaces.gitjob.IGitRepositoryVirtualRefsSyncJob" />
+ </class>
<lp:help-folder folder="help" name="+help-code" />
diff --git a/lib/lp/code/interfaces/gitjob.py b/lib/lp/code/interfaces/gitjob.py
index 4f31b19..7c3c038 100644
--- a/lib/lp/code/interfaces/gitjob.py
+++ b/lib/lp/code/interfaces/gitjob.py
@@ -1,4 +1,4 @@
-# Copyright 2015 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""GitJob interfaces."""
@@ -11,6 +11,8 @@ __all__ = [
'IGitRefScanJobSource',
'IGitRepositoryModifiedMailJob',
'IGitRepositoryModifiedMailJobSource',
+ 'IGitRepositoryVirtualRefsSyncJob',
+ 'IGitRepositoryVirtualRefsSyncJobSource',
'IReclaimGitRepositorySpaceJob',
'IReclaimGitRepositorySpaceJobSource',
]
@@ -93,3 +95,16 @@ class IGitRepositoryModifiedMailJobSource(IJobSource):
:param repository_delta: An `IGitRepositoryDelta` describing the
changes.
"""
+
+
+class IGitRepositoryVirtualRefsSyncJob(IRunnableJob):
+ """A job to synchronize all MPs virtual refs related to this repository."""
+
+
+class IGitRepositoryVirtualRefsSyncJobSource(IJobSource):
+
+ def create(repository):
+ """Send email about repository modifications.
+
+ :param repository: The `IGitRepository` that needs sync.
+ """
diff --git a/lib/lp/code/model/branchmergeproposal.py b/lib/lp/code/model/branchmergeproposal.py
index 8a29a99..bd804a8 100644
--- a/lib/lp/code/model/branchmergeproposal.py
+++ b/lib/lp/code/model/branchmergeproposal.py
@@ -1247,7 +1247,7 @@ class BranchMergeProposal(SQLBase, BugLinkTargetMixin):
self.target_git_repository.getInternalPath(),
GIT_MP_VIRTUAL_REF_FORMAT.format(mp=self))]
- def copyGitHostingVirtualRefs(self):
+ def copyGitHostingVirtualRefs(self, logger=logger):
"""Requests virtual refs copy operations on GitHosting in order to
keep them up-to-date with current MP's state.
@@ -1257,10 +1257,10 @@ class BranchMergeProposal(SQLBase, BugLinkTargetMixin):
hosting_client = getUtility(IGitHostingClient)
hosting_client.copyRefs(
self.source_git_repository.getInternalPath(),
- copy_operations)
+ copy_operations, logger=logger)
return copy_operations
- def deleteGitHostingVirtualRefs(self, except_refs=None):
+ def deleteGitHostingVirtualRefs(self, except_refs=None, logger=None):
"""Deletes on git code hosting service all virtual refs, except
those ones in the given list."""
if self.source_git_ref is None:
@@ -1278,14 +1278,16 @@ class BranchMergeProposal(SQLBase, BugLinkTargetMixin):
if ref not in (except_refs or []):
hosting_client = getUtility(IGitHostingClient)
hosting_client.deleteRef(
- self.target_git_repository.getInternalPath(), ref)
+ self.target_git_repository.getInternalPath(), ref,
+ logger=logger)
- def syncGitHostingVirtualRefs(self):
+ def syncGitHostingVirtualRefs(self, logger=None):
"""Requests all copies and deletion of virtual refs to make git code
hosting in sync with this MP."""
- operations = self.copyGitHostingVirtualRefs()
+ operations = self.copyGitHostingVirtualRefs(logger=logger)
copied_refs = [i.target_ref for i in operations]
- self.deleteGitHostingVirtualRefs(except_refs=copied_refs)
+ self.deleteGitHostingVirtualRefs(
+ except_refs=copied_refs, logger=logger)
def scheduleDiffUpdates(self, return_jobs=True):
"""See `IBranchMergeProposal`."""
diff --git a/lib/lp/code/model/gitjob.py b/lib/lp/code/model/gitjob.py
index 3c041da..d845f59 100644
--- a/lib/lp/code/model/gitjob.py
+++ b/lib/lp/code/model/gitjob.py
@@ -1,4 +1,4 @@
-# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -45,6 +45,8 @@ from lp.code.interfaces.gitjob import (
IGitRefScanJobSource,
IGitRepositoryModifiedMailJob,
IGitRepositoryModifiedMailJobSource,
+ IGitRepositoryVirtualRefsSyncJob,
+ IGitRepositoryVirtualRefsSyncJobSource,
IReclaimGitRepositorySpaceJob,
IReclaimGitRepositorySpaceJobSource,
)
@@ -100,6 +102,13 @@ class GitJobType(DBEnumeratedType):
modifications.
""")
+ SYNC_MP_VIRTUAL_REFS = DBItem(3, """
+ Sync merge proposals virtual refs
+
+ This job runs against a repository to synchronize the virtual refs
+ from all merge proposals related to this repository.
+ """)
+
@implementer(IGitJob)
class GitJob(StormBase):
@@ -404,3 +413,56 @@ class GitRepositoryModifiedMailJob(GitJobDerived):
def run(self):
"""See `IGitRepositoryModifiedMailJob`."""
self.getMailer().sendAll()
+
+
+@implementer(IGitRepositoryVirtualRefsSyncJob)
+@provider(IGitRepositoryVirtualRefsSyncJobSource)
+class GitRepositoryVirtualRefsSyncJob(GitJobDerived):
+ """A Job that scans a Git repository for its current list of references."""
+ class_job_type = GitJobType.SYNC_MP_VIRTUAL_REFS
+
+ class RetryException(Exception):
+ pass
+
+ max_retries = 5
+
+ retry_error_types = (RetryException, )
+
+ config = config.IGitRepositoryVirtualRefsSyncJobSource
+
+ @classmethod
+ def create(cls, repository):
+ metadata = {"synced_mp_ids": []}
+ git_job = GitJob(repository, cls.class_job_type, metadata)
+ job = cls(git_job)
+ job.celeryRunOnCommit()
+ return job
+
+ @property
+ def synced_mp_ids(self):
+ return self.metadata["synced_mp_ids"]
+
+ def add_synced_mp_id(self, mp_id):
+ self.metadata["synced_mp_ids"].append(mp_id)
+
+ def run(self):
+ log.info(
+ "Starting to re-sync virtual refs from repository %s",
+ self.repository)
+ failed = False
+ for mp in self.repository.landing_targets:
+ if mp.id in self.synced_mp_ids:
+ continue
+ try:
+ log.info("Re-syncing virtual refs from MP %s", mp)
+ mp.syncGitHostingVirtualRefs(logger=log)
+ self.synced_mp_ids.append(mp.id)
+ except Exception as e:
+ log.info(
+ "Re-syncing virtual refs from MP %s failed: %s", mp, e)
+ failed = True
+ log.info(
+ "Finished re-syncing virtual refs from repository %s%s",
+ self.repository, " with failures" if failed else "")
+ if failed:
+ raise GitRepositoryVirtualRefsSyncJob.RetryException()
diff --git a/lib/lp/code/model/gitrepository.py b/lib/lp/code/model/gitrepository.py
index e2451fa..6379fce 100644
--- a/lib/lp/code/model/gitrepository.py
+++ b/lib/lp/code/model/gitrepository.py
@@ -116,7 +116,10 @@ from lp.code.interfaces.gitcollection import (
IGitCollection,
)
from lp.code.interfaces.githosting import IGitHostingClient
-from lp.code.interfaces.gitjob import IGitRefScanJobSource
+from lp.code.interfaces.gitjob import (
+ IGitRefScanJobSource,
+ IGitRepositoryVirtualRefsSyncJobSource,
+ )
from lp.code.interfaces.gitlookup import IGitLookup
from lp.code.interfaces.gitnamespace import (
get_git_namespace,
@@ -866,6 +869,7 @@ class GitRepository(StormBase, WebhookTargetMixin, GitIdentityMixin):
raise CannotChangeInformationType("Forbidden by project policy.")
# XXX cjwatson 2019-03-29: Check privacy rules on snaps that use
# this repository.
+ was_private = self.private
self.information_type = information_type
self._reconcileAccess()
if (information_type in PRIVATE_INFORMATION_TYPES and
@@ -883,6 +887,12 @@ class GitRepository(StormBase, WebhookTargetMixin, GitIdentityMixin):
# subscriptions.
getUtility(IRemoveArtifactSubscriptionsJobSource).create(user, [self])
+ # If privacy changed, we need to re-sync all virtual refs from
+ # all MPs to avoid disclosing private code, or to add the virtual
+ # refs to the now public code.
+ if was_private != self.private:
+ getUtility(IGitRepositoryVirtualRefsSyncJobSource).create(self)
+
def setName(self, new_name, user):
"""See `IGitRepository`."""
self.namespace.moveRepository(self, user, new_name=new_name)
diff --git a/lib/lp/code/model/tests/test_branchmergeproposal.py b/lib/lp/code/model/tests/test_branchmergeproposal.py
index 54cf516..277650c 100644
--- a/lib/lp/code/model/tests/test_branchmergeproposal.py
+++ b/lib/lp/code/model/tests/test_branchmergeproposal.py
@@ -285,7 +285,7 @@ class TestGitBranchMergeProposalVirtualRefs(TestCaseWithFactory):
self.assertEqual(1, self.hosting_fixture.copyRefs.call_count)
args, kwargs = self.hosting_fixture.copyRefs.calls[0]
arg_path, arg_copy_operations = args
- self.assertEqual({}, kwargs)
+ self.assertEqual({'logger': None}, kwargs)
self.assertEqual(mp.source_git_repository.getInternalPath(), arg_path)
self.assertEqual(1, len(arg_copy_operations))
self.assertThat(arg_copy_operations[0], MatchesStructure(
@@ -357,7 +357,7 @@ class TestGitBranchMergeProposalVirtualRefs(TestCaseWithFactory):
# Check lp.code.subscribers.branchmergeproposal.merge_proposal_created.
self.assertEqual(1, self.hosting_fixture.copyRefs.call_count)
args, kwargs = self.hosting_fixture.copyRefs.calls[0]
- self.assertEquals({}, kwargs)
+ self.assertEquals({'logger': None}, kwargs)
self.assertEqual(args[0], source_repo.getInternalPath())
self.assertEqual(1, len(args[1]))
self.assertThat(args[1][0], MatchesStructure(
@@ -384,7 +384,7 @@ class TestGitBranchMergeProposalVirtualRefs(TestCaseWithFactory):
self.assertEqual(0, self.hosting_fixture.copyRefs.call_count)
self.assertEqual(1, self.hosting_fixture.deleteRef.call_count)
args, kwargs = self.hosting_fixture.deleteRef.calls[0]
- self.assertEqual({}, kwargs)
+ self.assertEqual({'logger': None}, kwargs)
self.assertEqual(
(target_repo.getInternalPath(), "refs/merge/%s/head" % mp.id),
args)
@@ -1658,7 +1658,7 @@ class TestBranchMergeProposalDeletion(TestCaseWithFactory):
args = hosting_fixture.deleteRef.calls[0]
self.assertEqual((
(proposal.target_git_repository.getInternalPath(),
- 'refs/merge/%s/head' % proposal.id), {}), args)
+ 'refs/merge/%s/head' % proposal.id), {'logger': None}), args)
class TestBranchMergeProposalBugs(WithVCSScenarios, TestCaseWithFactory):
diff --git a/lib/lp/code/model/tests/test_gitjob.py b/lib/lp/code/model/tests/test_gitjob.py
index 5964944..c92be62 100644
--- a/lib/lp/code/model/tests/test_gitjob.py
+++ b/lib/lp/code/model/tests/test_gitjob.py
@@ -1,4 +1,4 @@
-# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for `GitJob`s."""
@@ -20,13 +20,16 @@ from testtools.matchers import (
ContainsDict,
Equals,
MatchesDict,
+ MatchesListwise,
MatchesSetwise,
MatchesStructure,
)
import transaction
+from zope.component import getUtility
from zope.interface import providedBy
from zope.security.proxy import removeSecurityProxy
+from lp.app.enums import InformationType
from lp.code.adapters.gitrepository import GitRepositoryDelta
from lp.code.enums import (
GitGranteeType,
@@ -38,8 +41,10 @@ from lp.code.interfaces.branchmergeproposal import (
from lp.code.interfaces.gitjob import (
IGitJob,
IGitRefScanJob,
+ IGitRepositoryVirtualRefsSyncJobSource,
IReclaimGitRepositorySpaceJob,
)
+from lp.code.interfaces.gitrepository import GIT_CREATE_MP_VIRTUAL_REF
from lp.code.model.gitjob import (
describe_repository_delta,
GitJob,
@@ -49,9 +54,11 @@ from lp.code.model.gitjob import (
ReclaimGitRepositorySpaceJob,
)
from lp.code.tests.helpers import GitHostingFixture
+from lp.services.compat import mock
from lp.services.config import config
from lp.services.database.constants import UTC_NOW
from lp.services.features.testing import FeatureFixture
+from lp.services.job.interfaces.job import JobStatus
from lp.services.job.runner import JobRunner
from lp.services.utils import seconds_since_epoch
from lp.services.webapp import canonical_url
@@ -484,5 +491,124 @@ class TestDescribeRepositoryDelta(TestCaseWithFactory):
snapshot, repository)
+class TestGitRepositoryVirtualRefsSyncJob(TestCaseWithFactory):
+ """Tests for `GitRepositoryVirtualRefsSyncJob`."""
+
+ layer = ZopelessDatabaseLayer
+
+ def runJobs(self):
+ with dbuser("branchscanner"):
+ job_set = JobRunner.fromReady(
+ getUtility(IGitRepositoryVirtualRefsSyncJobSource))
+ job_set.runAll()
+ return job_set
+
+ def test_changing_repo_to_private_deletes_refs(self):
+ self.useFixture(FeatureFixture({GIT_CREATE_MP_VIRTUAL_REF: "on"}))
+ hosting_fixture = self.useFixture(GitHostingFixture())
+ mp = self.factory.makeBranchMergeProposalForGit()
+ source_repo = mp.source_git_repository
+ target_repo = mp.target_git_repository
+ source_repo.transitionToInformationType(
+ InformationType.PRIVATESECURITY, source_repo.owner, False)
+
+ hosting_fixture.copyRefs.resetCalls()
+ hosting_fixture.deleteRef.resetCalls()
+ jobs = self.runJobs()
+ self.assertEqual(1, len(jobs.completed_jobs))
+
+ self.assertEqual(0, hosting_fixture.copyRefs.call_count)
+ self.assertEqual(1, hosting_fixture.deleteRef.call_count)
+ args, kwargs = hosting_fixture.deleteRef.calls[0]
+ self.assertEqual(
+ (target_repo.getInternalPath(), 'refs/merge/%s/head' % mp.id),
+ args)
+
+ def test_changing_repo_to_public_recreates_refs(self):
+ self.useFixture(FeatureFixture({GIT_CREATE_MP_VIRTUAL_REF: "on"}))
+ hosting_fixture = self.useFixture(GitHostingFixture())
+ mp = self.factory.makeBranchMergeProposalForGit()
+ source_repo = mp.source_git_repository
+ source_repo.transitionToInformationType(
+ InformationType.PRIVATESECURITY, source_repo.owner, False)
+ self.runJobs()
+
+ # Move it back to public.
+ hosting_fixture.copyRefs.resetCalls()
+ hosting_fixture.deleteRef.resetCalls()
+ source_repo.transitionToInformationType(
+ InformationType.PUBLIC, source_repo.owner, False)
+ jobs = self.runJobs()
+ self.assertEqual(1, len(jobs.completed_jobs))
+
+ self.assertEqual(1, hosting_fixture.copyRefs.call_count)
+ self.assertEqual(0, hosting_fixture.deleteRef.call_count)
+ args, kwargs = hosting_fixture.copyRefs.calls[0]
+ self.assertEqual({'logger': mock.ANY}, kwargs)
+ self.assertEqual(2, len(args))
+ repo, operations = args
+ self.assertEqual(repo, mp.source_git_repository.getInternalPath())
+ self.assertThat(operations, MatchesListwise([
+ MatchesStructure(
+ source_ref=Equals(mp.source_git_commit_sha1),
+ target_repo=Equals(mp.target_git_repository.getInternalPath()),
+ target_ref=Equals("refs/merge/%s/head" % mp.id),
+ )
+ ]))
+
+ @mock.patch("lp.code.model.branchmergeproposal.BranchMergeProposal."
+ "syncGitHostingVirtualRefs")
+ def test_changing_repo_retry_skips_successful_syncs(
+ self, syncGitHostingVirtualRefs):
+ # Makes sure that successful syncs are not retried on failures.
+ self.useFixture(FeatureFixture({GIT_CREATE_MP_VIRTUAL_REF: "on"}))
+ hosting_fixture = self.useFixture(GitHostingFixture())
+ mp1 = self.factory.makeBranchMergeProposalForGit()
+ source_repo = mp1.source_git_repository
+
+ mp2 = self.factory.makeBranchMergeProposalForGit(
+ source_ref=mp1.source_git_ref)
+ mp3 = self.factory.makeBranchMergeProposalForGit(
+ source_ref=mp1.source_git_ref)
+
+ # The 1st call to syncGitHostingVirtualRefs (mp1) should succeed.
+ # The 2nd call (mp2, first try) should fail
+ # The 3rd call (mp3) should succeed.
+ # Then, 4th call (mp2 again) should succeed.
+ syncGitHostingVirtualRefs.reset_mock()
+ syncGitHostingVirtualRefs.side_effect = [None, Exception(), None, None]
+
+ # Make source repo private, to trigger the job.
+ hosting_fixture.copyRefs.resetCalls()
+ hosting_fixture.deleteRef.resetCalls()
+ source_repo.transitionToInformationType(
+ InformationType.PRIVATESECURITY, source_repo.owner, False)
+ jobs = self.runJobs()
+ # Should have no completed jobs, since the job should be retried.
+ self.assertEqual(
+ 0, len(jobs.completed_jobs),
+ "No job should have been finished.")
+ self.assertEqual(
+ 1, len(jobs.incomplete_jobs), "Job retry should be pending.")
+ self.assertEqual(
+ 3, syncGitHostingVirtualRefs.call_count,
+ "Even with a failure on mp2, syncGitHostingVirtualRefs should "
+ "have been called for every branch merge proposal.")
+ job = removeSecurityProxy(jobs.incomplete_jobs[0])
+ self.assertEqual([mp1.id, mp3.id], job.synced_mp_ids)
+
+ # Run the job again.
+ syncGitHostingVirtualRefs.reset_mock()
+ JobRunner(jobs.incomplete_jobs).runAll()
+ self.assertEqual(
+ JobStatus.COMPLETED, jobs.incomplete_jobs[0].status,
+ "The job should have completed this time, since mp2 sync should "
+ "not have raised exception.")
+ self.assertEqual(
+ 1, syncGitHostingVirtualRefs.call_count,
+ "mp2.syncGitHostingVirtualRefs should be the only call to this "
+ "method, since the sync should have been skipped for mp1 and mp3.")
+ self.assertEqual([mp1.id, mp3.id, mp2.id], job.synced_mp_ids)
+
# XXX cjwatson 2015-03-12: We should test that the jobs work via Celery too,
# but that isn't feasible until we have a proper turnip fixture.
diff --git a/lib/lp/code/model/tests/test_gitrepository.py b/lib/lp/code/model/tests/test_gitrepository.py
index b0116e1..c561b3c 100644
--- a/lib/lp/code/model/tests/test_gitrepository.py
+++ b/lib/lp/code/model/tests/test_gitrepository.py
@@ -18,6 +18,7 @@ from datetime import (
import email
from functools import partial
import hashlib
+import itertools
import json
from breezy import urlutils
@@ -89,6 +90,7 @@ from lp.code.interfaces.defaultgit import ICanHasDefaultGitRepository
from lp.code.interfaces.gitjob import (
IGitRefScanJobSource,
IGitRepositoryModifiedMailJobSource,
+ IGitRepositoryVirtualRefsSyncJobSource,
)
from lp.code.interfaces.gitlookup import IGitLookup
from lp.code.interfaces.gitnamespace import (
@@ -147,6 +149,7 @@ from lp.registry.interfaces.personociproject import IPersonOCIProjectFactory
from lp.registry.interfaces.personproduct import IPersonProductFactory
from lp.registry.tests.test_accesspolicy import get_policies_for_artifact
from lp.services.authserver.xmlrpc import AuthServerAPIView
+from lp.services.compat import mock
from lp.services.config import config
from lp.services.database.constants import UTC_NOW
from lp.services.database.interfaces import IStore
@@ -181,6 +184,7 @@ from lp.testing import (
verifyObject,
)
from lp.testing.dbuser import dbuser
+from lp.testing.fixture import ZopeUtilityFixture
from lp.testing.layers import (
DatabaseFunctionalLayer,
LaunchpadFunctionalLayer,
@@ -4662,3 +4666,61 @@ class TestGitRepositoryMacaroonIssuer(MacaroonTestMixin, TestCaseWithFactory):
["Caveat check for '%s' failed." %
find_caveats_by_name(macaroon2, "lp.expires")[0].caveat_id],
issuer, macaroon2, repository, user=repository.owner)
+
+
+class TestGitRepositoryPrivacyChangeSyncVirtualRefs(TestCaseWithFactory):
+ layer = DatabaseFunctionalLayer
+
+ def assertChangePrivacyTriggersSync(
+ self, from_list, to_list, should_trigger_sync=True):
+ """Runs repository.transitionToInformationType from every item in
+ `from_list` to each item in `to_list`, and checks if the virtual
+ refs sync was triggered or not, depending on `should_trigger_sync`."""
+ sync_job = mock.Mock()
+ self.useFixture(ZopeUtilityFixture(
+ sync_job, IGitRepositoryVirtualRefsSyncJobSource))
+
+ admin = self.factory.makeAdministrator()
+ login_person(admin)
+ for from_type, to_type in itertools.product(from_list, to_list):
+ if from_type == to_type:
+ continue
+ repository = self.factory.makeGitRepository()
+ naked_repo = removeSecurityProxy(repository)
+ naked_repo.information_type = from_type
+ # Skip access policy reconciliation.
+ naked_repo._reconcileAccess = mock.Mock()
+ naked_repo.transitionToInformationType(to_type, admin, False)
+
+ if should_trigger_sync:
+ sync_job.create.assert_called_with(repository)
+ else:
+ self.assertEqual(
+ 0, sync_job.create.call_count,
+ "Changing from %s to %s should't trigger vrefs sync"
+ % (from_type, to_type))
+ sync_job.reset_mock()
+
+ def test_setting_repo_public_triggers_ref_sync_job(self):
+ self.assertChangePrivacyTriggersSync(
+ PRIVATE_INFORMATION_TYPES,
+ PUBLIC_INFORMATION_TYPES,
+ should_trigger_sync=True)
+
+ def test_setting_repo_private_triggers_ref_sync_job(self):
+ self.assertChangePrivacyTriggersSync(
+ PUBLIC_INFORMATION_TYPES,
+ PRIVATE_INFORMATION_TYPES,
+ should_trigger_sync=True)
+
+ def test_keeping_repo_private_dont_trigger_ref_sync_job(self):
+ self.assertChangePrivacyTriggersSync(
+ PRIVATE_INFORMATION_TYPES,
+ PRIVATE_INFORMATION_TYPES,
+ should_trigger_sync=False)
+
+ def test_keeping_repo_public_dont_trigger_ref_sync_job(self):
+ self.assertChangePrivacyTriggersSync(
+ PUBLIC_INFORMATION_TYPES,
+ PUBLIC_INFORMATION_TYPES,
+ should_trigger_sync=False)
diff --git a/lib/lp/services/config/schema-lazr.conf b/lib/lp/services/config/schema-lazr.conf
index bb8afcd..3921da2 100644
--- a/lib/lp/services/config/schema-lazr.conf
+++ b/lib/lp/services/config/schema-lazr.conf
@@ -1828,6 +1828,10 @@ module: lp.code.interfaces.gitjob
dbuser: send-branch-mail
crontab_group: MAIN
+[IGitRepositoryVirtualRefsSyncJobSource]
+module: lp.code.interfaces.gitjob
+dbuser: branchscanner
+
[IInitializeDistroSeriesJobSource]
module: lp.soyuz.interfaces.distributionjob
dbuser: initializedistroseries
diff --git a/lib/lp/testing/fakemethod.py b/lib/lp/testing/fakemethod.py
index 4bba8d3..b4895ce 100644
--- a/lib/lp/testing/fakemethod.py
+++ b/lib/lp/testing/fakemethod.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -57,3 +57,6 @@ class FakeMethod:
def extract_kwargs(self):
"""Return just the calls' keyword-arguments dicts."""
return [kwargs for args, kwargs in self.calls]
+
+ def resetCalls(self):
+ self.calls = []