Merge ~pappacena/launchpad:git-repo-async-privacy into launchpad:master
- Git
- lp:~pappacena/launchpad
- git-repo-async-privacy
- Merge into master
Status: | Needs review |
---|---|
Proposed branch: | ~pappacena/launchpad:git-repo-async-privacy |
Merge into: | launchpad:master |
Diff against target: |
734 lines (+312/-31) 17 files modified
database/schema/security.cfg (+18/-0) lib/lp/app/widgets/itemswidgets.py (+6/-2) lib/lp/code/browser/gitrepository.py (+9/-1) lib/lp/code/browser/tests/test_branchmergeproposal.py (+1/-1) lib/lp/code/browser/tests/test_gitrepository.py (+69/-3) lib/lp/code/configure.zcml (+9/-0) lib/lp/code/interfaces/gitjob.py (+20/-1) lib/lp/code/interfaces/gitrepository.py (+4/-0) lib/lp/code/model/gitjob.py (+57/-0) lib/lp/code/model/gitref.py (+1/-1) lib/lp/code/model/gitrepository.py (+42/-9) lib/lp/code/model/tests/test_gitcollection.py (+1/-1) lib/lp/code/model/tests/test_gitjob.py (+59/-0) lib/lp/code/model/tests/test_gitrepository.py (+4/-6) lib/lp/code/xmlrpc/tests/test_git.py (+5/-5) lib/lp/services/config/schema-lazr.conf (+6/-0) lib/lp/testing/factory.py (+1/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Needs Information | ||
Ioana Lasc (community) | Approve | ||
Review via email: mp+392776@code.launchpad.net |
Commit message
Doing repository privacy change in background, and blocking user from changing it while another change is in progress.
Description of the change
- 974b22a... by Thiago F. Pappacena
-
Fixing test
Colin Watson (cjwatson) wrote : | # |
I think this is mostly OK, but I wonder if it could be even better. Instead of adding a new item to GitRepositorySt
- 826cbad... by Thiago F. Pappacena
-
Encapsulating GitRepository.
pending_ change_ information_ type - eeeb231... by Thiago F. Pappacena
-
Renaming GitRepo information type change job
- 2493c1d... by Thiago F. Pappacena
-
Changing exception msg when blocking info type transition for git repo
Thiago F. Pappacena (pappacena) wrote : | # |
Pushed the requested changes.
I thought about just checking on-the-fly for unfinished jobs, but didn't go that way because it could be slightly expensive to run such query. But we do that in so few places that I agree it doesn't worth to concentrate that check in a garbo job. I have changed it too.
Unmerged commits
- 2493c1d... by Thiago F. Pappacena
-
Changing exception msg when blocking info type transition for git repo
- eeeb231... by Thiago F. Pappacena
-
Renaming GitRepo information type change job
- 826cbad... by Thiago F. Pappacena
-
Encapsulating GitRepository.
pending_ change_ information_ type - 974b22a... by Thiago F. Pappacena
-
Fixing test
- 4bd7dcc... by Thiago F. Pappacena
-
Fixing configuration of info type transition job
- 76e0272... by Thiago F. Pappacena
-
Blocking at the UI information type change while it is already changing
- caf2673... by Thiago F. Pappacena
-
Adding tests for async GitRepo.
transitionToInf ormationType - 3a5e053... by Thiago F. Pappacena
-
Changing GitRepo.
transitionInfor mationType method to be async
Preview Diff
1 | diff --git a/database/schema/security.cfg b/database/schema/security.cfg | |||
2 | index 11aba6f..290e7bc 100644 | |||
3 | --- a/database/schema/security.cfg | |||
4 | +++ b/database/schema/security.cfg | |||
5 | @@ -2082,6 +2082,24 @@ public.webhookjob = SELECT, INSERT | |||
6 | 2082 | public.xref = SELECT, INSERT, DELETE | 2082 | public.xref = SELECT, INSERT, DELETE |
7 | 2083 | type=user | 2083 | type=user |
8 | 2084 | 2084 | ||
9 | 2085 | [privacy-change-jobs] | ||
10 | 2086 | groups=script | ||
11 | 2087 | public.accessartifact = SELECT, UPDATE, DELETE, INSERT | ||
12 | 2088 | public.accessartifactgrant = SELECT, UPDATE, DELETE, INSERT | ||
13 | 2089 | public.accesspolicyartifact = SELECT, UPDATE, DELETE, INSERT | ||
14 | 2090 | public.accesspolicygrant = SELECT, UPDATE, DELETE | ||
15 | 2091 | public.account = SELECT | ||
16 | 2092 | public.distribution = SELECT | ||
17 | 2093 | public.gitjob = SELECT, UPDATE | ||
18 | 2094 | public.gitrepository = SELECT, UPDATE | ||
19 | 2095 | public.gitsubscription = SELECT, UPDATE, DELETE | ||
20 | 2096 | public.job = SELECT, INSERT, UPDATE | ||
21 | 2097 | public.person = SELECT | ||
22 | 2098 | public.product = SELECT | ||
23 | 2099 | public.sharingjob = SELECT, INSERT, UPDATE | ||
24 | 2100 | public.teamparticipation = SELECT | ||
25 | 2101 | type=user | ||
26 | 2102 | |||
27 | 2085 | [sharing-jobs] | 2103 | [sharing-jobs] |
28 | 2086 | groups=script | 2104 | groups=script |
29 | 2087 | public.accessartifactgrant = SELECT, UPDATE, DELETE | 2105 | public.accessartifactgrant = SELECT, UPDATE, DELETE |
30 | diff --git a/lib/lp/app/widgets/itemswidgets.py b/lib/lp/app/widgets/itemswidgets.py | |||
31 | index 1dbb59f..a644a96 100644 | |||
32 | --- a/lib/lp/app/widgets/itemswidgets.py | |||
33 | +++ b/lib/lp/app/widgets/itemswidgets.py | |||
34 | @@ -189,25 +189,29 @@ class LaunchpadRadioWidgetWithDescription(LaunchpadRadioWidget): | |||
35 | 189 | """Render an item of the list.""" | 189 | """Render an item of the list.""" |
36 | 190 | text = html_escape(text) | 190 | text = html_escape(text) |
37 | 191 | id = '%s.%s' % (name, index) | 191 | id = '%s.%s' % (name, index) |
38 | 192 | extra_attr = {"disabled": "disabled"} if self.context.readonly else {} | ||
39 | 192 | elem = renderElement(u'input', | 193 | elem = renderElement(u'input', |
40 | 193 | value=value, | 194 | value=value, |
41 | 194 | name=name, | 195 | name=name, |
42 | 195 | id=id, | 196 | id=id, |
43 | 196 | cssClass=cssClass, | 197 | cssClass=cssClass, |
45 | 197 | type='radio') | 198 | type='radio', |
46 | 199 | **extra_attr) | ||
47 | 198 | return self._renderRow(text, value, id, elem) | 200 | return self._renderRow(text, value, id, elem) |
48 | 199 | 201 | ||
49 | 200 | def renderSelectedItem(self, index, text, value, name, cssClass): | 202 | def renderSelectedItem(self, index, text, value, name, cssClass): |
50 | 201 | """Render a selected item of the list.""" | 203 | """Render a selected item of the list.""" |
51 | 202 | text = html_escape(text) | 204 | text = html_escape(text) |
52 | 203 | id = '%s.%s' % (name, index) | 205 | id = '%s.%s' % (name, index) |
53 | 206 | extra_attr = {"disabled": "disabled"} if self.context.readonly else {} | ||
54 | 204 | elem = renderElement(u'input', | 207 | elem = renderElement(u'input', |
55 | 205 | value=value, | 208 | value=value, |
56 | 206 | name=name, | 209 | name=name, |
57 | 207 | id=id, | 210 | id=id, |
58 | 208 | cssClass=cssClass, | 211 | cssClass=cssClass, |
59 | 209 | checked="checked", | 212 | checked="checked", |
61 | 210 | type='radio') | 213 | type='radio', |
62 | 214 | **extra_attr) | ||
63 | 211 | return self._renderRow(text, value, id, elem) | 215 | return self._renderRow(text, value, id, elem) |
64 | 212 | 216 | ||
65 | 213 | def renderExtraHint(self): | 217 | def renderExtraHint(self): |
66 | diff --git a/lib/lp/code/browser/gitrepository.py b/lib/lp/code/browser/gitrepository.py | |||
67 | index 2552ffb..1ec1b16 100644 | |||
68 | --- a/lib/lp/code/browser/gitrepository.py | |||
69 | +++ b/lib/lp/code/browser/gitrepository.py | |||
70 | @@ -483,6 +483,8 @@ class GitRepositoryView(InformationTypePortletMixin, LaunchpadView, | |||
71 | 483 | def warning_message(self): | 483 | def warning_message(self): |
72 | 484 | if self.context.status == GitRepositoryStatus.CREATING: | 484 | if self.context.status == GitRepositoryStatus.CREATING: |
73 | 485 | return "This repository is being created." | 485 | return "This repository is being created." |
74 | 486 | if self.context.pending_change_information_type: | ||
75 | 487 | return "This repository's information type is being changed." | ||
76 | 486 | return None | 488 | return None |
77 | 487 | 489 | ||
78 | 488 | @property | 490 | @property |
79 | @@ -573,6 +575,7 @@ class GitRepositoryEditFormView(LaunchpadEditFormView): | |||
80 | 573 | @cachedproperty | 575 | @cachedproperty |
81 | 574 | def schema(self): | 576 | def schema(self): |
82 | 575 | info_types = self.getInformationTypesToShow() | 577 | info_types = self.getInformationTypesToShow() |
83 | 578 | read_only_info_type = self.context.pending_change_information_type | ||
84 | 576 | 579 | ||
85 | 577 | class GitRepositoryEditSchema(Interface): | 580 | class GitRepositoryEditSchema(Interface): |
86 | 578 | """Defines the fields for the edit form. | 581 | """Defines the fields for the edit form. |
87 | @@ -582,7 +585,8 @@ class GitRepositoryEditFormView(LaunchpadEditFormView): | |||
88 | 582 | """ | 585 | """ |
89 | 583 | use_template(IGitRepository, include=["default_branch"]) | 586 | use_template(IGitRepository, include=["default_branch"]) |
90 | 584 | information_type = copy_field( | 587 | information_type = copy_field( |
92 | 585 | IGitRepository["information_type"], readonly=False, | 588 | IGitRepository["information_type"], |
93 | 589 | readonly=read_only_info_type, | ||
94 | 586 | vocabulary=InformationTypeVocabulary(types=info_types)) | 590 | vocabulary=InformationTypeVocabulary(types=info_types)) |
95 | 587 | name = copy_field(IGitRepository["name"], readonly=False) | 591 | name = copy_field(IGitRepository["name"], readonly=False) |
96 | 588 | owner = copy_field(IGitRepository["owner"], readonly=False) | 592 | owner = copy_field(IGitRepository["owner"], readonly=False) |
97 | @@ -785,6 +789,10 @@ class GitRepositoryEditView(CodeEditOwnerMixin, GitRepositoryEditFormView): | |||
98 | 785 | self.widgets["target"].hint = ( | 789 | self.widgets["target"].hint = ( |
99 | 786 | "This is the default repository for this target, so it " | 790 | "This is the default repository for this target, so it " |
100 | 787 | "cannot be moved to another target.") | 791 | "cannot be moved to another target.") |
101 | 792 | if self.context.pending_change_information_type: | ||
102 | 793 | self.widgets["information_type"].hint = ( | ||
103 | 794 | "The information type is being changed. The operation needs " | ||
104 | 795 | "to finish before you can change it again.") | ||
105 | 788 | if self.context.default_branch: | 796 | if self.context.default_branch: |
106 | 789 | self.widgets['default_branch'].context.required = True | 797 | self.widgets['default_branch'].context.required = True |
107 | 790 | 798 | ||
108 | diff --git a/lib/lp/code/browser/tests/test_branchmergeproposal.py b/lib/lp/code/browser/tests/test_branchmergeproposal.py | |||
109 | index f2ec4ec..83c51dc 100644 | |||
110 | --- a/lib/lp/code/browser/tests/test_branchmergeproposal.py | |||
111 | +++ b/lib/lp/code/browser/tests/test_branchmergeproposal.py | |||
112 | @@ -2336,7 +2336,7 @@ class TestLatestProposalsForEachBranchGit( | |||
113 | 2336 | 2336 | ||
114 | 2337 | @staticmethod | 2337 | @staticmethod |
115 | 2338 | def _setBranchInvisible(branch): | 2338 | def _setBranchInvisible(branch): |
117 | 2339 | removeSecurityProxy(branch.repository).transitionToInformationType( | 2339 | removeSecurityProxy(branch.repository)._transitionToInformationType( |
118 | 2340 | InformationType.USERDATA, branch.owner, verify_policy=False) | 2340 | InformationType.USERDATA, branch.owner, verify_policy=False) |
119 | 2341 | 2341 | ||
120 | 2342 | 2342 | ||
121 | diff --git a/lib/lp/code/browser/tests/test_gitrepository.py b/lib/lp/code/browser/tests/test_gitrepository.py | |||
122 | index 262d038..13a3a16 100644 | |||
123 | --- a/lib/lp/code/browser/tests/test_gitrepository.py | |||
124 | +++ b/lib/lp/code/browser/tests/test_gitrepository.py | |||
125 | @@ -60,6 +60,9 @@ from lp.code.enums import ( | |||
126 | 60 | GitRepositoryType, | 60 | GitRepositoryType, |
127 | 61 | ) | 61 | ) |
128 | 62 | from lp.code.interfaces.gitcollection import IGitCollection | 62 | from lp.code.interfaces.gitcollection import IGitCollection |
129 | 63 | from lp.code.interfaces.gitjob import ( | ||
130 | 64 | IGitRepositoryChangeInformationTypeJobSource, | ||
131 | 65 | ) | ||
132 | 63 | from lp.code.interfaces.gitrepository import IGitRepositorySet | 66 | from lp.code.interfaces.gitrepository import IGitRepositorySet |
133 | 64 | from lp.code.interfaces.revision import IRevisionSet | 67 | from lp.code.interfaces.revision import IRevisionSet |
134 | 65 | from lp.code.model.gitjob import GitRefScanJob | 68 | from lp.code.model.gitjob import GitRefScanJob |
135 | @@ -161,6 +164,15 @@ class TestGitRepositoryView(BrowserTestCase): | |||
136 | 161 | self.assertTextMatchesExpressionIgnoreWhitespace( | 164 | self.assertTextMatchesExpressionIgnoreWhitespace( |
137 | 162 | r"""This repository is being created\..*""", text) | 165 | r"""This repository is being created\..*""", text) |
138 | 163 | 166 | ||
139 | 167 | def test_changing_info_type_warning_message_is_present(self): | ||
140 | 168 | repository = removeSecurityProxy(self.factory.makeGitRepository()) | ||
141 | 169 | repository.transitionToInformationType( | ||
142 | 170 | InformationType.PRIVATESECURITY, repository.owner) | ||
143 | 171 | text = self.getMainText(repository, "+index", user=repository.owner) | ||
144 | 172 | self.assertTextMatchesExpressionIgnoreWhitespace( | ||
145 | 173 | r"""This repository's information type is being changed\..*""", | ||
146 | 174 | text) | ||
147 | 175 | |||
148 | 164 | def test_creating_warning_message_is_not_shown(self): | 176 | def test_creating_warning_message_is_not_shown(self): |
149 | 165 | repository = removeSecurityProxy(self.factory.makeGitRepository()) | 177 | repository = removeSecurityProxy(self.factory.makeGitRepository()) |
150 | 166 | repository.status = GitRepositoryStatus.AVAILABLE | 178 | repository.status = GitRepositoryStatus.AVAILABLE |
151 | @@ -1185,8 +1197,54 @@ class TestGitRepositoryEditView(TestCaseWithFactory): | |||
152 | 1185 | browser.getControl("Private", index=1).click() | 1197 | browser.getControl("Private", index=1).click() |
153 | 1186 | browser.getControl("Change Git Repository").click() | 1198 | browser.getControl("Change Git Repository").click() |
154 | 1187 | with person_logged_in(person): | 1199 | with person_logged_in(person): |
157 | 1188 | self.assertEqual( | 1200 | self.assertTrue(repository.pending_change_information_type) |
158 | 1189 | InformationType.USERDATA, repository.information_type) | 1201 | job_util = getUtility( |
159 | 1202 | IGitRepositoryChangeInformationTypeJobSource) | ||
160 | 1203 | jobs = list(job_util.iterReady()) | ||
161 | 1204 | self.assertEqual(1, len(jobs)) | ||
162 | 1205 | job = removeSecurityProxy(jobs[0]) | ||
163 | 1206 | self.assertEqual(repository, job.repository) | ||
164 | 1207 | self.assertEqual(InformationType.USERDATA, job.information_type) | ||
165 | 1208 | self.assertEqual(admin, job.user) | ||
166 | 1209 | |||
167 | 1210 | def test_information_type_in_ui_blocked_if_already_changing(self): | ||
168 | 1211 | # The information_type of a repository can't be changed via the UI | ||
169 | 1212 | # if the repository is already pending a info type change. | ||
170 | 1213 | person = self.factory.makePerson() | ||
171 | 1214 | repository = self.factory.makeGitRepository(owner=person) | ||
172 | 1215 | with admin_logged_in(): | ||
173 | 1216 | repository.transitionToInformationType( | ||
174 | 1217 | InformationType.PRIVATESECURITY, repository.owner) | ||
175 | 1218 | # There should be 1 job pending to be executed. | ||
176 | 1219 | job_util = getUtility( | ||
177 | 1220 | IGitRepositoryChangeInformationTypeJobSource) | ||
178 | 1221 | jobs = list(job_util.iterReady()) | ||
179 | 1222 | self.assertEqual(1, len(jobs)) | ||
180 | 1223 | admin = getUtility(ILaunchpadCelebrities).admin.teamowner | ||
181 | 1224 | browser = self.getUserBrowser( | ||
182 | 1225 | canonical_url(repository) + "/+edit", user=admin) | ||
183 | 1226 | # Make sure the privacy controls are all disabled in the UI. | ||
184 | 1227 | controls = [ | ||
185 | 1228 | "Public", "Public Security", "Private Security", "Private", | ||
186 | 1229 | "Proprietary", "Embargoed"] | ||
187 | 1230 | self.assertTrue( | ||
188 | 1231 | all(browser.getControl(i, index=0).disabled for i in controls)) | ||
189 | 1232 | expected_msg = ( | ||
190 | 1233 | "The information type is being changed. The operation needs to " | ||
191 | 1234 | "finish before you can change it again.") | ||
192 | 1235 | self.assertIn(expected_msg, extract_text(browser.contents)) | ||
193 | 1236 | |||
194 | 1237 | # Trying to change should have no effect in the backend, since the | ||
195 | 1238 | # repository is already changing info type and this field is read-only. | ||
196 | 1239 | browser.getControl("Private", index=1).click() | ||
197 | 1240 | browser.getControl("Change Git Repository").click() | ||
198 | 1241 | with person_logged_in(person): | ||
199 | 1242 | self.assertTrue(repository.pending_change_information_type) | ||
200 | 1243 | # It should have the same job it already had. | ||
201 | 1244 | job_util = getUtility( | ||
202 | 1245 | IGitRepositoryChangeInformationTypeJobSource) | ||
203 | 1246 | new_jobs = list(job_util.iterReady()) | ||
204 | 1247 | self.assertEqual(jobs, new_jobs) | ||
205 | 1190 | 1248 | ||
206 | 1191 | def test_edit_view_ajax_render(self): | 1249 | def test_edit_view_ajax_render(self): |
207 | 1192 | # An information type change request is processed as expected when | 1250 | # An information type change request is processed as expected when |
208 | @@ -1208,8 +1266,16 @@ class TestGitRepositoryEditView(TestCaseWithFactory): | |||
209 | 1208 | person, repository.target, repository, view] | 1266 | person, repository.target, repository, view] |
210 | 1209 | result = view.render() | 1267 | result = view.render() |
211 | 1210 | self.assertEqual("", result) | 1268 | self.assertEqual("", result) |
212 | 1269 | self.assertTrue(repository.pending_change_information_type) | ||
213 | 1270 | job_util = getUtility( | ||
214 | 1271 | IGitRepositoryChangeInformationTypeJobSource) | ||
215 | 1272 | jobs = list(job_util.iterReady()) | ||
216 | 1273 | self.assertEqual(1, len(jobs)) | ||
217 | 1274 | job = removeSecurityProxy(jobs[0]) | ||
218 | 1275 | self.assertEqual(repository, job.repository) | ||
219 | 1211 | self.assertEqual( | 1276 | self.assertEqual( |
221 | 1212 | repository.information_type, InformationType.PUBLICSECURITY) | 1277 | InformationType.PUBLICSECURITY, job.information_type) |
222 | 1278 | self.assertEqual(person, job.user) | ||
223 | 1213 | 1279 | ||
224 | 1214 | def test_change_default_branch(self): | 1280 | def test_change_default_branch(self): |
225 | 1215 | # An authorised user can change the default branch to one that | 1281 | # An authorised user can change the default branch to one that |
226 | diff --git a/lib/lp/code/configure.zcml b/lib/lp/code/configure.zcml | |||
227 | index 898e645..3b03c7d 100644 | |||
228 | --- a/lib/lp/code/configure.zcml | |||
229 | +++ b/lib/lp/code/configure.zcml | |||
230 | @@ -1111,6 +1111,11 @@ | |||
231 | 1111 | provides="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJobSource"> | 1111 | provides="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJobSource"> |
232 | 1112 | <allow interface="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJobSource" /> | 1112 | <allow interface="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJobSource" /> |
233 | 1113 | </securedutility> | 1113 | </securedutility> |
234 | 1114 | <securedutility | ||
235 | 1115 | component="lp.code.model.gitjob.GitRepositoryChangeInformationTypeJob" | ||
236 | 1116 | provides="lp.code.interfaces.gitjob.IGitRepositoryChangeInformationTypeJobSource"> | ||
237 | 1117 | <allow interface="lp.code.interfaces.gitjob.IGitRepositoryChangeInformationTypeJobSource" /> | ||
238 | 1118 | </securedutility> | ||
239 | 1114 | <class class="lp.code.model.gitjob.GitRefScanJob"> | 1119 | <class class="lp.code.model.gitjob.GitRefScanJob"> |
240 | 1115 | <allow interface="lp.code.interfaces.gitjob.IGitJob" /> | 1120 | <allow interface="lp.code.interfaces.gitjob.IGitJob" /> |
241 | 1116 | <allow interface="lp.code.interfaces.gitjob.IGitRefScanJob" /> | 1121 | <allow interface="lp.code.interfaces.gitjob.IGitRefScanJob" /> |
242 | @@ -1123,6 +1128,10 @@ | |||
243 | 1123 | <allow interface="lp.code.interfaces.gitjob.IGitJob" /> | 1128 | <allow interface="lp.code.interfaces.gitjob.IGitJob" /> |
244 | 1124 | <allow interface="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJob" /> | 1129 | <allow interface="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJob" /> |
245 | 1125 | </class> | 1130 | </class> |
246 | 1131 | <class class="lp.code.model.gitjob.GitRepositoryChangeInformationTypeJob"> | ||
247 | 1132 | <allow interface="lp.code.interfaces.gitjob.IGitJob" /> | ||
248 | 1133 | <allow interface="lp.code.interfaces.gitjob.IGitRepositoryChangeInformationTypeJob" /> | ||
249 | 1134 | </class> | ||
250 | 1126 | 1135 | ||
251 | 1127 | <lp:help-folder folder="help" name="+help-code" /> | 1136 | <lp:help-folder folder="help" name="+help-code" /> |
252 | 1128 | 1137 | ||
253 | diff --git a/lib/lp/code/interfaces/gitjob.py b/lib/lp/code/interfaces/gitjob.py | |||
254 | index 4f31b19..4bbca04 100644 | |||
255 | --- a/lib/lp/code/interfaces/gitjob.py | |||
256 | +++ b/lib/lp/code/interfaces/gitjob.py | |||
257 | @@ -1,4 +1,4 @@ | |||
259 | 1 | # Copyright 2015 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2015-2020 Canonical Ltd. This software is licensed under the |
260 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
261 | 3 | 3 | ||
262 | 4 | """GitJob interfaces.""" | 4 | """GitJob interfaces.""" |
263 | @@ -11,6 +11,8 @@ __all__ = [ | |||
264 | 11 | 'IGitRefScanJobSource', | 11 | 'IGitRefScanJobSource', |
265 | 12 | 'IGitRepositoryModifiedMailJob', | 12 | 'IGitRepositoryModifiedMailJob', |
266 | 13 | 'IGitRepositoryModifiedMailJobSource', | 13 | 'IGitRepositoryModifiedMailJobSource', |
267 | 14 | 'IGitRepositoryChangeInformationTypeJob', | ||
268 | 15 | 'IGitRepositoryChangeInformationTypeJobSource', | ||
269 | 14 | 'IReclaimGitRepositorySpaceJob', | 16 | 'IReclaimGitRepositorySpaceJob', |
270 | 15 | 'IReclaimGitRepositorySpaceJobSource', | 17 | 'IReclaimGitRepositorySpaceJobSource', |
271 | 16 | ] | 18 | ] |
272 | @@ -93,3 +95,20 @@ class IGitRepositoryModifiedMailJobSource(IJobSource): | |||
273 | 93 | :param repository_delta: An `IGitRepositoryDelta` describing the | 95 | :param repository_delta: An `IGitRepositoryDelta` describing the |
274 | 94 | changes. | 96 | changes. |
275 | 95 | """ | 97 | """ |
276 | 98 | |||
277 | 99 | |||
278 | 100 | class IGitRepositoryChangeInformationTypeJob(IRunnableJob): | ||
279 | 101 | """A Job to change repository's information type.""" | ||
280 | 102 | |||
281 | 103 | |||
282 | 104 | class IGitRepositoryChangeInformationTypeJobSource(IJobSource): | ||
283 | 105 | |||
284 | 106 | def create(repository, user, information_type, verify_policy=True): | ||
285 | 107 | """Create a job to change git repository's information type. | ||
286 | 108 | |||
287 | 109 | :param repository: The `IGitRepository` that was modified. | ||
288 | 110 | :param information_type: The `InformationType` to transition to. | ||
289 | 111 | :param user: The `IPerson` who is making the change. | ||
290 | 112 | :param verify_policy: Check if the new information type complies | ||
291 | 113 | with the `IGitNamespacePolicy`. | ||
292 | 114 | """ | ||
293 | diff --git a/lib/lp/code/interfaces/gitrepository.py b/lib/lp/code/interfaces/gitrepository.py | |||
294 | index 192133a..dac0e8f 100644 | |||
295 | --- a/lib/lp/code/interfaces/gitrepository.py | |||
296 | +++ b/lib/lp/code/interfaces/gitrepository.py | |||
297 | @@ -582,6 +582,10 @@ class IGitRepositoryView(IHasRecipes): | |||
298 | 582 | "Whether there are recent changes in this repository that have not " | 582 | "Whether there are recent changes in this repository that have not " |
299 | 583 | "yet been scanned.") | 583 | "yet been scanned.") |
300 | 584 | 584 | ||
301 | 585 | pending_change_information_type = Attribute( | ||
302 | 586 | "Whether there is a pending request to change the information type " | ||
303 | 587 | "of this repository that have not been processed yet.") | ||
304 | 588 | |||
305 | 585 | def updateMergeCommitIDs(paths): | 589 | def updateMergeCommitIDs(paths): |
306 | 586 | """Update commit SHA1s of merge proposals for this repository. | 590 | """Update commit SHA1s of merge proposals for this repository. |
307 | 587 | 591 | ||
308 | diff --git a/lib/lp/code/model/gitjob.py b/lib/lp/code/model/gitjob.py | |||
309 | index 1eb87da..f1e3d2a 100644 | |||
310 | --- a/lib/lp/code/model/gitjob.py | |||
311 | +++ b/lib/lp/code/model/gitjob.py | |||
312 | @@ -33,10 +33,12 @@ from zope.interface import ( | |||
313 | 33 | provider, | 33 | provider, |
314 | 34 | ) | 34 | ) |
315 | 35 | 35 | ||
316 | 36 | from lp.app.enums import InformationType | ||
317 | 36 | from lp.app.errors import NotFoundError | 37 | from lp.app.errors import NotFoundError |
318 | 37 | from lp.code.enums import ( | 38 | from lp.code.enums import ( |
319 | 38 | GitActivityType, | 39 | GitActivityType, |
320 | 39 | GitPermissionType, | 40 | GitPermissionType, |
321 | 41 | GitRepositoryStatus, | ||
322 | 40 | ) | 42 | ) |
323 | 41 | from lp.code.interfaces.githosting import IGitHostingClient | 43 | from lp.code.interfaces.githosting import IGitHostingClient |
324 | 42 | from lp.code.interfaces.gitjob import ( | 44 | from lp.code.interfaces.gitjob import ( |
325 | @@ -45,6 +47,8 @@ from lp.code.interfaces.gitjob import ( | |||
326 | 45 | IGitRefScanJobSource, | 47 | IGitRefScanJobSource, |
327 | 46 | IGitRepositoryModifiedMailJob, | 48 | IGitRepositoryModifiedMailJob, |
328 | 47 | IGitRepositoryModifiedMailJobSource, | 49 | IGitRepositoryModifiedMailJobSource, |
329 | 50 | IGitRepositoryChangeInformationTypeJob, | ||
330 | 51 | IGitRepositoryChangeInformationTypeJobSource, | ||
331 | 48 | IReclaimGitRepositorySpaceJob, | 52 | IReclaimGitRepositorySpaceJob, |
332 | 49 | IReclaimGitRepositorySpaceJobSource, | 53 | IReclaimGitRepositorySpaceJobSource, |
333 | 50 | ) | 54 | ) |
334 | @@ -100,6 +104,13 @@ class GitJobType(DBEnumeratedType): | |||
335 | 100 | modifications. | 104 | modifications. |
336 | 101 | """) | 105 | """) |
337 | 102 | 106 | ||
338 | 107 | REPOSITORY_TRANSITION_TO_INFO_TYPE = DBItem(3, """ | ||
339 | 108 | Change repository's information type | ||
340 | 109 | |||
341 | 110 | This job runs when a user requests to change privacy settings of a | ||
342 | 111 | repository. | ||
343 | 112 | """) | ||
344 | 113 | |||
345 | 103 | 114 | ||
346 | 104 | @implementer(IGitJob) | 115 | @implementer(IGitJob) |
347 | 105 | class GitJob(StormBase): | 116 | class GitJob(StormBase): |
348 | @@ -393,3 +404,49 @@ class GitRepositoryModifiedMailJob(GitJobDerived): | |||
349 | 393 | def run(self): | 404 | def run(self): |
350 | 394 | """See `IGitRepositoryModifiedMailJob`.""" | 405 | """See `IGitRepositoryModifiedMailJob`.""" |
351 | 395 | self.getMailer().sendAll() | 406 | self.getMailer().sendAll() |
352 | 407 | |||
353 | 408 | |||
354 | 409 | @implementer(IGitRepositoryChangeInformationTypeJob) | ||
355 | 410 | @provider(IGitRepositoryChangeInformationTypeJobSource) | ||
356 | 411 | class GitRepositoryChangeInformationTypeJob(GitJobDerived): | ||
357 | 412 | """A Job to change git repository's information type.""" | ||
358 | 413 | |||
359 | 414 | class_job_type = GitJobType.REPOSITORY_TRANSITION_TO_INFO_TYPE | ||
360 | 415 | |||
361 | 416 | config = config.IGitRepositoryChangeInformationTypeJobSource | ||
362 | 417 | |||
363 | 418 | @classmethod | ||
364 | 419 | def create(cls, repository, information_type, user, verify_policy=True): | ||
365 | 420 | """See `IGitRepositoryChangeInformationTypeJobSource`.""" | ||
366 | 421 | metadata = { | ||
367 | 422 | "user": user.id, | ||
368 | 423 | "information_type": information_type.value, | ||
369 | 424 | "verify_policy": verify_policy, | ||
370 | 425 | } | ||
371 | 426 | git_job = GitJob(repository, cls.class_job_type, metadata) | ||
372 | 427 | job = cls(git_job) | ||
373 | 428 | job.celeryRunOnCommit() | ||
374 | 429 | return job | ||
375 | 430 | |||
376 | 431 | @property | ||
377 | 432 | def user(self): | ||
378 | 433 | return getUtility(IPersonSet).get(self.metadata["user"]) | ||
379 | 434 | |||
380 | 435 | @property | ||
381 | 436 | def verify_policy(self): | ||
382 | 437 | return self.metadata["verify_policy"] | ||
383 | 438 | |||
384 | 439 | @property | ||
385 | 440 | def information_type(self): | ||
386 | 441 | return InformationType.items[self.metadata["information_type"]] | ||
387 | 442 | |||
388 | 443 | def run(self): | ||
389 | 444 | """See `IGitRepositoryChangeInformationTypeJob`.""" | ||
390 | 445 | if not self.repository.pending_change_information_type: | ||
391 | 446 | raise AttributeError( | ||
392 | 447 | "The repository %s is not pending information type change." % | ||
393 | 448 | self.repository) | ||
394 | 449 | |||
395 | 450 | self.repository._transitionToInformationType( | ||
396 | 451 | self.information_type, self.user, self.verify_policy) | ||
397 | 452 | self.repository.status = GitRepositoryStatus.AVAILABLE | ||
398 | diff --git a/lib/lp/code/model/gitref.py b/lib/lp/code/model/gitref.py | |||
399 | index f7dc142..e2f788d 100644 | |||
400 | --- a/lib/lp/code/model/gitref.py | |||
401 | +++ b/lib/lp/code/model/gitref.py | |||
402 | @@ -180,7 +180,7 @@ class GitRefMixin: | |||
403 | 180 | 180 | ||
404 | 181 | def transitionToInformationType(self, information_type, user, | 181 | def transitionToInformationType(self, information_type, user, |
405 | 182 | verify_policy=True): | 182 | verify_policy=True): |
407 | 183 | return self.repository.transitionToInformationType( | 183 | return self.repository._transitionToInformationType( |
408 | 184 | information_type, user, verify_policy=verify_policy) | 184 | information_type, user, verify_policy=verify_policy) |
409 | 185 | 185 | ||
410 | 186 | @property | 186 | @property |
411 | diff --git a/lib/lp/code/model/gitrepository.py b/lib/lp/code/model/gitrepository.py | |||
412 | index fb5e703..d5bd382 100644 | |||
413 | --- a/lib/lp/code/model/gitrepository.py | |||
414 | +++ b/lib/lp/code/model/gitrepository.py | |||
415 | @@ -117,7 +117,10 @@ from lp.code.interfaces.gitcollection import ( | |||
416 | 117 | IGitCollection, | 117 | IGitCollection, |
417 | 118 | ) | 118 | ) |
418 | 119 | from lp.code.interfaces.githosting import IGitHostingClient | 119 | from lp.code.interfaces.githosting import IGitHostingClient |
420 | 120 | from lp.code.interfaces.gitjob import IGitRefScanJobSource | 120 | from lp.code.interfaces.gitjob import ( |
421 | 121 | IGitRefScanJobSource, | ||
422 | 122 | IGitRepositoryChangeInformationTypeJobSource, | ||
423 | 123 | ) | ||
424 | 121 | from lp.code.interfaces.gitlookup import IGitLookup | 124 | from lp.code.interfaces.gitlookup import IGitLookup |
425 | 122 | from lp.code.interfaces.gitnamespace import ( | 125 | from lp.code.interfaces.gitnamespace import ( |
426 | 123 | get_git_namespace, | 126 | get_git_namespace, |
427 | @@ -882,6 +885,28 @@ class GitRepository(StormBase, WebhookTargetMixin, GitIdentityMixin): | |||
428 | 882 | def transitionToInformationType(self, information_type, user, | 885 | def transitionToInformationType(self, information_type, user, |
429 | 883 | verify_policy=True): | 886 | verify_policy=True): |
430 | 884 | """See `IGitRepository`.""" | 887 | """See `IGitRepository`.""" |
431 | 888 | error_msg = None | ||
432 | 889 | if self.status != GitRepositoryStatus.AVAILABLE: | ||
433 | 890 | error_msg = ("Cannot change privacy settings while git " | ||
434 | 891 | "repository is being created.") | ||
435 | 892 | elif self.pending_change_information_type: | ||
436 | 893 | error_msg = ("Cannot change privacy settings while git repository " | ||
437 | 894 | "is pending another information type change.") | ||
438 | 895 | if error_msg is not None: | ||
439 | 896 | raise CannotChangeInformationType(error_msg) | ||
440 | 897 | util = getUtility( | ||
441 | 898 | IGitRepositoryChangeInformationTypeJobSource) | ||
442 | 899 | return util.create(self, information_type, user, verify_policy) | ||
443 | 900 | |||
444 | 901 | def _transitionToInformationType(self, information_type, user, | ||
445 | 902 | verify_policy=True): | ||
446 | 903 | """Synchronously make the change in this repository's information | ||
447 | 904 | type. | ||
448 | 905 | |||
449 | 906 | External callers should use the async, public version of this | ||
450 | 907 | method, since it deals with the side effects of changing | ||
451 | 908 | repository's privacy changes. | ||
452 | 909 | """ | ||
453 | 885 | if self.information_type == information_type: | 910 | if self.information_type == information_type: |
454 | 886 | return | 911 | return |
455 | 887 | if (verify_policy and | 912 | if (verify_policy and |
456 | @@ -1120,21 +1145,29 @@ class GitRepository(StormBase, WebhookTargetMixin, GitIdentityMixin): | |||
457 | 1120 | """See `IGitRepository`.""" | 1145 | """See `IGitRepository`.""" |
458 | 1121 | return self.namespace.areRepositoriesMergeable(self, other) | 1146 | return self.namespace.areRepositoriesMergeable(self, other) |
459 | 1122 | 1147 | ||
467 | 1123 | @property | 1148 | def hasPendingJob(self, job_type): |
468 | 1124 | def pending_updates(self): | 1149 | from lp.code.model.gitjob import GitJob |
462 | 1125 | """See `IGitRepository`.""" | ||
463 | 1126 | from lp.code.model.gitjob import ( | ||
464 | 1127 | GitJob, | ||
465 | 1128 | GitJobType, | ||
466 | 1129 | ) | ||
469 | 1130 | jobs = Store.of(self).find( | 1150 | jobs = Store.of(self).find( |
470 | 1131 | GitJob, | 1151 | GitJob, |
471 | 1132 | GitJob.repository == self, | 1152 | GitJob.repository == self, |
473 | 1133 | GitJob.job_type == GitJobType.REF_SCAN, | 1153 | GitJob.job_type == job_type, |
474 | 1134 | GitJob.job == Job.id, | 1154 | GitJob.job == Job.id, |
475 | 1135 | Job._status.is_in([JobStatus.WAITING, JobStatus.RUNNING])) | 1155 | Job._status.is_in([JobStatus.WAITING, JobStatus.RUNNING])) |
476 | 1136 | return not jobs.is_empty() | 1156 | return not jobs.is_empty() |
477 | 1137 | 1157 | ||
478 | 1158 | @property | ||
479 | 1159 | def pending_updates(self): | ||
480 | 1160 | """See `IGitRepository`.""" | ||
481 | 1161 | from lp.code.model.gitjob import GitJobType | ||
482 | 1162 | return self.hasPendingJob(GitJobType.REF_SCAN) | ||
483 | 1163 | |||
484 | 1164 | @property | ||
485 | 1165 | def pending_change_information_type(self): | ||
486 | 1166 | """See `IGitRepository`.""" | ||
487 | 1167 | from lp.code.model.gitjob import GitJobType | ||
488 | 1168 | return self.hasPendingJob( | ||
489 | 1169 | GitJobType.REPOSITORY_TRANSITION_TO_INFO_TYPE) | ||
490 | 1170 | |||
491 | 1138 | def updateMergeCommitIDs(self, paths): | 1171 | def updateMergeCommitIDs(self, paths): |
492 | 1139 | """See `IGitRepository`.""" | 1172 | """See `IGitRepository`.""" |
493 | 1140 | store = Store.of(self) | 1173 | store = Store.of(self) |
494 | diff --git a/lib/lp/code/model/tests/test_gitcollection.py b/lib/lp/code/model/tests/test_gitcollection.py | |||
495 | index 447fcb4..372a633 100644 | |||
496 | --- a/lib/lp/code/model/tests/test_gitcollection.py | |||
497 | +++ b/lib/lp/code/model/tests/test_gitcollection.py | |||
498 | @@ -599,7 +599,7 @@ class TestBranchMergeProposals(TestCaseWithFactory): | |||
499 | 599 | registrant = self.factory.makePerson() | 599 | registrant = self.factory.makePerson() |
500 | 600 | mp1 = self.factory.makeBranchMergeProposalForGit(registrant=registrant) | 600 | mp1 = self.factory.makeBranchMergeProposalForGit(registrant=registrant) |
501 | 601 | naked_repository = removeSecurityProxy(mp1.target_git_repository) | 601 | naked_repository = removeSecurityProxy(mp1.target_git_repository) |
503 | 602 | naked_repository.transitionToInformationType( | 602 | naked_repository._transitionToInformationType( |
504 | 603 | InformationType.USERDATA, registrant, verify_policy=False) | 603 | InformationType.USERDATA, registrant, verify_policy=False) |
505 | 604 | collection = self.all_repositories.visibleByUser(None) | 604 | collection = self.all_repositories.visibleByUser(None) |
506 | 605 | proposals = collection.getMergeProposals() | 605 | proposals = collection.getMergeProposals() |
507 | diff --git a/lib/lp/code/model/tests/test_gitjob.py b/lib/lp/code/model/tests/test_gitjob.py | |||
508 | index 3f606c0..b4487b9 100644 | |||
509 | --- a/lib/lp/code/model/tests/test_gitjob.py | |||
510 | +++ b/lib/lp/code/model/tests/test_gitjob.py | |||
511 | @@ -25,13 +25,16 @@ from testtools.matchers import ( | |||
512 | 25 | MatchesStructure, | 25 | MatchesStructure, |
513 | 26 | ) | 26 | ) |
514 | 27 | import transaction | 27 | import transaction |
515 | 28 | from zope.component import getUtility | ||
516 | 28 | from zope.interface import providedBy | 29 | from zope.interface import providedBy |
517 | 29 | from zope.security.proxy import removeSecurityProxy | 30 | from zope.security.proxy import removeSecurityProxy |
518 | 30 | 31 | ||
519 | 32 | from lp.app.enums import InformationType | ||
520 | 31 | from lp.code.adapters.gitrepository import GitRepositoryDelta | 33 | from lp.code.adapters.gitrepository import GitRepositoryDelta |
521 | 32 | from lp.code.enums import ( | 34 | from lp.code.enums import ( |
522 | 33 | GitGranteeType, | 35 | GitGranteeType, |
523 | 34 | GitObjectType, | 36 | GitObjectType, |
524 | 37 | GitRepositoryStatus, | ||
525 | 35 | ) | 38 | ) |
526 | 36 | from lp.code.interfaces.branchmergeproposal import ( | 39 | from lp.code.interfaces.branchmergeproposal import ( |
527 | 37 | BRANCH_MERGE_PROPOSAL_WEBHOOKS_FEATURE_FLAG, | 40 | BRANCH_MERGE_PROPOSAL_WEBHOOKS_FEATURE_FLAG, |
528 | @@ -39,6 +42,7 @@ from lp.code.interfaces.branchmergeproposal import ( | |||
529 | 39 | from lp.code.interfaces.gitjob import ( | 42 | from lp.code.interfaces.gitjob import ( |
530 | 40 | IGitJob, | 43 | IGitJob, |
531 | 41 | IGitRefScanJob, | 44 | IGitRefScanJob, |
532 | 45 | IGitRepositoryChangeInformationTypeJobSource, | ||
533 | 42 | IReclaimGitRepositorySpaceJob, | 46 | IReclaimGitRepositorySpaceJob, |
534 | 43 | ) | 47 | ) |
535 | 44 | from lp.code.model.gitjob import ( | 48 | from lp.code.model.gitjob import ( |
536 | @@ -47,9 +51,11 @@ from lp.code.model.gitjob import ( | |||
537 | 47 | GitJobDerived, | 51 | GitJobDerived, |
538 | 48 | GitJobType, | 52 | GitJobType, |
539 | 49 | GitRefScanJob, | 53 | GitRefScanJob, |
540 | 54 | GitRepositoryChangeInformationTypeJob, | ||
541 | 50 | ReclaimGitRepositorySpaceJob, | 55 | ReclaimGitRepositorySpaceJob, |
542 | 51 | ) | 56 | ) |
543 | 52 | from lp.code.tests.helpers import GitHostingFixture | 57 | from lp.code.tests.helpers import GitHostingFixture |
544 | 58 | from lp.registry.errors import CannotChangeInformationType | ||
545 | 53 | from lp.services.config import config | 59 | from lp.services.config import config |
546 | 54 | from lp.services.database.constants import UTC_NOW | 60 | from lp.services.database.constants import UTC_NOW |
547 | 55 | from lp.services.features.testing import FeatureFixture | 61 | from lp.services.features.testing import FeatureFixture |
548 | @@ -362,6 +368,59 @@ class TestReclaimGitRepositorySpaceJob(TestCaseWithFactory): | |||
549 | 362 | self.assertEqual([(path,)], hosting_fixture.delete.extract_args()) | 368 | self.assertEqual([(path,)], hosting_fixture.delete.extract_args()) |
550 | 363 | 369 | ||
551 | 364 | 370 | ||
552 | 371 | class TestGitRepositoryTransitionInformationType(TestCaseWithFactory): | ||
553 | 372 | |||
554 | 373 | layer = ZopelessDatabaseLayer | ||
555 | 374 | |||
556 | 375 | def test_block_multiple_requests_to_change_info_type(self): | ||
557 | 376 | repo = self.factory.makeGitRepository() | ||
558 | 377 | repo.transitionToInformationType( | ||
559 | 378 | InformationType.PRIVATESECURITY, repo.owner) | ||
560 | 379 | self.assertTrue(repo.pending_change_information_type) | ||
561 | 380 | expected_msg = ( | ||
562 | 381 | "Cannot change privacy settings while git repository is " | ||
563 | 382 | "pending another information type change.") | ||
564 | 383 | self.assertRaisesRegex( | ||
565 | 384 | CannotChangeInformationType, expected_msg, | ||
566 | 385 | repo.transitionToInformationType, | ||
567 | 386 | InformationType.PROPRIETARY, repo.owner) | ||
568 | 387 | |||
569 | 388 | def test_avoid_transitioning_while_creating(self): | ||
570 | 389 | repo = self.factory.makeGitRepository() | ||
571 | 390 | removeSecurityProxy(repo).status = GitRepositoryStatus.CREATING | ||
572 | 391 | expected_msg = ( | ||
573 | 392 | "Cannot change privacy settings while git repository is " | ||
574 | 393 | "being created.") | ||
575 | 394 | self.assertRaisesRegex( | ||
576 | 395 | CannotChangeInformationType, expected_msg, | ||
577 | 396 | repo.transitionToInformationType, | ||
578 | 397 | InformationType.PROPRIETARY, repo.owner) | ||
579 | 398 | |||
580 | 399 | def test_run_changes_info_type(self): | ||
581 | 400 | repo = self.factory.makeGitRepository( | ||
582 | 401 | information_type=InformationType.PUBLIC) | ||
583 | 402 | # Change to a private info type and with verify_policy, so we hit as | ||
584 | 403 | # many database tables as possible. | ||
585 | 404 | repo.transitionToInformationType( | ||
586 | 405 | InformationType.PRIVATESECURITY, repo.owner, verify_policy=True) | ||
587 | 406 | self.assertTrue(repo.pending_change_information_type) | ||
588 | 407 | |||
589 | 408 | job_util = getUtility( | ||
590 | 409 | IGitRepositoryChangeInformationTypeJobSource) | ||
591 | 410 | jobs = list(job_util.iterReady()) | ||
592 | 411 | self.assertEqual(1, len(jobs)) | ||
593 | 412 | with dbuser(GitRepositoryChangeInformationTypeJob.config.dbuser): | ||
594 | 413 | JobRunner(jobs).runAll() | ||
595 | 414 | |||
596 | 415 | self.assertEqual(GitRepositoryStatus.AVAILABLE, repo.status) | ||
597 | 416 | self.assertEqual( | ||
598 | 417 | InformationType.PRIVATESECURITY, repo.information_type) | ||
599 | 418 | |||
600 | 419 | # After the job finished, another change is possible. | ||
601 | 420 | repo.transitionToInformationType(InformationType.PUBLIC, repo.owner) | ||
602 | 421 | self.assertTrue(repo.pending_change_information_type) | ||
603 | 422 | |||
604 | 423 | |||
605 | 365 | class TestDescribeRepositoryDelta(TestCaseWithFactory): | 424 | class TestDescribeRepositoryDelta(TestCaseWithFactory): |
606 | 366 | """Tests for `describe_repository_delta`.""" | 425 | """Tests for `describe_repository_delta`.""" |
607 | 367 | 426 | ||
608 | diff --git a/lib/lp/code/model/tests/test_gitrepository.py b/lib/lp/code/model/tests/test_gitrepository.py | |||
609 | index b83f9da..f0ba2b4 100644 | |||
610 | --- a/lib/lp/code/model/tests/test_gitrepository.py | |||
611 | +++ b/lib/lp/code/model/tests/test_gitrepository.py | |||
612 | @@ -809,7 +809,7 @@ class TestGitRepositoryDeletion(TestCaseWithFactory): | |||
613 | 809 | 809 | ||
614 | 810 | def test_private_subscription_does_not_disable_deletion(self): | 810 | def test_private_subscription_does_not_disable_deletion(self): |
615 | 811 | # A private repository that has a subscription can be deleted. | 811 | # A private repository that has a subscription can be deleted. |
617 | 812 | self.repository.transitionToInformationType( | 812 | removeSecurityProxy(self.repository)._transitionToInformationType( |
618 | 813 | InformationType.USERDATA, self.repository.owner, | 813 | InformationType.USERDATA, self.repository.owner, |
619 | 814 | verify_policy=False) | 814 | verify_policy=False) |
620 | 815 | self.repository.subscribe( | 815 | self.repository.subscribe( |
621 | @@ -2054,8 +2054,7 @@ class TestGitRepositoryModerate(TestCaseWithFactory): | |||
622 | 2054 | project.setBranchSharingPolicy(BranchSharingPolicy.PUBLIC) | 2054 | project.setBranchSharingPolicy(BranchSharingPolicy.PUBLIC) |
623 | 2055 | repository.transitionToInformationType( | 2055 | repository.transitionToInformationType( |
624 | 2056 | InformationType.PRIVATESECURITY, project.owner) | 2056 | InformationType.PRIVATESECURITY, project.owner) |
627 | 2057 | self.assertEqual( | 2057 | self.assertTrue(repository.pending_change_information_type) |
626 | 2058 | InformationType.PRIVATESECURITY, repository.information_type) | ||
628 | 2059 | 2058 | ||
629 | 2060 | def test_attribute_smoketest(self): | 2059 | def test_attribute_smoketest(self): |
630 | 2061 | # Users with launchpad.Moderate can set attributes. | 2060 | # Users with launchpad.Moderate can set attributes. |
631 | @@ -3454,7 +3453,7 @@ class TestGitRepositorySet(TestCaseWithFactory): | |||
632 | 3454 | ] | 3453 | ] |
633 | 3455 | for repository, modified_date in zip(repositories, modified_dates): | 3454 | for repository, modified_date in zip(repositories, modified_dates): |
634 | 3456 | removeSecurityProxy(repository).date_last_modified = modified_date | 3455 | removeSecurityProxy(repository).date_last_modified = modified_date |
636 | 3457 | removeSecurityProxy(repositories[0]).transitionToInformationType( | 3456 | removeSecurityProxy(repositories[0])._transitionToInformationType( |
637 | 3458 | InformationType.PRIVATESECURITY, repositories[0].registrant) | 3457 | InformationType.PRIVATESECURITY, repositories[0].registrant) |
638 | 3459 | self.assertEqual( | 3458 | self.assertEqual( |
639 | 3460 | [repositories[3], repositories[4], repositories[1], | 3459 | [repositories[3], repositories[4], repositories[1], |
640 | @@ -3966,8 +3965,7 @@ class TestGitRepositoryWebservice(TestCaseWithFactory): | |||
641 | 3966 | json.dumps({"information_type": "Public Security"})) | 3965 | json.dumps({"information_type": "Public Security"})) |
642 | 3967 | self.assertEqual(209, response.status) | 3966 | self.assertEqual(209, response.status) |
643 | 3968 | with person_logged_in(ANONYMOUS): | 3967 | with person_logged_in(ANONYMOUS): |
646 | 3969 | self.assertEqual( | 3968 | self.assertTrue(repository_db.pending_change_information_type) |
645 | 3970 | InformationType.PUBLICSECURITY, repository_db.information_type) | ||
647 | 3971 | 3969 | ||
648 | 3972 | def test_set_information_type_other_person(self): | 3970 | def test_set_information_type_other_person(self): |
649 | 3973 | # An unrelated user cannot change the information type. | 3971 | # An unrelated user cannot change the information type. |
650 | diff --git a/lib/lp/code/xmlrpc/tests/test_git.py b/lib/lp/code/xmlrpc/tests/test_git.py | |||
651 | index dcbcee6..e884cd2 100644 | |||
652 | --- a/lib/lp/code/xmlrpc/tests/test_git.py | |||
653 | +++ b/lib/lp/code/xmlrpc/tests/test_git.py | |||
654 | @@ -899,7 +899,7 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory): | |||
655 | 899 | for _ in range(2)] | 899 | for _ in range(2)] |
656 | 900 | private_repository = code_imports[0].git_repository | 900 | private_repository = code_imports[0].git_repository |
657 | 901 | removeSecurityProxy( | 901 | removeSecurityProxy( |
659 | 902 | private_repository).transitionToInformationType( | 902 | private_repository)._transitionToInformationType( |
660 | 903 | InformationType.PRIVATESECURITY, private_repository.owner) | 903 | InformationType.PRIVATESECURITY, private_repository.owner) |
661 | 904 | with celebrity_logged_in("vcs_imports"): | 904 | with celebrity_logged_in("vcs_imports"): |
662 | 905 | jobs = [ | 905 | jobs = [ |
663 | @@ -1077,7 +1077,7 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory): | |||
664 | 1077 | for _ in range(2)] | 1077 | for _ in range(2)] |
665 | 1078 | private_repository = code_imports[0].git_repository | 1078 | private_repository = code_imports[0].git_repository |
666 | 1079 | removeSecurityProxy( | 1079 | removeSecurityProxy( |
668 | 1080 | private_repository).transitionToInformationType( | 1080 | private_repository)._transitionToInformationType( |
669 | 1081 | InformationType.PRIVATESECURITY, private_repository.owner) | 1081 | InformationType.PRIVATESECURITY, private_repository.owner) |
670 | 1082 | with celebrity_logged_in("vcs_imports"): | 1082 | with celebrity_logged_in("vcs_imports"): |
671 | 1083 | jobs = [ | 1083 | jobs = [ |
672 | @@ -1687,7 +1687,7 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory): | |||
673 | 1687 | target_rcs_type=TargetRevisionControlSystems.GIT) | 1687 | target_rcs_type=TargetRevisionControlSystems.GIT) |
674 | 1688 | for _ in range(2)] | 1688 | for _ in range(2)] |
675 | 1689 | private_repository = code_imports[0].git_repository | 1689 | private_repository = code_imports[0].git_repository |
677 | 1690 | removeSecurityProxy(private_repository).transitionToInformationType( | 1690 | removeSecurityProxy(private_repository)._transitionToInformationType( |
678 | 1691 | InformationType.PRIVATESECURITY, private_repository.owner) | 1691 | InformationType.PRIVATESECURITY, private_repository.owner) |
679 | 1692 | with celebrity_logged_in("vcs_imports"): | 1692 | with celebrity_logged_in("vcs_imports"): |
680 | 1693 | jobs = [ | 1693 | jobs = [ |
681 | @@ -2012,7 +2012,7 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory): | |||
682 | 2012 | target_rcs_type=TargetRevisionControlSystems.GIT) | 2012 | target_rcs_type=TargetRevisionControlSystems.GIT) |
683 | 2013 | for _ in range(2)] | 2013 | for _ in range(2)] |
684 | 2014 | private_repository = code_imports[0].git_repository | 2014 | private_repository = code_imports[0].git_repository |
686 | 2015 | removeSecurityProxy(private_repository).transitionToInformationType( | 2015 | removeSecurityProxy(private_repository)._transitionToInformationType( |
687 | 2016 | InformationType.PRIVATESECURITY, private_repository.owner) | 2016 | InformationType.PRIVATESECURITY, private_repository.owner) |
688 | 2017 | with celebrity_logged_in("vcs_imports"): | 2017 | with celebrity_logged_in("vcs_imports"): |
689 | 2018 | jobs = [ | 2018 | jobs = [ |
690 | @@ -2268,7 +2268,7 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory): | |||
691 | 2268 | target_rcs_type=TargetRevisionControlSystems.GIT) | 2268 | target_rcs_type=TargetRevisionControlSystems.GIT) |
692 | 2269 | for _ in range(2)] | 2269 | for _ in range(2)] |
693 | 2270 | private_repository = code_imports[0].git_repository | 2270 | private_repository = code_imports[0].git_repository |
695 | 2271 | removeSecurityProxy(private_repository).transitionToInformationType( | 2271 | removeSecurityProxy(private_repository)._transitionToInformationType( |
696 | 2272 | InformationType.PRIVATESECURITY, private_repository.owner) | 2272 | InformationType.PRIVATESECURITY, private_repository.owner) |
697 | 2273 | with celebrity_logged_in("vcs_imports"): | 2273 | with celebrity_logged_in("vcs_imports"): |
698 | 2274 | jobs = [ | 2274 | jobs = [ |
699 | diff --git a/lib/lp/services/config/schema-lazr.conf b/lib/lp/services/config/schema-lazr.conf | |||
700 | index aaa9d30..1157cb5 100644 | |||
701 | --- a/lib/lp/services/config/schema-lazr.conf | |||
702 | +++ b/lib/lp/services/config/schema-lazr.conf | |||
703 | @@ -1768,6 +1768,7 @@ job_sources: | |||
704 | 1768 | ICommercialExpiredJobSource, | 1768 | ICommercialExpiredJobSource, |
705 | 1769 | IExpiringMembershipNotificationJobSource, | 1769 | IExpiringMembershipNotificationJobSource, |
706 | 1770 | IGitRepositoryModifiedMailJobSource, | 1770 | IGitRepositoryModifiedMailJobSource, |
707 | 1771 | IGitRepositoryChangeInformationTypeJobSource, | ||
708 | 1771 | IMembershipNotificationJobSource, | 1772 | IMembershipNotificationJobSource, |
709 | 1772 | IOCIRecipeRequestBuildsJobSource, | 1773 | IOCIRecipeRequestBuildsJobSource, |
710 | 1773 | IOCIRegistryUploadJobSource, | 1774 | IOCIRegistryUploadJobSource, |
711 | @@ -1829,6 +1830,11 @@ module: lp.code.interfaces.gitjob | |||
712 | 1829 | dbuser: send-branch-mail | 1830 | dbuser: send-branch-mail |
713 | 1830 | crontab_group: MAIN | 1831 | crontab_group: MAIN |
714 | 1831 | 1832 | ||
715 | 1833 | [IGitRepositoryChangeInformationTypeJobSource] | ||
716 | 1834 | module: lp.code.interfaces.gitjob | ||
717 | 1835 | dbuser: privacy-change-jobs | ||
718 | 1836 | crontab_group: MAIN | ||
719 | 1837 | |||
720 | 1832 | [IInitializeDistroSeriesJobSource] | 1838 | [IInitializeDistroSeriesJobSource] |
721 | 1833 | module: lp.soyuz.interfaces.distributionjob | 1839 | module: lp.soyuz.interfaces.distributionjob |
722 | 1834 | dbuser: initializedistroseries | 1840 | dbuser: initializedistroseries |
723 | diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py | |||
724 | index 57babc5..9d1f53b 100644 | |||
725 | --- a/lib/lp/testing/factory.py | |||
726 | +++ b/lib/lp/testing/factory.py | |||
727 | @@ -1815,7 +1815,7 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
728 | 1815 | reviewer=reviewer, **optional_repository_args) | 1815 | reviewer=reviewer, **optional_repository_args) |
729 | 1816 | naked_repository = removeSecurityProxy(repository) | 1816 | naked_repository = removeSecurityProxy(repository) |
730 | 1817 | if information_type is not None: | 1817 | if information_type is not None: |
732 | 1818 | naked_repository.transitionToInformationType( | 1818 | naked_repository._transitionToInformationType( |
733 | 1819 | information_type, registrant, verify_policy=False) | 1819 | information_type, registrant, verify_policy=False) |
734 | 1820 | return repository | 1820 | return repository |
735 | 1821 | 1821 |
This MP is already quite big, so I'll create in another MP the garbo job that will deal with GitRepositories that are PENDING_ INFORMATION_ TYPE_TRANSITION for too long (to avoid blocking them forever).