Merge lp:~cjwatson/launchpad/git-repository-delete-job into lp:launchpad
- git-repository-delete-job
- Merge into devel
Proposed by
Colin Watson
Status: | Rejected | ||||
---|---|---|---|---|---|
Rejected by: | Colin Watson | ||||
Proposed branch: | lp:~cjwatson/launchpad/git-repository-delete-job | ||||
Merge into: | lp:launchpad | ||||
Prerequisite: | lp:~cjwatson/launchpad/branch-delete-job | ||||
Diff against target: |
752 lines (+317/-33) 10 files modified
database/schema/security.cfg (+7/-0) lib/lp/code/configure.zcml (+9/-0) lib/lp/code/enums.py (+12/-0) lib/lp/code/interfaces/gitjob.py (+22/-1) lib/lp/code/interfaces/gitrepository.py (+6/-0) lib/lp/code/model/gitjob.py (+73/-0) lib/lp/code/model/gitrepository.py (+18/-0) lib/lp/code/model/tests/test_gitjob.py (+104/-0) lib/lp/code/model/tests/test_gitrepository.py (+60/-32) lib/lp/services/config/schema-lazr.conf (+6/-0) |
||||
To merge this branch: | bzr merge lp:~cjwatson/launchpad/git-repository-delete-job | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Launchpad code reviewers | Pending | ||
Review via email: mp+364910@code.launchpad.net |
Commit message
Add a Git repository deletion job.
Description of the change
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote : | # |
Unmerged revisions
- 18913. By Colin Watson
-
Add a Git repository deletion job.
- 18912. By Colin Watson
-
Add a branch deletion job.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'database/schema/security.cfg' | |||
2 | --- database/schema/security.cfg 2019-03-21 16:22:19 +0000 | |||
3 | +++ database/schema/security.cfg 2019-03-21 16:22:19 +0000 | |||
4 | @@ -2621,6 +2621,13 @@ | |||
5 | 2621 | public.distributionsourcepackage = SELECT, DELETE | 2621 | public.distributionsourcepackage = SELECT, DELETE |
6 | 2622 | public.distroseries = SELECT | 2622 | public.distroseries = SELECT |
7 | 2623 | public.emailaddress = SELECT | 2623 | public.emailaddress = SELECT |
8 | 2624 | public.gitactivity = SELECT, DELETE | ||
9 | 2625 | public.gitjob = SELECT, INSERT, DELETE | ||
10 | 2626 | public.gitref = SELECT, DELETE | ||
11 | 2627 | public.gitrepository = SELECT, UPDATE, DELETE | ||
12 | 2628 | public.gitrule = SELECT, DELETE | ||
13 | 2629 | public.gitrulegrant = SELECT, DELETE | ||
14 | 2630 | public.gitsubscription = SELECT, DELETE | ||
15 | 2624 | public.job = SELECT, INSERT, UPDATE, DELETE | 2631 | public.job = SELECT, INSERT, UPDATE, DELETE |
16 | 2625 | public.person = SELECT | 2632 | public.person = SELECT |
17 | 2626 | public.previewdiff = SELECT, DELETE | 2633 | public.previewdiff = SELECT, DELETE |
18 | 2627 | 2634 | ||
19 | === modified file 'lib/lp/code/configure.zcml' | |||
20 | --- lib/lp/code/configure.zcml 2019-03-21 16:22:19 +0000 | |||
21 | +++ lib/lp/code/configure.zcml 2019-03-21 16:22:19 +0000 | |||
22 | @@ -1092,6 +1092,11 @@ | |||
23 | 1092 | provides="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJobSource"> | 1092 | provides="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJobSource"> |
24 | 1093 | <allow interface="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJobSource" /> | 1093 | <allow interface="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJobSource" /> |
25 | 1094 | </securedutility> | 1094 | </securedutility> |
26 | 1095 | <securedutility | ||
27 | 1096 | component="lp.code.model.gitjob.GitRepositoryDeleteJob" | ||
28 | 1097 | provides="lp.code.interfaces.gitjob.IGitRepositoryDeleteJobSource"> | ||
29 | 1098 | <allow interface="lp.code.interfaces.gitjob.IGitRepositoryDeleteJobSource" /> | ||
30 | 1099 | </securedutility> | ||
31 | 1095 | <class class="lp.code.model.gitjob.GitRefScanJob"> | 1100 | <class class="lp.code.model.gitjob.GitRefScanJob"> |
32 | 1096 | <allow interface="lp.code.interfaces.gitjob.IGitJob" /> | 1101 | <allow interface="lp.code.interfaces.gitjob.IGitJob" /> |
33 | 1097 | <allow interface="lp.code.interfaces.gitjob.IGitRefScanJob" /> | 1102 | <allow interface="lp.code.interfaces.gitjob.IGitRefScanJob" /> |
34 | @@ -1104,6 +1109,10 @@ | |||
35 | 1104 | <allow interface="lp.code.interfaces.gitjob.IGitJob" /> | 1109 | <allow interface="lp.code.interfaces.gitjob.IGitJob" /> |
36 | 1105 | <allow interface="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJob" /> | 1110 | <allow interface="lp.code.interfaces.gitjob.IGitRepositoryModifiedMailJob" /> |
37 | 1106 | </class> | 1111 | </class> |
38 | 1112 | <class class="lp.code.model.gitjob.GitRepositoryDeleteJob"> | ||
39 | 1113 | <allow interface="lp.code.interfaces.gitjob.IGitJob" /> | ||
40 | 1114 | <allow interface="lp.code.interfaces.gitjob.IGitRepositoryDeleteJob" /> | ||
41 | 1115 | </class> | ||
42 | 1107 | 1116 | ||
43 | 1108 | <lp:help-folder folder="help" name="+help-code" /> | 1117 | <lp:help-folder folder="help" name="+help-code" /> |
44 | 1109 | 1118 | ||
45 | 1110 | 1119 | ||
46 | === modified file 'lib/lp/code/enums.py' | |||
47 | --- lib/lp/code/enums.py 2019-03-21 16:22:19 +0000 | |||
48 | +++ lib/lp/code/enums.py 2019-03-21 16:22:19 +0000 | |||
49 | @@ -26,6 +26,7 @@ | |||
50 | 26 | 'GitGranteeType', | 26 | 'GitGranteeType', |
51 | 27 | 'GitObjectType', | 27 | 'GitObjectType', |
52 | 28 | 'GitPermissionType', | 28 | 'GitPermissionType', |
53 | 29 | 'GitRepositoryDeletionStatus', | ||
54 | 29 | 'GitRepositoryType', | 30 | 'GitRepositoryType', |
55 | 30 | 'NON_CVS_RCS_TYPES', | 31 | 'NON_CVS_RCS_TYPES', |
56 | 31 | 'RevisionControlSystems', | 32 | 'RevisionControlSystems', |
57 | @@ -158,6 +159,17 @@ | |||
58 | 158 | """) | 159 | """) |
59 | 159 | 160 | ||
60 | 160 | 161 | ||
61 | 162 | class GitRepositoryDeletionStatus(DBEnumeratedType): | ||
62 | 163 | """Git Repository Deletion Status | ||
63 | 164 | |||
64 | 165 | The deletion status of a repository is used to track asynchronous | ||
65 | 166 | deletion. | ||
66 | 167 | """ | ||
67 | 168 | |||
68 | 169 | ACTIVE = DBItem(0, "Active") | ||
69 | 170 | DELETING = DBItem(1, "Deleting") | ||
70 | 171 | |||
71 | 172 | |||
72 | 161 | class GitObjectType(DBEnumeratedType): | 173 | class GitObjectType(DBEnumeratedType): |
73 | 162 | """Git Object Type | 174 | """Git Object Type |
74 | 163 | 175 | ||
75 | 164 | 176 | ||
76 | === modified file 'lib/lp/code/interfaces/gitjob.py' | |||
77 | --- lib/lp/code/interfaces/gitjob.py 2015-09-01 17:10:46 +0000 | |||
78 | +++ lib/lp/code/interfaces/gitjob.py 2019-03-21 16:22:19 +0000 | |||
79 | @@ -9,6 +9,8 @@ | |||
80 | 9 | 'IGitJob', | 9 | 'IGitJob', |
81 | 10 | 'IGitRefScanJob', | 10 | 'IGitRefScanJob', |
82 | 11 | 'IGitRefScanJobSource', | 11 | 'IGitRefScanJobSource', |
83 | 12 | 'IGitRepositoryDeleteJob', | ||
84 | 13 | 'IGitRepositoryDeleteJobSource', | ||
85 | 12 | 'IGitRepositoryModifiedMailJob', | 14 | 'IGitRepositoryModifiedMailJob', |
86 | 13 | 'IGitRepositoryModifiedMailJobSource', | 15 | 'IGitRepositoryModifiedMailJobSource', |
87 | 14 | 'IReclaimGitRepositorySpaceJob', | 16 | 'IReclaimGitRepositorySpaceJob', |
88 | @@ -20,7 +22,10 @@ | |||
89 | 20 | Attribute, | 22 | Attribute, |
90 | 21 | Interface, | 23 | Interface, |
91 | 22 | ) | 24 | ) |
93 | 23 | from zope.schema import Text | 25 | from zope.schema import ( |
94 | 26 | Int, | ||
95 | 27 | Text, | ||
96 | 28 | ) | ||
97 | 24 | 29 | ||
98 | 25 | from lp import _ | 30 | from lp import _ |
99 | 26 | from lp.code.interfaces.gitrepository import IGitRepository | 31 | from lp.code.interfaces.gitrepository import IGitRepository |
100 | @@ -93,3 +98,19 @@ | |||
101 | 93 | :param repository_delta: An `IGitRepositoryDelta` describing the | 98 | :param repository_delta: An `IGitRepositoryDelta` describing the |
102 | 94 | changes. | 99 | changes. |
103 | 95 | """ | 100 | """ |
104 | 101 | |||
105 | 102 | |||
106 | 103 | class IGitRepositoryDeleteJob(IRunnableJob): | ||
107 | 104 | """A Job that deletes a repository from the database.""" | ||
108 | 105 | |||
109 | 106 | repository_id = Int(title=_("The id of the repository to delete.")) | ||
110 | 107 | |||
111 | 108 | |||
112 | 109 | class IGitRepositoryDeleteJobSource(IJobSource): | ||
113 | 110 | |||
114 | 111 | def create(repository, requester): | ||
115 | 112 | """Delete a repository from the database. | ||
116 | 113 | |||
117 | 114 | :param repository: The `IGitRepository` to delete. | ||
118 | 115 | :param requester: The `IPerson` who requested the deletion. | ||
119 | 116 | """ | ||
120 | 96 | 117 | ||
121 | === modified file 'lib/lp/code/interfaces/gitrepository.py' | |||
122 | --- lib/lp/code/interfaces/gitrepository.py 2019-01-28 17:19:44 +0000 | |||
123 | +++ lib/lp/code/interfaces/gitrepository.py 2019-03-21 16:22:19 +0000 | |||
124 | @@ -64,6 +64,7 @@ | |||
125 | 64 | BranchSubscriptionDiffSize, | 64 | BranchSubscriptionDiffSize, |
126 | 65 | BranchSubscriptionNotificationLevel, | 65 | BranchSubscriptionNotificationLevel, |
127 | 66 | CodeReviewNotificationLevel, | 66 | CodeReviewNotificationLevel, |
128 | 67 | GitRepositoryDeletionStatus, | ||
129 | 67 | GitRepositoryType, | 68 | GitRepositoryType, |
130 | 68 | ) | 69 | ) |
131 | 69 | from lp.code.interfaces.defaultgit import ICanHasDefaultGitRepository | 70 | from lp.code.interfaces.defaultgit import ICanHasDefaultGitRepository |
132 | @@ -139,6 +140,11 @@ | |||
133 | 139 | "The way this repository is hosted: directly on Launchpad, or " | 140 | "The way this repository is hosted: directly on Launchpad, or " |
134 | 140 | "imported from somewhere else."))) | 141 | "imported from somewhere else."))) |
135 | 141 | 142 | ||
136 | 143 | deletion_status = exported(Choice( | ||
137 | 144 | title=_("Deletion status"), required=True, readonly=True, | ||
138 | 145 | vocabulary=GitRepositoryDeletionStatus, | ||
139 | 146 | description=_("The deletion status of this repository."))) | ||
140 | 147 | |||
141 | 142 | registrant = exported(PublicPersonChoice( | 148 | registrant = exported(PublicPersonChoice( |
142 | 143 | title=_("Registrant"), required=True, readonly=True, | 149 | title=_("Registrant"), required=True, readonly=True, |
143 | 144 | vocabulary="ValidPersonOrTeam", | 150 | vocabulary="ValidPersonOrTeam", |
144 | 145 | 151 | ||
145 | === modified file 'lib/lp/code/model/gitjob.py' | |||
146 | --- lib/lp/code/model/gitjob.py 2018-10-22 00:37:07 +0000 | |||
147 | +++ lib/lp/code/model/gitjob.py 2019-03-21 16:22:19 +0000 | |||
148 | @@ -8,6 +8,7 @@ | |||
149 | 8 | 'GitJob', | 8 | 'GitJob', |
150 | 9 | 'GitJobType', | 9 | 'GitJobType', |
151 | 10 | 'GitRefScanJob', | 10 | 'GitRefScanJob', |
152 | 11 | 'GitRepositoryDeleteJob', | ||
153 | 11 | 'GitRepositoryModifiedMailJob', | 12 | 'GitRepositoryModifiedMailJob', |
154 | 12 | 'ReclaimGitRepositorySpaceJob', | 13 | 'ReclaimGitRepositorySpaceJob', |
155 | 13 | ] | 14 | ] |
156 | @@ -25,6 +26,7 @@ | |||
157 | 25 | SQL, | 26 | SQL, |
158 | 26 | Store, | 27 | Store, |
159 | 27 | ) | 28 | ) |
160 | 29 | import transaction | ||
161 | 28 | from zope.component import getUtility | 30 | from zope.component import getUtility |
162 | 29 | from zope.interface import ( | 31 | from zope.interface import ( |
163 | 30 | implementer, | 32 | implementer, |
164 | @@ -36,17 +38,22 @@ | |||
165 | 36 | from lp.code.enums import ( | 38 | from lp.code.enums import ( |
166 | 37 | GitActivityType, | 39 | GitActivityType, |
167 | 38 | GitPermissionType, | 40 | GitPermissionType, |
168 | 41 | GitRepositoryDeletionStatus, | ||
169 | 39 | ) | 42 | ) |
170 | 43 | from lp.code.errors import CannotDeleteGitRepository | ||
171 | 40 | from lp.code.interfaces.githosting import IGitHostingClient | 44 | from lp.code.interfaces.githosting import IGitHostingClient |
172 | 41 | from lp.code.interfaces.gitjob import ( | 45 | from lp.code.interfaces.gitjob import ( |
173 | 42 | IGitJob, | 46 | IGitJob, |
174 | 43 | IGitRefScanJob, | 47 | IGitRefScanJob, |
175 | 44 | IGitRefScanJobSource, | 48 | IGitRefScanJobSource, |
176 | 49 | IGitRepositoryDeleteJob, | ||
177 | 50 | IGitRepositoryDeleteJobSource, | ||
178 | 45 | IGitRepositoryModifiedMailJob, | 51 | IGitRepositoryModifiedMailJob, |
179 | 46 | IGitRepositoryModifiedMailJobSource, | 52 | IGitRepositoryModifiedMailJobSource, |
180 | 47 | IReclaimGitRepositorySpaceJob, | 53 | IReclaimGitRepositorySpaceJob, |
181 | 48 | IReclaimGitRepositorySpaceJobSource, | 54 | IReclaimGitRepositorySpaceJobSource, |
182 | 49 | ) | 55 | ) |
183 | 56 | from lp.code.interfaces.gitlookup import IGitLookup | ||
184 | 50 | from lp.code.interfaces.gitrule import describe_git_permissions | 57 | from lp.code.interfaces.gitrule import describe_git_permissions |
185 | 51 | from lp.code.mail.branch import BranchMailer | 58 | from lp.code.mail.branch import BranchMailer |
186 | 52 | from lp.registry.interfaces.person import IPersonSet | 59 | from lp.registry.interfaces.person import IPersonSet |
187 | @@ -99,6 +106,12 @@ | |||
188 | 99 | modifications. | 106 | modifications. |
189 | 100 | """) | 107 | """) |
190 | 101 | 108 | ||
191 | 109 | DELETE_REPOSITORY = DBItem(3, """ | ||
192 | 110 | Delete repository | ||
193 | 111 | |||
194 | 112 | This job deletes a repository from the database. | ||
195 | 113 | """) | ||
196 | 114 | |||
197 | 102 | 115 | ||
198 | 103 | @implementer(IGitJob) | 116 | @implementer(IGitJob) |
199 | 104 | class GitJob(StormBase): | 117 | class GitJob(StormBase): |
200 | @@ -405,3 +418,63 @@ | |||
201 | 405 | def run(self): | 418 | def run(self): |
202 | 406 | """See `IGitRepositoryModifiedMailJob`.""" | 419 | """See `IGitRepositoryModifiedMailJob`.""" |
203 | 407 | self.getMailer().sendAll() | 420 | self.getMailer().sendAll() |
204 | 421 | |||
205 | 422 | |||
206 | 423 | @implementer(IGitRepositoryDeleteJob) | ||
207 | 424 | @provider(IGitRepositoryDeleteJobSource) | ||
208 | 425 | class GitRepositoryDeleteJob(GitJobDerived): | ||
209 | 426 | """A Job that deletes a repository from the database.""" | ||
210 | 427 | |||
211 | 428 | class_job_type = GitJobType.DELETE_REPOSITORY | ||
212 | 429 | |||
213 | 430 | user_error_types = (CannotDeleteGitRepository,) | ||
214 | 431 | |||
215 | 432 | config = config.IGitRepositoryDeleteJobSource | ||
216 | 433 | |||
217 | 434 | def getOperationDescription(self): | ||
218 | 435 | return "deleting a repository" | ||
219 | 436 | |||
220 | 437 | @classmethod | ||
221 | 438 | def create(cls, repository, requester): | ||
222 | 439 | """See `IGitRepositoryDeleteJobSource`.""" | ||
223 | 440 | metadata = { | ||
224 | 441 | "repository_id": repository.id, | ||
225 | 442 | "repository_name": repository.unique_name, | ||
226 | 443 | } | ||
227 | 444 | # The GitJob has a repository of None, because we don't want to | ||
228 | 445 | # delete this job while trying to delete the repository. | ||
229 | 446 | git_job = GitJob( | ||
230 | 447 | None, cls.class_job_type, metadata, requester=requester) | ||
231 | 448 | job = cls(git_job) | ||
232 | 449 | job.celeryRunOnCommit() | ||
233 | 450 | return job | ||
234 | 451 | |||
235 | 452 | @property | ||
236 | 453 | def repository_id(self): | ||
237 | 454 | return self.metadata["repository_id"] | ||
238 | 455 | |||
239 | 456 | def run(self): | ||
240 | 457 | """See `IGitRepositoryDeleteJob`.""" | ||
241 | 458 | repository = getUtility(IGitLookup).get(self.repository_id) | ||
242 | 459 | if repository is None: | ||
243 | 460 | log.info( | ||
244 | 461 | "Skipping repository %s because it has already been deleted." % | ||
245 | 462 | self._cached_repository_name) | ||
246 | 463 | elif (repository.deletion_status != | ||
247 | 464 | GitRepositoryDeletionStatus.DELETING): | ||
248 | 465 | log.warning( | ||
249 | 466 | "Skipping repository %s because its deletion status is not " | ||
250 | 467 | "DELETING." % self._cached_repository_name) | ||
251 | 468 | else: | ||
252 | 469 | try: | ||
253 | 470 | repository.destroySelf(break_references=True) | ||
254 | 471 | except CannotDeleteGitRepository: | ||
255 | 472 | # Set the deletion status back to ACTIVE so that it's | ||
256 | 473 | # possible to try again. We don't attempt to undo the | ||
257 | 474 | # renaming at the moment. Do this in its own transaction | ||
258 | 475 | # since the job runner will abort the transaction. | ||
259 | 476 | transaction.abort() | ||
260 | 477 | removeSecurityProxy(repository).deletion_status = ( | ||
261 | 478 | GitRepositoryDeletionStatus.ACTIVE) | ||
262 | 479 | transaction.commit() | ||
263 | 480 | raise | ||
264 | 408 | 481 | ||
265 | === modified file 'lib/lp/code/model/gitrepository.py' | |||
266 | --- lib/lp/code/model/gitrepository.py 2019-03-21 16:22:19 +0000 | |||
267 | +++ lib/lp/code/model/gitrepository.py 2019-03-21 16:22:19 +0000 | |||
268 | @@ -91,6 +91,7 @@ | |||
269 | 91 | GitGranteeType, | 91 | GitGranteeType, |
270 | 92 | GitObjectType, | 92 | GitObjectType, |
271 | 93 | GitPermissionType, | 93 | GitPermissionType, |
272 | 94 | GitRepositoryDeletionStatus, | ||
273 | 94 | GitRepositoryType, | 95 | GitRepositoryType, |
274 | 95 | ) | 96 | ) |
275 | 96 | from lp.code.errors import ( | 97 | from lp.code.errors import ( |
276 | @@ -282,6 +283,23 @@ | |||
277 | 282 | repository_type = EnumCol( | 283 | repository_type = EnumCol( |
278 | 283 | dbName='repository_type', enum=GitRepositoryType, notNull=True) | 284 | dbName='repository_type', enum=GitRepositoryType, notNull=True) |
279 | 284 | 285 | ||
280 | 286 | _deletion_status = EnumCol( | ||
281 | 287 | dbName='deletion_status', enum=GitRepositoryDeletionStatus, | ||
282 | 288 | default=GitRepositoryDeletionStatus.ACTIVE) | ||
283 | 289 | |||
284 | 290 | @property | ||
285 | 291 | def deletion_status(self): | ||
286 | 292 | # XXX cjwatson 2019-03-19: Remove once this column has been | ||
287 | 293 | # backfilled. | ||
288 | 294 | if self._deletion_status is None: | ||
289 | 295 | return GitRepositoryDeletionStatus.ACTIVE | ||
290 | 296 | else: | ||
291 | 297 | return self._deletion_status | ||
292 | 298 | |||
293 | 299 | @deletion_status.setter | ||
294 | 300 | def deletion_status(self, value): | ||
295 | 301 | self._deletion_status = value | ||
296 | 302 | |||
297 | 285 | registrant_id = Int(name='registrant', allow_none=False) | 303 | registrant_id = Int(name='registrant', allow_none=False) |
298 | 286 | registrant = Reference(registrant_id, 'Person.id') | 304 | registrant = Reference(registrant_id, 'Person.id') |
299 | 287 | 305 | ||
300 | 288 | 306 | ||
301 | === modified file 'lib/lp/code/model/tests/test_gitjob.py' | |||
302 | --- lib/lp/code/model/tests/test_gitjob.py 2018-10-21 17:38:05 +0000 | |||
303 | +++ lib/lp/code/model/tests/test_gitjob.py 2019-03-21 16:22:19 +0000 | |||
304 | @@ -13,6 +13,10 @@ | |||
305 | 13 | ) | 13 | ) |
306 | 14 | import hashlib | 14 | import hashlib |
307 | 15 | 15 | ||
308 | 16 | from fixtures import ( | ||
309 | 17 | FakeLogger, | ||
310 | 18 | MockPatch, | ||
311 | 19 | ) | ||
312 | 16 | from lazr.lifecycle.snapshot import Snapshot | 20 | from lazr.lifecycle.snapshot import Snapshot |
313 | 17 | import pytz | 21 | import pytz |
314 | 18 | from testtools.matchers import ( | 22 | from testtools.matchers import ( |
315 | @@ -23,6 +27,7 @@ | |||
316 | 23 | MatchesStructure, | 27 | MatchesStructure, |
317 | 24 | ) | 28 | ) |
318 | 25 | import transaction | 29 | import transaction |
319 | 30 | from zope.component import getUtility | ||
320 | 26 | from zope.interface import providedBy | 31 | from zope.interface import providedBy |
321 | 27 | from zope.security.proxy import removeSecurityProxy | 32 | from zope.security.proxy import removeSecurityProxy |
322 | 28 | 33 | ||
323 | @@ -30,6 +35,7 @@ | |||
324 | 30 | from lp.code.enums import ( | 35 | from lp.code.enums import ( |
325 | 31 | GitGranteeType, | 36 | GitGranteeType, |
326 | 32 | GitObjectType, | 37 | GitObjectType, |
327 | 38 | GitRepositoryDeletionStatus, | ||
328 | 33 | ) | 39 | ) |
329 | 34 | from lp.code.interfaces.branchmergeproposal import ( | 40 | from lp.code.interfaces.branchmergeproposal import ( |
330 | 35 | BRANCH_MERGE_PROPOSAL_WEBHOOKS_FEATURE_FLAG, | 41 | BRANCH_MERGE_PROPOSAL_WEBHOOKS_FEATURE_FLAG, |
331 | @@ -37,8 +43,11 @@ | |||
332 | 37 | from lp.code.interfaces.gitjob import ( | 43 | from lp.code.interfaces.gitjob import ( |
333 | 38 | IGitJob, | 44 | IGitJob, |
334 | 39 | IGitRefScanJob, | 45 | IGitRefScanJob, |
335 | 46 | IGitRepositoryDeleteJob, | ||
336 | 47 | IGitRepositoryDeleteJobSource, | ||
337 | 40 | IReclaimGitRepositorySpaceJob, | 48 | IReclaimGitRepositorySpaceJob, |
338 | 41 | ) | 49 | ) |
339 | 50 | from lp.code.interfaces.gitlookup import IGitLookup | ||
340 | 42 | from lp.code.model.gitjob import ( | 51 | from lp.code.model.gitjob import ( |
341 | 43 | describe_repository_delta, | 52 | describe_repository_delta, |
342 | 44 | GitJob, | 53 | GitJob, |
343 | @@ -52,6 +61,7 @@ | |||
344 | 52 | from lp.services.database.constants import UTC_NOW | 61 | from lp.services.database.constants import UTC_NOW |
345 | 53 | from lp.services.features.testing import FeatureFixture | 62 | from lp.services.features.testing import FeatureFixture |
346 | 54 | from lp.services.job.runner import JobRunner | 63 | from lp.services.job.runner import JobRunner |
347 | 64 | from lp.services.mail.sendmail import format_address_for_person | ||
348 | 55 | from lp.services.utils import seconds_since_epoch | 65 | from lp.services.utils import seconds_since_epoch |
349 | 56 | from lp.services.webapp import canonical_url | 66 | from lp.services.webapp import canonical_url |
350 | 57 | from lp.services.webapp.snapshot import notify_modified | 67 | from lp.services.webapp.snapshot import notify_modified |
351 | @@ -65,6 +75,7 @@ | |||
352 | 65 | DatabaseFunctionalLayer, | 75 | DatabaseFunctionalLayer, |
353 | 66 | ZopelessDatabaseLayer, | 76 | ZopelessDatabaseLayer, |
354 | 67 | ) | 77 | ) |
355 | 78 | from lp.testing.mail_helpers import pop_notifications | ||
356 | 68 | 79 | ||
357 | 69 | 80 | ||
358 | 70 | class TestGitJob(TestCaseWithFactory): | 81 | class TestGitJob(TestCaseWithFactory): |
359 | @@ -473,5 +484,98 @@ | |||
360 | 473 | snapshot, repository) | 484 | snapshot, repository) |
361 | 474 | 485 | ||
362 | 475 | 486 | ||
363 | 487 | class TestGitRepositoryDeleteJob(TestCaseWithFactory): | ||
364 | 488 | |||
365 | 489 | layer = ZopelessDatabaseLayer | ||
366 | 490 | |||
367 | 491 | def test_providesInterface(self): | ||
368 | 492 | repository = self.factory.makeGitRepository() | ||
369 | 493 | requester = repository.registrant | ||
370 | 494 | job = getUtility(IGitRepositoryDeleteJobSource).create( | ||
371 | 495 | repository, requester) | ||
372 | 496 | self.assertProvides(job, IGitRepositoryDeleteJob) | ||
373 | 497 | |||
374 | 498 | def test_run(self): | ||
375 | 499 | repository = self.factory.makeGitRepository() | ||
376 | 500 | repository_id = repository.id | ||
377 | 501 | requester = repository.registrant | ||
378 | 502 | job = getUtility(IGitRepositoryDeleteJobSource).create( | ||
379 | 503 | repository, requester) | ||
380 | 504 | removeSecurityProxy(repository).deletion_status = ( | ||
381 | 505 | GitRepositoryDeletionStatus.DELETING) | ||
382 | 506 | logger = self.useFixture(FakeLogger()) | ||
383 | 507 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): | ||
384 | 508 | job.run() | ||
385 | 509 | self.assertEqual('', logger.output) | ||
386 | 510 | self.assertIsNone(getUtility(IGitLookup).get(repository_id)) | ||
387 | 511 | |||
388 | 512 | def test_already_deleted(self): | ||
389 | 513 | repository = self.factory.makeGitRepository() | ||
390 | 514 | repository_name = repository.unique_name | ||
391 | 515 | requester = repository.registrant | ||
392 | 516 | job = getUtility(IGitRepositoryDeleteJobSource).create( | ||
393 | 517 | repository, requester) | ||
394 | 518 | repository.destroySelf() | ||
395 | 519 | logger = self.useFixture(FakeLogger()) | ||
396 | 520 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): | ||
397 | 521 | job.run() | ||
398 | 522 | self.assertEqual( | ||
399 | 523 | "Skipping repository %s because it has already been " | ||
400 | 524 | "deleted.\n" % repository_name, | ||
401 | 525 | logger.output) | ||
402 | 526 | |||
403 | 527 | def test_not_deleting(self): | ||
404 | 528 | # The job skips repositories that aren't DELETING. This shouldn't | ||
405 | 529 | # be possible in practice, but is a guard against accidents. | ||
406 | 530 | repository = self.factory.makeGitRepository() | ||
407 | 531 | repository_id = repository.id | ||
408 | 532 | repository_name = repository.unique_name | ||
409 | 533 | self.assertNotEqual( | ||
410 | 534 | GitRepositoryDeletionStatus.DELETING, repository.deletion_status) | ||
411 | 535 | requester = repository.registrant | ||
412 | 536 | job = getUtility(IGitRepositoryDeleteJobSource).create( | ||
413 | 537 | repository, requester) | ||
414 | 538 | logger = self.useFixture(FakeLogger()) | ||
415 | 539 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): | ||
416 | 540 | job.run() | ||
417 | 541 | self.assertEqual( | ||
418 | 542 | "Skipping repository %s because its deletion status is not " | ||
419 | 543 | "DELETING.\n" % repository_name, | ||
420 | 544 | logger.output) | ||
421 | 545 | self.assertEqual(repository, getUtility(IGitLookup).get(repository_id)) | ||
422 | 546 | |||
423 | 547 | def test_error(self): | ||
424 | 548 | # If deleting the repository fails, an error message is sent to the | ||
425 | 549 | # requester and the deletion status is set back to ACTIVE. This | ||
426 | 550 | # can't normally happen because the job always breaks references to | ||
427 | 551 | # the repository, so we patch in a failure to allow testing the | ||
428 | 552 | # error path. | ||
429 | 553 | repository = self.factory.makeGitRepository() | ||
430 | 554 | repository_id = repository.id | ||
431 | 555 | requester = repository.registrant | ||
432 | 556 | job = getUtility(IGitRepositoryDeleteJobSource).create( | ||
433 | 557 | repository, requester) | ||
434 | 558 | removeSecurityProxy(repository).deletion_status = ( | ||
435 | 559 | GitRepositoryDeletionStatus.DELETING) | ||
436 | 560 | logger = self.useFixture(FakeLogger()) | ||
437 | 561 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): | ||
438 | 562 | with MockPatch( | ||
439 | 563 | "lp.code.model.gitrepository.GitRepository.canBeDeleted", | ||
440 | 564 | return_value=False): | ||
441 | 565 | JobRunner([job]).runJobHandleError(job) | ||
442 | 566 | self.assertIn( | ||
443 | 567 | "failed with user error CannotDeleteGitRepository", logger.output) | ||
444 | 568 | self.assertEqual(repository, getUtility(IGitLookup).get(repository_id)) | ||
445 | 569 | self.assertEqual( | ||
446 | 570 | GitRepositoryDeletionStatus.ACTIVE, repository.deletion_status) | ||
447 | 571 | self.assertEqual([], self.oopses) | ||
448 | 572 | [mail] = pop_notifications() | ||
449 | 573 | self.assertEqual(format_address_for_person(requester), mail["to"]) | ||
450 | 574 | self.assertEqual( | ||
451 | 575 | "Launchpad error while deleting a repository", mail["subject"]) | ||
452 | 576 | self.assertIn( | ||
453 | 577 | "Cannot delete Git repository", mail.get_payload(decode=True)) | ||
454 | 578 | |||
455 | 579 | |||
456 | 476 | # XXX cjwatson 2015-03-12: We should test that the jobs work via Celery too, | 580 | # XXX cjwatson 2015-03-12: We should test that the jobs work via Celery too, |
457 | 477 | # but that isn't feasible until we have a proper turnip fixture. | 581 | # but that isn't feasible until we have a proper turnip fixture. |
458 | 478 | 582 | ||
459 | === modified file 'lib/lp/code/model/tests/test_gitrepository.py' | |||
460 | --- lib/lp/code/model/tests/test_gitrepository.py 2019-02-11 12:31:06 +0000 | |||
461 | +++ lib/lp/code/model/tests/test_gitrepository.py 2019-03-21 16:22:19 +0000 | |||
462 | @@ -638,7 +638,8 @@ | |||
463 | 638 | self.repository.canBeDeleted(), | 638 | self.repository.canBeDeleted(), |
464 | 639 | "A newly created repository should be able to be deleted.") | 639 | "A newly created repository should be able to be deleted.") |
465 | 640 | repository_id = self.repository.id | 640 | repository_id = self.repository.id |
467 | 641 | self.repository.destroySelf() | 641 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
468 | 642 | self.repository.destroySelf() | ||
469 | 642 | self.assertIsNone( | 643 | self.assertIsNone( |
470 | 643 | getUtility(IGitLookup).get(repository_id), | 644 | getUtility(IGitLookup).get(repository_id), |
471 | 644 | "The repository has not been deleted.") | 645 | "The repository has not been deleted.") |
472 | @@ -650,7 +651,8 @@ | |||
473 | 650 | CodeReviewNotificationLevel.NOEMAIL, self.user) | 651 | CodeReviewNotificationLevel.NOEMAIL, self.user) |
474 | 651 | self.assertTrue(self.repository.canBeDeleted()) | 652 | self.assertTrue(self.repository.canBeDeleted()) |
475 | 652 | repository_id = self.repository.id | 653 | repository_id = self.repository.id |
477 | 653 | self.repository.destroySelf() | 654 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
478 | 655 | self.repository.destroySelf() | ||
479 | 654 | self.assertIsNone( | 656 | self.assertIsNone( |
480 | 655 | getUtility(IGitLookup).get(repository_id), | 657 | getUtility(IGitLookup).get(repository_id), |
481 | 656 | "The repository has not been deleted.") | 658 | "The repository has not been deleted.") |
482 | @@ -665,7 +667,8 @@ | |||
483 | 665 | CodeReviewNotificationLevel.NOEMAIL, self.user) | 667 | CodeReviewNotificationLevel.NOEMAIL, self.user) |
484 | 666 | self.assertTrue(self.repository.canBeDeleted()) | 668 | self.assertTrue(self.repository.canBeDeleted()) |
485 | 667 | repository_id = self.repository.id | 669 | repository_id = self.repository.id |
487 | 668 | self.repository.destroySelf() | 670 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
488 | 671 | self.repository.destroySelf() | ||
489 | 669 | self.assertIsNone( | 672 | self.assertIsNone( |
490 | 670 | getUtility(IGitLookup).get(repository_id), | 673 | getUtility(IGitLookup).get(repository_id), |
491 | 671 | "The repository has not been deleted.") | 674 | "The repository has not been deleted.") |
492 | @@ -687,8 +690,9 @@ | |||
493 | 687 | self.assertFalse( | 690 | self.assertFalse( |
494 | 688 | self.repository.canBeDeleted(), | 691 | self.repository.canBeDeleted(), |
495 | 689 | "A repository with a landing target is not deletable.") | 692 | "A repository with a landing target is not deletable.") |
498 | 690 | self.assertRaises( | 693 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
499 | 691 | CannotDeleteGitRepository, self.repository.destroySelf) | 694 | self.assertRaises( |
500 | 695 | CannotDeleteGitRepository, self.repository.destroySelf) | ||
501 | 692 | 696 | ||
502 | 693 | def test_landing_candidate_disables_deletion(self): | 697 | def test_landing_candidate_disables_deletion(self): |
503 | 694 | # A repository with a landing candidate cannot be deleted. | 698 | # A repository with a landing candidate cannot be deleted. |
504 | @@ -698,8 +702,9 @@ | |||
505 | 698 | self.assertFalse( | 702 | self.assertFalse( |
506 | 699 | self.repository.canBeDeleted(), | 703 | self.repository.canBeDeleted(), |
507 | 700 | "A repository with a landing candidate is not deletable.") | 704 | "A repository with a landing candidate is not deletable.") |
510 | 701 | self.assertRaises( | 705 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
511 | 702 | CannotDeleteGitRepository, self.repository.destroySelf) | 706 | self.assertRaises( |
512 | 707 | CannotDeleteGitRepository, self.repository.destroySelf) | ||
513 | 703 | 708 | ||
514 | 704 | def test_prerequisite_repository_disables_deletion(self): | 709 | def test_prerequisite_repository_disables_deletion(self): |
515 | 705 | # A repository that is a prerequisite repository cannot be deleted. | 710 | # A repository that is a prerequisite repository cannot be deleted. |
516 | @@ -711,14 +716,16 @@ | |||
517 | 711 | self.assertFalse( | 716 | self.assertFalse( |
518 | 712 | self.repository.canBeDeleted(), | 717 | self.repository.canBeDeleted(), |
519 | 713 | "A repository with a prerequisite target is not deletable.") | 718 | "A repository with a prerequisite target is not deletable.") |
522 | 714 | self.assertRaises( | 719 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
523 | 715 | CannotDeleteGitRepository, self.repository.destroySelf) | 720 | self.assertRaises( |
524 | 721 | CannotDeleteGitRepository, self.repository.destroySelf) | ||
525 | 716 | 722 | ||
526 | 717 | def test_related_GitJobs_deleted(self): | 723 | def test_related_GitJobs_deleted(self): |
527 | 718 | # A repository with an associated job will delete those jobs. | 724 | # A repository with an associated job will delete those jobs. |
528 | 719 | GitAPI(None, None).notify(self.repository.getInternalPath()) | 725 | GitAPI(None, None).notify(self.repository.getInternalPath()) |
529 | 720 | store = Store.of(self.repository) | 726 | store = Store.of(self.repository) |
531 | 721 | self.repository.destroySelf() | 727 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
532 | 728 | self.repository.destroySelf() | ||
533 | 722 | # Need to commit the transaction to fire off the constraint checks. | 729 | # Need to commit the transaction to fire off the constraint checks. |
534 | 723 | transaction.commit() | 730 | transaction.commit() |
535 | 724 | jobs = store.find(GitJob, GitJob.job_type == GitJobType.REF_SCAN) | 731 | jobs = store.find(GitJob, GitJob.job_type == GitJobType.REF_SCAN) |
536 | @@ -729,7 +736,8 @@ | |||
537 | 729 | # to remove the repository from disk as well. | 736 | # to remove the repository from disk as well. |
538 | 730 | repository_path = self.repository.getInternalPath() | 737 | repository_path = self.repository.getInternalPath() |
539 | 731 | store = Store.of(self.repository) | 738 | store = Store.of(self.repository) |
541 | 732 | self.repository.destroySelf() | 739 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
542 | 740 | self.repository.destroySelf() | ||
543 | 733 | jobs = store.find( | 741 | jobs = store.find( |
544 | 734 | GitJob, | 742 | GitJob, |
545 | 735 | GitJob.job_type == GitJobType.RECLAIM_REPOSITORY_SPACE) | 743 | GitJob.job_type == GitJobType.RECLAIM_REPOSITORY_SPACE) |
546 | @@ -742,14 +750,16 @@ | |||
547 | 742 | # If repository is a base_git_repository in a recipe, it is deleted. | 750 | # If repository is a base_git_repository in a recipe, it is deleted. |
548 | 743 | recipe = self.factory.makeSourcePackageRecipe( | 751 | recipe = self.factory.makeSourcePackageRecipe( |
549 | 744 | branches=self.factory.makeGitRefs(owner=self.user)) | 752 | branches=self.factory.makeGitRefs(owner=self.user)) |
551 | 745 | recipe.base_git_repository.destroySelf(break_references=True) | 753 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
552 | 754 | recipe.base_git_repository.destroySelf(break_references=True) | ||
553 | 746 | 755 | ||
554 | 747 | def test_destroySelf_with_SourcePackageRecipe_as_non_base(self): | 756 | def test_destroySelf_with_SourcePackageRecipe_as_non_base(self): |
555 | 748 | # If repository is referred to by a recipe, it is deleted. | 757 | # If repository is referred to by a recipe, it is deleted. |
556 | 749 | [ref1] = self.factory.makeGitRefs(owner=self.user) | 758 | [ref1] = self.factory.makeGitRefs(owner=self.user) |
557 | 750 | [ref2] = self.factory.makeGitRefs(owner=self.user) | 759 | [ref2] = self.factory.makeGitRefs(owner=self.user) |
558 | 751 | self.factory.makeSourcePackageRecipe(branches=[ref1, ref2]) | 760 | self.factory.makeSourcePackageRecipe(branches=[ref1, ref2]) |
560 | 752 | ref2.repository.destroySelf(break_references=True) | 761 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
561 | 762 | ref2.repository.destroySelf(break_references=True) | ||
562 | 753 | 763 | ||
563 | 754 | def test_destroySelf_with_inline_comments_draft(self): | 764 | def test_destroySelf_with_inline_comments_draft(self): |
564 | 755 | # Draft inline comments related to a deleted repository (source or | 765 | # Draft inline comments related to a deleted repository (source or |
565 | @@ -763,7 +773,8 @@ | |||
566 | 763 | previewdiff_id=preview_diff.id, | 773 | previewdiff_id=preview_diff.id, |
567 | 764 | person=self.user, | 774 | person=self.user, |
568 | 765 | comments={"1": "Should vanish."}) | 775 | comments={"1": "Should vanish."}) |
570 | 766 | self.repository.destroySelf(break_references=True) | 776 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
571 | 777 | self.repository.destroySelf(break_references=True) | ||
572 | 767 | 778 | ||
573 | 768 | def test_destroySelf_with_inline_comments_published(self): | 779 | def test_destroySelf_with_inline_comments_published(self): |
574 | 769 | # Published inline comments related to a deleted repository (source | 780 | # Published inline comments related to a deleted repository (source |
575 | @@ -779,12 +790,14 @@ | |||
576 | 779 | previewdiff_id=preview_diff.id, | 790 | previewdiff_id=preview_diff.id, |
577 | 780 | inline_comments={"1": "Must disappear."}, | 791 | inline_comments={"1": "Must disappear."}, |
578 | 781 | ) | 792 | ) |
580 | 782 | self.repository.destroySelf(break_references=True) | 793 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
581 | 794 | self.repository.destroySelf(break_references=True) | ||
582 | 783 | 795 | ||
583 | 784 | def test_related_webhooks_deleted(self): | 796 | def test_related_webhooks_deleted(self): |
584 | 785 | webhook = self.factory.makeWebhook(target=self.repository) | 797 | webhook = self.factory.makeWebhook(target=self.repository) |
585 | 786 | webhook.ping() | 798 | webhook.ping() |
587 | 787 | self.repository.destroySelf() | 799 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
588 | 800 | self.repository.destroySelf() | ||
589 | 788 | transaction.commit() | 801 | transaction.commit() |
590 | 789 | self.assertRaises(LostObjectError, getattr, webhook, 'target') | 802 | self.assertRaises(LostObjectError, getattr, webhook, 'target') |
591 | 790 | 803 | ||
592 | @@ -796,7 +809,8 @@ | |||
593 | 796 | activities = store.find( | 809 | activities = store.find( |
594 | 797 | GitActivity, GitActivity.repository_id == repository_id) | 810 | GitActivity, GitActivity.repository_id == repository_id) |
595 | 798 | self.assertNotEqual([], list(activities)) | 811 | self.assertNotEqual([], list(activities)) |
597 | 799 | self.repository.destroySelf() | 812 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
598 | 813 | self.repository.destroySelf() | ||
599 | 800 | transaction.commit() | 814 | transaction.commit() |
600 | 801 | self.assertRaises(LostObjectError, getattr, grant, 'rule') | 815 | self.assertRaises(LostObjectError, getattr, grant, 'rule') |
601 | 802 | self.assertRaises(LostObjectError, getattr, rule, 'repository') | 816 | self.assertRaises(LostObjectError, getattr, rule, 'repository') |
602 | @@ -895,7 +909,8 @@ | |||
603 | 895 | merge_proposal1, merge_proposal2 = self.makeMergeProposals() | 909 | merge_proposal1, merge_proposal2 = self.makeMergeProposals() |
604 | 896 | merge_proposal1_id = merge_proposal1.id | 910 | merge_proposal1_id = merge_proposal1.id |
605 | 897 | BranchMergeProposal.get(merge_proposal1_id) | 911 | BranchMergeProposal.get(merge_proposal1_id) |
607 | 898 | self.repository.destroySelf(break_references=True) | 912 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
608 | 913 | self.repository.destroySelf(break_references=True) | ||
609 | 899 | self.assertRaises( | 914 | self.assertRaises( |
610 | 900 | SQLObjectNotFound, BranchMergeProposal.get, merge_proposal1_id) | 915 | SQLObjectNotFound, BranchMergeProposal.get, merge_proposal1_id) |
611 | 901 | 916 | ||
612 | @@ -905,8 +920,9 @@ | |||
613 | 905 | merge_proposal1, merge_proposal2 = self.makeMergeProposals() | 920 | merge_proposal1, merge_proposal2 = self.makeMergeProposals() |
614 | 906 | merge_proposal1_id = merge_proposal1.id | 921 | merge_proposal1_id = merge_proposal1.id |
615 | 907 | BranchMergeProposal.get(merge_proposal1_id) | 922 | BranchMergeProposal.get(merge_proposal1_id) |
618 | 908 | merge_proposal1.target_git_repository.destroySelf( | 923 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
619 | 909 | break_references=True) | 924 | merge_proposal1.target_git_repository.destroySelf( |
620 | 925 | break_references=True) | ||
621 | 910 | self.assertRaises(SQLObjectNotFound, | 926 | self.assertRaises(SQLObjectNotFound, |
622 | 911 | BranchMergeProposal.get, merge_proposal1_id) | 927 | BranchMergeProposal.get, merge_proposal1_id) |
623 | 912 | 928 | ||
624 | @@ -914,8 +930,9 @@ | |||
625 | 914 | # Merge proposal prerequisite repositories can be deleted with | 930 | # Merge proposal prerequisite repositories can be deleted with |
626 | 915 | # break_references. | 931 | # break_references. |
627 | 916 | merge_proposal1, merge_proposal2 = self.makeMergeProposals() | 932 | merge_proposal1, merge_proposal2 = self.makeMergeProposals() |
630 | 917 | merge_proposal1.prerequisite_git_repository.destroySelf( | 933 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
631 | 918 | break_references=True) | 934 | merge_proposal1.prerequisite_git_repository.destroySelf( |
632 | 935 | break_references=True) | ||
633 | 919 | self.assertIsNone(merge_proposal1.prerequisite_git_repository) | 936 | self.assertIsNone(merge_proposal1.prerequisite_git_repository) |
634 | 920 | 937 | ||
635 | 921 | def test_delete_source_CodeReviewComment(self): | 938 | def test_delete_source_CodeReviewComment(self): |
636 | @@ -923,7 +940,8 @@ | |||
637 | 923 | comment = self.factory.makeCodeReviewComment(git=True) | 940 | comment = self.factory.makeCodeReviewComment(git=True) |
638 | 924 | comment_id = comment.id | 941 | comment_id = comment.id |
639 | 925 | repository = comment.branch_merge_proposal.source_git_repository | 942 | repository = comment.branch_merge_proposal.source_git_repository |
641 | 926 | repository.destroySelf(break_references=True) | 943 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
642 | 944 | repository.destroySelf(break_references=True) | ||
643 | 927 | self.assertRaises( | 945 | self.assertRaises( |
644 | 928 | SQLObjectNotFound, CodeReviewComment.get, comment_id) | 946 | SQLObjectNotFound, CodeReviewComment.get, comment_id) |
645 | 929 | 947 | ||
646 | @@ -932,7 +950,8 @@ | |||
647 | 932 | comment = self.factory.makeCodeReviewComment(git=True) | 950 | comment = self.factory.makeCodeReviewComment(git=True) |
648 | 933 | comment_id = comment.id | 951 | comment_id = comment.id |
649 | 934 | repository = comment.branch_merge_proposal.target_git_repository | 952 | repository = comment.branch_merge_proposal.target_git_repository |
651 | 935 | repository.destroySelf(break_references=True) | 953 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
652 | 954 | repository.destroySelf(break_references=True) | ||
653 | 936 | self.assertRaises( | 955 | self.assertRaises( |
654 | 937 | SQLObjectNotFound, CodeReviewComment.get, comment_id) | 956 | SQLObjectNotFound, CodeReviewComment.get, comment_id) |
655 | 938 | 957 | ||
656 | @@ -941,14 +960,18 @@ | |||
657 | 941 | merge_proposal = self.factory.makeBranchMergeProposalForGit() | 960 | merge_proposal = self.factory.makeBranchMergeProposalForGit() |
658 | 942 | merge_proposal.nominateReviewer( | 961 | merge_proposal.nominateReviewer( |
659 | 943 | self.factory.makePerson(), self.factory.makePerson()) | 962 | self.factory.makePerson(), self.factory.makePerson()) |
661 | 944 | merge_proposal.source_git_repository.destroySelf(break_references=True) | 963 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
662 | 964 | merge_proposal.source_git_repository.destroySelf( | ||
663 | 965 | break_references=True) | ||
664 | 945 | 966 | ||
665 | 946 | def test_targetBranchWithCodeReviewVoteReference(self): | 967 | def test_targetBranchWithCodeReviewVoteReference(self): |
666 | 947 | # break_references handles CodeReviewVoteReference target repository. | 968 | # break_references handles CodeReviewVoteReference target repository. |
667 | 948 | merge_proposal = self.factory.makeBranchMergeProposalForGit() | 969 | merge_proposal = self.factory.makeBranchMergeProposalForGit() |
668 | 949 | merge_proposal.nominateReviewer( | 970 | merge_proposal.nominateReviewer( |
669 | 950 | self.factory.makePerson(), self.factory.makePerson()) | 971 | self.factory.makePerson(), self.factory.makePerson()) |
671 | 951 | merge_proposal.target_git_repository.destroySelf(break_references=True) | 972 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
672 | 973 | merge_proposal.target_git_repository.destroySelf( | ||
673 | 974 | break_references=True) | ||
674 | 952 | 975 | ||
675 | 953 | def test_code_import_requirements(self): | 976 | def test_code_import_requirements(self): |
676 | 954 | # Code imports are not included explicitly in deletion requirements. | 977 | # Code imports are not included explicitly in deletion requirements. |
677 | @@ -967,7 +990,8 @@ | |||
678 | 967 | code_import = self.factory.makeCodeImport( | 990 | code_import = self.factory.makeCodeImport( |
679 | 968 | target_rcs_type=TargetRevisionControlSystems.GIT) | 991 | target_rcs_type=TargetRevisionControlSystems.GIT) |
680 | 969 | code_import_id = code_import.id | 992 | code_import_id = code_import.id |
682 | 970 | code_import.git_repository.destroySelf(break_references=True) | 993 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
683 | 994 | code_import.git_repository.destroySelf(break_references=True) | ||
684 | 971 | self.assertRaises( | 995 | self.assertRaises( |
685 | 972 | NotFoundError, getUtility(ICodeImportSet).get, code_import_id) | 996 | NotFoundError, getUtility(ICodeImportSet).get, code_import_id) |
686 | 973 | 997 | ||
687 | @@ -988,7 +1012,8 @@ | |||
688 | 988 | repository=repository, paths=["refs/heads/1", "refs/heads/2"]) | 1012 | repository=repository, paths=["refs/heads/1", "refs/heads/2"]) |
689 | 989 | snap1 = self.factory.makeSnap(git_ref=ref1) | 1013 | snap1 = self.factory.makeSnap(git_ref=ref1) |
690 | 990 | snap2 = self.factory.makeSnap(git_ref=ref2) | 1014 | snap2 = self.factory.makeSnap(git_ref=ref2) |
692 | 991 | repository.destroySelf(break_references=True) | 1015 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
693 | 1016 | repository.destroySelf(break_references=True) | ||
694 | 992 | transaction.commit() | 1017 | transaction.commit() |
695 | 993 | self.assertIsNone(snap1.git_repository) | 1018 | self.assertIsNone(snap1.git_repository) |
696 | 994 | self.assertIsNone(snap1.git_path) | 1019 | self.assertIsNone(snap1.git_path) |
697 | @@ -1001,7 +1026,8 @@ | |||
698 | 1001 | merge_proposal = removeSecurityProxy(self.makeMergeProposals()[0]) | 1026 | merge_proposal = removeSecurityProxy(self.makeMergeProposals()[0]) |
699 | 1002 | with person_logged_in( | 1027 | with person_logged_in( |
700 | 1003 | merge_proposal.prerequisite_git_repository.owner): | 1028 | merge_proposal.prerequisite_git_repository.owner): |
702 | 1004 | ClearPrerequisiteRepository(merge_proposal)() | 1029 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
703 | 1030 | ClearPrerequisiteRepository(merge_proposal)() | ||
704 | 1005 | self.assertIsNone(merge_proposal.prerequisite_git_repository) | 1031 | self.assertIsNone(merge_proposal.prerequisite_git_repository) |
705 | 1006 | 1032 | ||
706 | 1007 | def test_DeletionOperation(self): | 1033 | def test_DeletionOperation(self): |
707 | @@ -1012,8 +1038,9 @@ | |||
708 | 1012 | # DeletionCallable must invoke the callable. | 1038 | # DeletionCallable must invoke the callable. |
709 | 1013 | merge_proposal = self.factory.makeBranchMergeProposalForGit() | 1039 | merge_proposal = self.factory.makeBranchMergeProposalForGit() |
710 | 1014 | merge_proposal_id = merge_proposal.id | 1040 | merge_proposal_id = merge_proposal.id |
713 | 1015 | DeletionCallable( | 1041 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
714 | 1016 | merge_proposal, "blah", merge_proposal.deleteProposal)() | 1042 | DeletionCallable( |
715 | 1043 | merge_proposal, "blah", merge_proposal.deleteProposal)() | ||
716 | 1017 | self.assertRaises( | 1044 | self.assertRaises( |
717 | 1018 | SQLObjectNotFound, BranchMergeProposal.get, merge_proposal_id) | 1045 | SQLObjectNotFound, BranchMergeProposal.get, merge_proposal_id) |
718 | 1019 | 1046 | ||
719 | @@ -1023,7 +1050,8 @@ | |||
720 | 1023 | code_import = self.factory.makeCodeImport( | 1050 | code_import = self.factory.makeCodeImport( |
721 | 1024 | target_rcs_type=TargetRevisionControlSystems.GIT) | 1051 | target_rcs_type=TargetRevisionControlSystems.GIT) |
722 | 1025 | code_import_id = code_import.id | 1052 | code_import_id = code_import.id |
724 | 1026 | DeleteCodeImport(code_import)() | 1053 | with dbuser(config.IGitRepositoryDeleteJobSource.dbuser): |
725 | 1054 | DeleteCodeImport(code_import)() | ||
726 | 1027 | self.assertRaises( | 1055 | self.assertRaises( |
727 | 1028 | NotFoundError, getUtility(ICodeImportSet).get, code_import_id) | 1056 | NotFoundError, getUtility(ICodeImportSet).get, code_import_id) |
728 | 1029 | 1057 | ||
729 | 1030 | 1058 | ||
730 | === modified file 'lib/lp/services/config/schema-lazr.conf' | |||
731 | --- lib/lp/services/config/schema-lazr.conf 2019-03-21 16:22:19 +0000 | |||
732 | +++ lib/lp/services/config/schema-lazr.conf 2019-03-21 16:22:19 +0000 | |||
733 | @@ -1871,6 +1871,7 @@ | |||
734 | 1871 | IBranchModifiedMailJobSource, | 1871 | IBranchModifiedMailJobSource, |
735 | 1872 | ICommercialExpiredJobSource, | 1872 | ICommercialExpiredJobSource, |
736 | 1873 | IExpiringMembershipNotificationJobSource, | 1873 | IExpiringMembershipNotificationJobSource, |
737 | 1874 | IGitRepositoryDeleteJobSource, | ||
738 | 1874 | IGitRepositoryModifiedMailJobSource, | 1875 | IGitRepositoryModifiedMailJobSource, |
739 | 1875 | IMembershipNotificationJobSource, | 1876 | IMembershipNotificationJobSource, |
740 | 1876 | IPackageUploadNotificationJobSource, | 1877 | IPackageUploadNotificationJobSource, |
741 | @@ -1931,6 +1932,11 @@ | |||
742 | 1931 | module: lp.code.interfaces.gitjob | 1932 | module: lp.code.interfaces.gitjob |
743 | 1932 | dbuser: branchscanner | 1933 | dbuser: branchscanner |
744 | 1933 | 1934 | ||
745 | 1935 | [IGitRepositoryDeleteJobSource] | ||
746 | 1936 | module: lp.code.interfaces.gitjob | ||
747 | 1937 | dbuser: branch-delete-job | ||
748 | 1938 | crontab_group: MAIN | ||
749 | 1939 | |||
750 | 1934 | [IGitRepositoryModifiedMailJobSource] | 1940 | [IGitRepositoryModifiedMailJobSource] |
751 | 1935 | module: lp.code.interfaces.gitjob | 1941 | module: lp.code.interfaces.gitjob |
752 | 1936 | dbuser: send-branch-mail | 1942 | dbuser: send-branch-mail |
This seems unnecessary since adding some more indexes has made deletion fast enough, so withdrawing this until we have a clear need.