14538.1.3
by Curtis Hovey
Updated copyright. |
1 |
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
|
8687.15.17
by Karl Fogel
Add the copyright header block to the rest of the files under lib/lp/. |
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
3 |
||
4983.1.2
by Curtis Hovey
Added pylint exceptions to database classes. |
4 |
# pylint: disable-msg=E0611,W0212
|
1102.1.63
by David Allouche
Revision and RevisionAuthor, remove ArchUserID |
5 |
|
6 |
__metaclass__ = type |
|
3849.1.22
by jml at canonical
Add RevisionProperty class and interface. |
7 |
__all__ = [ |
7675.169.3
by Tim Penhey
Add methods to populate and prune the RevisionCache. |
8 |
'Revision', |
9 |
'RevisionAuthor', |
|
10 |
'RevisionCache', |
|
11 |
'RevisionParent', |
|
12 |
'RevisionProperty', |
|
3849.1.22
by jml at canonical
Add RevisionProperty class and interface. |
13 |
'RevisionSet'] |
1102.1.63
by David Allouche
Revision and RevisionAuthor, remove ArchUserID |
14 |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
15 |
from datetime import ( |
16 |
datetime, |
|
17 |
timedelta, |
|
18 |
)
|
|
4177.3.2
by Jonathan Lange
Add 'getNameWithoutEmail' to get just the name of the revision author. |
19 |
import email |
20 |
||
11486.2.4
by Aaron Bentley
Add Revision.getLefthandParent. |
21 |
from bzrlib.revision import NULL_REVISION |
6096.5.5
by Tim Penhey
initial work |
22 |
import pytz |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
23 |
from sqlobject import ( |
24 |
BoolCol, |
|
25 |
ForeignKey, |
|
26 |
IntCol, |
|
27 |
SQLMultipleJoin, |
|
28 |
SQLObjectNotFound, |
|
29 |
StringCol, |
|
30 |
)
|
|
31 |
from storm.expr import ( |
|
32 |
And, |
|
33 |
Asc, |
|
34 |
Desc, |
|
35 |
Join, |
|
36 |
Not, |
|
37 |
Or, |
|
38 |
Select, |
|
39 |
)
|
|
40 |
from storm.locals import ( |
|
41 |
Bool, |
|
42 |
Int, |
|
43 |
Min, |
|
44 |
Reference, |
|
45 |
Storm, |
|
46 |
)
|
|
6736.3.1
by Tim Penhey
Initial work. |
47 |
from storm.store import Store |
5543.7.3
by Tim Penhey
New revisions now check to see if we know the author. |
48 |
from zope.component import getUtility |
1102.1.63
by David Allouche
Revision and RevisionAuthor, remove ArchUserID |
49 |
from zope.interface import implements |
7675.169.3
by Tim Penhey
Add methods to populate and prune the RevisionCache. |
50 |
from zope.security.proxy import removeSecurityProxy |
1102.1.63
by David Allouche
Revision and RevisionAuthor, remove ArchUserID |
51 |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
52 |
from canonical.database.constants import ( |
53 |
DEFAULT, |
|
54 |
UTC_NOW, |
|
55 |
)
|
|
7925.4.1
by Tim Penhey
Don't create revisions with future revision_dates. |
56 |
from canonical.database.datetimecol import UtcDateTimeCol |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
57 |
from canonical.database.sqlbase import ( |
58 |
quote, |
|
59 |
SQLBase, |
|
60 |
sqlvalues, |
|
61 |
)
|
|
8590.2.2
by Tim Penhey
Fix the fallout in the zcml and other imports. |
62 |
from canonical.launchpad.helpers import shortlist |
14538.1.2
by Curtis Hovey
Moved account and email address to lp.services.identity. |
63 |
from lp.services.identity.interfaces.emailaddress import ( |
11262.3.4
by Curtis Hovey
On import per line. |
64 |
EmailAddressStatus, |
65 |
IEmailAddressSet, |
|
66 |
)
|
|
11693.1.1
by Stuart Bishop
Fix performance of revision karma allocator under PostgreSQL 8.4 |
67 |
from canonical.launchpad.interfaces.lpstorm import IMasterStore, IStore |
8590.2.2
by Tim Penhey
Fix the fallout in the zcml and other imports. |
68 |
from canonical.launchpad.webapp.interfaces import ( |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
69 |
DEFAULT_FLAVOR, |
70 |
IStoreSelector, |
|
71 |
MAIN_STORE, |
|
72 |
)
|
|
8590.2.2
by Tim Penhey
Fix the fallout in the zcml and other imports. |
73 |
from lp.code.interfaces.branch import DEFAULT_BRANCH_STATUS_IN_LISTING |
74 |
from lp.code.interfaces.revision import ( |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
75 |
IRevision, |
76 |
IRevisionAuthor, |
|
77 |
IRevisionParent, |
|
78 |
IRevisionProperty, |
|
79 |
IRevisionSet, |
|
80 |
)
|
|
81 |
from lp.registry.interfaces.person import validate_public_person |
|
7675.110.3
by Curtis Hovey
Ran the migration script to move registry code to lp.registry. |
82 |
from lp.registry.interfaces.product import IProduct |
10326.1.2
by Henning Eggers
Renamed project interfaces module to projectgroup. |
83 |
from lp.registry.interfaces.projectgroup import IProjectGroup |
12337.2.10
by Jeroen Vermeulen
Extend and reuse PersonSet.getPrecachedPersonsFromIDs. |
84 |
from lp.registry.model.person import ValidPersonCache |
1102.1.63
by David Allouche
Revision and RevisionAuthor, remove ArchUserID |
85 |
|
86 |
||
87 |
class Revision(SQLBase): |
|
88 |
"""See IRevision."""
|
|
89 |
||
90 |
implements(IRevision) |
|
91 |
||
92 |
date_created = UtcDateTimeCol(notNull=True, default=DEFAULT) |
|
93 |
log_body = StringCol(notNull=True) |
|
1102.1.143
by David Allouche
Review fixes to Branch view. |
94 |
gpgkey = ForeignKey(dbName='gpgkey', foreignKey='GPGKey', default=None) |
95 |
||
12337.2.3
by Jeroen Vermeulen
Use formatter for RevisionAuthor; prefetch author info. |
96 |
revision_author_id = Int(name='revision_author', allow_none=False) |
97 |
revision_author = Reference(revision_author_id, 'RevisionAuthor.id') |
|
98 |
||
3504.2.10
by James Henstridge
changes suggested by spiv in review |
99 |
revision_id = StringCol(notNull=True, alternateID=True, |
100 |
alternateMethodName='byRevisionID') |
|
1102.1.63
by David Allouche
Revision and RevisionAuthor, remove ArchUserID |
101 |
revision_date = UtcDateTimeCol(notNull=False) |
102 |
||
6623.4.1
by Tim Penhey
Initial karma allocation done. TODO: karma when revision author linked. |
103 |
karma_allocated = BoolCol(default=False, notNull=True) |
104 |
||
3849.1.22
by jml at canonical
Add RevisionProperty class and interface. |
105 |
properties = SQLMultipleJoin('RevisionProperty', joinColumn='revision') |
106 |
||
1102.1.123
by david
schema supports ghost revisions |
107 |
@property
|
3691.25.5
by James Henstridge
get rid of direct RevisionParent use in bzrsync |
108 |
def parents(self): |
109 |
"""See IRevision.parents"""
|
|
110 |
return shortlist(RevisionParent.selectBy( |
|
3691.62.21
by kiko
Clean up the use of ID/.id in select*By and constructors |
111 |
revision=self, orderBy='sequence')) |
3691.25.5
by James Henstridge
get rid of direct RevisionParent use in bzrsync |
112 |
|
113 |
@property
|
|
1102.1.123
by david
schema supports ghost revisions |
114 |
def parent_ids(self): |
1102.1.147
by David Allouche
review fixes: launchpad.database.revision |
115 |
"""Sequence of globally unique ids for the parents of this revision.
|
116 |
||
117 |
The corresponding Revision objects can be retrieved, if they are
|
|
118 |
present in the database, using the RevisionSet Zope utility.
|
|
119 |
"""
|
|
3691.25.5
by James Henstridge
get rid of direct RevisionParent use in bzrsync |
120 |
return [parent.parent_id for parent in self.parents] |
1102.1.116
by david
TODOs for branch.txt and revision.py |
121 |
|
11486.2.4
by Aaron Bentley
Add Revision.getLefthandParent. |
122 |
def getLefthandParent(self): |
123 |
if len(self.parent_ids) == 0: |
|
124 |
parent_id = NULL_REVISION |
|
125 |
else: |
|
126 |
parent_id = self.parent_ids[0] |
|
127 |
return RevisionSet().getByRevisionId(parent_id) |
|
128 |
||
3849.1.23
by jml at canonical
Add getProperties method to return the properties as a dict. |
129 |
def getProperties(self): |
6623.4.1
by Tim Penhey
Initial karma allocation done. TODO: karma when revision author linked. |
130 |
"""See `IRevision`."""
|
3849.1.23
by jml at canonical
Add getProperties method to return the properties as a dict. |
131 |
return dict((prop.name, prop.value) for prop in self.properties) |
132 |
||
6623.4.1
by Tim Penhey
Initial karma allocation done. TODO: karma when revision author linked. |
133 |
def allocateKarma(self, branch): |
134 |
"""See `IRevision`."""
|
|
135 |
# If we know who the revision author is, give them karma.
|
|
136 |
author = self.revision_author.person |
|
7675.439.12
by Tim Penhey
Remove unneeded parentheses. |
137 |
if author is not None: |
6950.1.3
by Tim Penhey
Updates following review. |
138 |
# Backdate the karma to the time the revision was created. If the
|
139 |
# revision_date on the revision is in future (for whatever weird
|
|
140 |
# reason) we will use the date_created from the revision (which
|
|
141 |
# will be now) as the karma date created. Having future karma
|
|
142 |
# events is both wrong, as the revision has been created (and it
|
|
143 |
# is lying), and a problem with the way the Launchpad code
|
|
144 |
# currently does its karma degradation over time.
|
|
7675.439.13
by Tim Penhey
change how we allocate backdated karma |
145 |
karma_date = min(self.revision_date, self.date_created) |
146 |
karma = branch.target.assignKarma( |
|
147 |
author, 'revisionadded', karma_date) |
|
6623.4.23
by Tim Penhey
Fix a bug with attempting to allocate karma to an inactive person, and fix logging in the cron script. |
148 |
if karma is not None: |
149 |
self.karma_allocated = True |
|
7675.439.5
by Tim Penhey
Make the test_karmaDateForFutureRevisions actually test what it says it is testing. |
150 |
return karma |
151 |
else: |
|
152 |
return None |
|
6623.4.1
by Tim Penhey
Initial karma allocation done. TODO: karma when revision author linked. |
153 |
|
6623.4.25
by Tim Penhey
Make sure we don't try to allocate karma to junk branches. |
154 |
def getBranch(self, allow_private=False, allow_junk=True): |
6736.3.4
by Tim Penhey
Added tests for addition Revision and RevisionSet methods. |
155 |
"""See `IRevision`."""
|
8138.1.2
by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing. |
156 |
from lp.code.model.branch import Branch |
157 |
from lp.code.model.branchrevision import BranchRevision |
|
6736.3.4
by Tim Penhey
Added tests for addition Revision and RevisionSet methods. |
158 |
|
159 |
store = Store.of(self) |
|
160 |
||
6623.4.6
by Tim Penhey
Hook up the claiming of older revisions. |
161 |
query = And( |
7675.747.1
by Jeroen Vermeulen
Intermediate state: stormifying BranchRevision. |
162 |
self.id == BranchRevision.revision_id, |
163 |
BranchRevision.branch_id == Branch.id) |
|
13931.4.3
by Ian Booth
Add new transitively_private attribute on branch |
164 |
if not allow_private: |
165 |
query = And(query, Not(Branch.transitively_private)) |
|
13875.2.2
by Ian Booth
Use new Storm recursive With and make implementation more efficient by using early filtering |
166 |
if not allow_junk: |
13931.4.3
by Ian Booth
Add new transitively_private attribute on branch |
167 |
query = And( |
168 |
query, |
|
169 |
# Not-junk branches are either associated with a product
|
|
170 |
# or with a source package.
|
|
171 |
Or( |
|
13875.2.2
by Ian Booth
Use new Storm recursive With and make implementation more efficient by using early filtering |
172 |
(Branch.product != None), |
173 |
And( |
|
174 |
Branch.sourcepackagename != None, |
|
13931.4.3
by Ian Booth
Add new transitively_private attribute on branch |
175 |
Branch.distroseries != None))) |
6623.4.6
by Tim Penhey
Hook up the claiming of older revisions. |
176 |
result_set = store.find(Branch, query) |
6736.3.4
by Tim Penhey
Added tests for addition Revision and RevisionSet methods. |
177 |
if self.revision_author.person is None: |
178 |
result_set.order_by(Asc(BranchRevision.sequence)) |
|
179 |
else: |
|
180 |
result_set.order_by( |
|
181 |
Branch.ownerID != self.revision_author.personID, |
|
182 |
Asc(BranchRevision.sequence)) |
|
183 |
||
184 |
return result_set.first() |
|
185 |
||
1102.1.63
by David Allouche
Revision and RevisionAuthor, remove ArchUserID |
186 |
|
187 |
class RevisionAuthor(SQLBase): |
|
188 |
implements(IRevisionAuthor) |
|
189 |
||
190 |
_table = 'RevisionAuthor' |
|
191 |
||
3504.2.10
by James Henstridge
changes suggested by spiv in review |
192 |
name = StringCol(notNull=True, alternateID=True) |
1102.1.63
by David Allouche
Revision and RevisionAuthor, remove ArchUserID |
193 |
|
5743.2.6
by Tim Penhey
Updates following review |
194 |
@property
|
195 |
def name_without_email(self): |
|
4177.3.2
by Jonathan Lange
Add 'getNameWithoutEmail' to get just the name of the revision author. |
196 |
"""Return the name of the revision author without the email address.
|
197 |
||
198 |
If there is no name information (i.e. when the revision author only
|
|
199 |
supplied their email address), return None.
|
|
200 |
"""
|
|
5743.2.2
by Tim Penhey
Listing now has revision number and codebrowse hyperlink. |
201 |
if '@' not in self.name: |
202 |
return self.name |
|
4177.3.9
by Jonathan Lange
Return empty string from name_without_email if there's no name, and display |
203 |
return email.Utils.parseaddr(self.name)[0] |
4177.3.2
by Jonathan Lange
Add 'getNameWithoutEmail' to get just the name of the revision author. |
204 |
|
5543.7.8
by Tim Penhey
Giving email a default. |
205 |
email = StringCol(notNull=False, default=None) |
5543.7.3
by Tim Penhey
New revisions now check to see if we know the author. |
206 |
person = ForeignKey(dbName='person', foreignKey='Person', notNull=False, |
5821.2.40
by James Henstridge
* Move all the uses of public_person_validator over to the Storm |
207 |
storm_validator=validate_public_person, default=None) |
5543.7.3
by Tim Penhey
New revisions now check to see if we know the author. |
208 |
|
209 |
def linkToLaunchpadPerson(self): |
|
210 |
"""See `IRevisionAuthor`."""
|
|
211 |
if self.person is not None or self.email is None: |
|
212 |
return False |
|
213 |
lp_email = getUtility(IEmailAddressSet).getByEmail(self.email) |
|
214 |
# If not found, we didn't link this person.
|
|
215 |
if lp_email is None: |
|
216 |
return False |
|
217 |
# Only accept an email address that is validated.
|
|
5543.7.4
by Tim Penhey
All done. |
218 |
if lp_email.status != EmailAddressStatus.NEW: |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
219 |
self.personID = lp_email.personID |
5543.7.3
by Tim Penhey
New revisions now check to see if we know the author. |
220 |
return True |
221 |
else: |
|
222 |
return False |
|
5543.7.2
by Tim Penhey
Removed the Revision.author field, and add interface and database bits for RevisionAuthor email and person fields. |
223 |
|
1102.1.123
by david
schema supports ghost revisions |
224 |
|
225 |
class RevisionParent(SQLBase): |
|
226 |
"""The association between a revision and its parent."""
|
|
227 |
||
228 |
implements(IRevisionParent) |
|
229 |
||
230 |
_table = 'RevisionParent' |
|
231 |
||
232 |
revision = ForeignKey( |
|
233 |
dbName='revision', foreignKey='Revision', notNull=True) |
|
1102.1.147
by David Allouche
review fixes: launchpad.database.revision |
234 |
|
1102.1.123
by david
schema supports ghost revisions |
235 |
sequence = IntCol(notNull=True) |
236 |
parent_id = StringCol(notNull=True) |
|
237 |
||
238 |
||
3849.1.22
by jml at canonical
Add RevisionProperty class and interface. |
239 |
class RevisionProperty(SQLBase): |
240 |
"""A property on a revision. See IRevisionProperty."""
|
|
241 |
||
242 |
implements(IRevisionProperty) |
|
243 |
||
244 |
_table = 'RevisionProperty' |
|
245 |
||
246 |
revision = ForeignKey( |
|
247 |
dbName='revision', foreignKey='Revision', notNull=True) |
|
248 |
name = StringCol(notNull=True) |
|
249 |
value = StringCol(notNull=True) |
|
250 |
||
251 |
||
1102.1.126
by david
add RevisionSet |
252 |
class RevisionSet: |
253 |
||
254 |
implements(IRevisionSet) |
|
255 |
||
256 |
def getByRevisionId(self, revision_id): |
|
257 |
return Revision.selectOneBy(revision_id=revision_id) |
|
3691.25.3
by James Henstridge
add IRevisionSet.new() |
258 |
|
5543.7.3
by Tim Penhey
New revisions now check to see if we know the author. |
259 |
def _createRevisionAuthor(self, revision_author): |
260 |
"""Extract out the email and check to see if it matches a Person."""
|
|
261 |
email_address = email.Utils.parseaddr(revision_author)[1] |
|
262 |
# If there is no @, then it isn't a real email address.
|
|
263 |
if '@' not in email_address: |
|
264 |
email_address = None |
|
5543.7.4
by Tim Penhey
All done. |
265 |
|
266 |
author = RevisionAuthor(name=revision_author, email=email_address) |
|
267 |
author.linkToLaunchpadPerson() |
|
268 |
return author |
|
5543.7.3
by Tim Penhey
New revisions now check to see if we know the author. |
269 |
|
5485.1.18
by Edwin Grubbs
Fixed lots of lint issues |
270 |
def new(self, revision_id, log_body, revision_date, revision_author, |
9984.4.2
by Tim Penhey
Allow the passing of date_created to the factory method to create revisions. |
271 |
parent_ids, properties, _date_created=None): |
3691.25.3
by James Henstridge
add IRevisionSet.new() |
272 |
"""See IRevisionSet.new()"""
|
3849.1.25
by jml at canonical
Store the revision properties. |
273 |
if properties is None: |
274 |
properties = {} |
|
9984.4.2
by Tim Penhey
Allow the passing of date_created to the factory method to create revisions. |
275 |
if _date_created is None: |
276 |
_date_created = UTC_NOW |
|
8716.4.2
by Aaron Bentley
Use Revision authors and Peson.unique_displayname where possible. |
277 |
author = self.acquireRevisionAuthor(revision_author) |
3691.25.3
by James Henstridge
add IRevisionSet.new() |
278 |
|
9984.4.2
by Tim Penhey
Allow the passing of date_created to the factory method to create revisions. |
279 |
revision = Revision( |
280 |
revision_id=revision_id, |
|
281 |
log_body=log_body, |
|
282 |
revision_date=revision_date, |
|
283 |
revision_author=author, |
|
284 |
date_created=_date_created) |
|
7925.4.1
by Tim Penhey
Don't create revisions with future revision_dates. |
285 |
# Don't create future revisions.
|
286 |
if revision.revision_date > revision.date_created: |
|
287 |
revision.revision_date = revision.date_created |
|
288 |
||
3691.25.3
by James Henstridge
add IRevisionSet.new() |
289 |
seen_parents = set() |
290 |
for sequence, parent_id in enumerate(parent_ids): |
|
291 |
if parent_id in seen_parents: |
|
292 |
continue
|
|
293 |
seen_parents.add(parent_id) |
|
3691.62.21
by kiko
Clean up the use of ID/.id in select*By and constructors |
294 |
RevisionParent(revision=revision, sequence=sequence, |
3691.25.3
by James Henstridge
add IRevisionSet.new() |
295 |
parent_id=parent_id) |
3849.1.25
by jml at canonical
Store the revision properties. |
296 |
|
297 |
# Create revision properties.
|
|
298 |
for name, value in properties.iteritems(): |
|
299 |
RevisionProperty(revision=revision, name=name, value=value) |
|
300 |
||
3691.25.3
by James Henstridge
add IRevisionSet.new() |
301 |
return revision |
5543.7.4
by Tim Penhey
All done. |
302 |
|
8716.4.2
by Aaron Bentley
Use Revision authors and Peson.unique_displayname where possible. |
303 |
def acquireRevisionAuthor(self, name): |
304 |
"""Find or create the RevisionAuthor with the specified name.
|
|
305 |
||
306 |
Name may be any arbitrary string, but if it is an email-id, and
|
|
307 |
its email address is a verified email address, it will be
|
|
308 |
automatically linked to the corresponding Person.
|
|
309 |
||
310 |
Email-ids come in two major forms:
|
|
311 |
"Foo Bar" <foo@bar.com>
|
|
312 |
foo@bar.com (Foo Bar)
|
|
313 |
"""
|
|
314 |
# create a RevisionAuthor if necessary:
|
|
315 |
try: |
|
316 |
return RevisionAuthor.byName(name) |
|
317 |
except SQLObjectNotFound: |
|
318 |
return self._createRevisionAuthor(name) |
|
319 |
||
6789.3.9
by Jonathan Lange
Move the revision from bzr revision method into RevisionSet. |
320 |
def _timestampToDatetime(self, timestamp): |
321 |
"""Convert the given timestamp to a datetime object.
|
|
322 |
||
323 |
This works around a bug in Python that causes datetime.fromtimestamp
|
|
324 |
to raise an exception if it is given a negative, fractional timestamp.
|
|
325 |
||
326 |
:param timestamp: A timestamp from a bzrlib.revision.Revision
|
|
327 |
:type timestamp: float
|
|
328 |
||
329 |
:return: A datetime corresponding to the given timestamp.
|
|
330 |
"""
|
|
331 |
# Work around Python bug #1646728.
|
|
332 |
# See https://launchpad.net/bugs/81544.
|
|
333 |
UTC = pytz.timezone('UTC') |
|
334 |
int_timestamp = int(timestamp) |
|
335 |
revision_date = datetime.fromtimestamp(int_timestamp, tz=UTC) |
|
336 |
revision_date += timedelta(seconds=timestamp - int_timestamp) |
|
337 |
return revision_date |
|
338 |
||
339 |
def newFromBazaarRevision(self, bzr_revision): |
|
340 |
"""See `IRevisionSet`."""
|
|
341 |
revision_id = bzr_revision.revision_id |
|
342 |
revision_date = self._timestampToDatetime(bzr_revision.timestamp) |
|
8303.5.3
by Jonathan Lange
Looks like revisions might not actually have authors. |
343 |
authors = bzr_revision.get_apparent_authors() |
344 |
# XXX: JonathanLange 2009-05-01 bug=362686: We can only have one
|
|
345 |
# author per revision, so we use the first on the assumption that
|
|
346 |
# this is the primary author.
|
|
347 |
try: |
|
13269.2.6
by Jonathan Lange
Avoid unnecessary work. |
348 |
author = authors[0] |
8303.5.3
by Jonathan Lange
Looks like revisions might not actually have authors. |
349 |
except IndexError: |
350 |
author = None |
|
6789.3.9
by Jonathan Lange
Move the revision from bzr revision method into RevisionSet. |
351 |
return self.new( |
352 |
revision_id=revision_id, |
|
353 |
log_body=bzr_revision.message, |
|
354 |
revision_date=revision_date, |
|
8303.5.3
by Jonathan Lange
Looks like revisions might not actually have authors. |
355 |
revision_author=author, |
6789.3.9
by Jonathan Lange
Move the revision from bzr revision method into RevisionSet. |
356 |
parent_ids=bzr_revision.parent_ids, |
357 |
properties=bzr_revision.properties) |
|
358 |
||
7049.1.1
by Michael Hudson
hacking graph yoga to determine revisions to insert |
359 |
@staticmethod
|
7049.1.9
by Michael Hudson
more tidying |
360 |
def onlyPresent(revids): |
7049.1.18
by Michael Hudson
review comments |
361 |
"""See `IRevisionSet`."""
|
7049.1.10
by Michael Hudson
more sql, less graph yoga |
362 |
if not revids: |
363 |
return set() |
|
7049.1.1
by Michael Hudson
hacking graph yoga to determine revisions to insert |
364 |
store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) |
7049.1.10
by Michael Hudson
more sql, less graph yoga |
365 |
store.execute( |
366 |
"""
|
|
367 |
CREATE TEMPORARY TABLE Revids
|
|
368 |
(revision_id text)
|
|
369 |
""") |
|
370 |
data = [] |
|
7049.1.16
by Michael Hudson
docstrings, comments |
371 |
for revid in revids: |
372 |
data.append('(%s)' % sqlvalues(revid)) |
|
7049.1.10
by Michael Hudson
more sql, less graph yoga |
373 |
data = ', '.join(data) |
374 |
store.execute( |
|
375 |
"INSERT INTO Revids (revision_id) VALUES %s" % data) |
|
376 |
result = store.execute( |
|
377 |
"""
|
|
378 |
SELECT Revids.revision_id
|
|
379 |
FROM Revids, Revision
|
|
380 |
WHERE Revids.revision_id = Revision.revision_id
|
|
381 |
""") |
|
382 |
present = set() |
|
383 |
for row in result.get_all(): |
|
384 |
present.add(row[0]) |
|
385 |
store.execute("DROP TABLE Revids") |
|
386 |
return present |
|
7049.1.1
by Michael Hudson
hacking graph yoga to determine revisions to insert |
387 |
|
5543.7.4
by Tim Penhey
All done. |
388 |
def checkNewVerifiedEmail(self, email): |
389 |
"""See `IRevisionSet`."""
|
|
6596.1.3
by Guilherme Salgado
Some small changes requested by Gavin. |
390 |
# Bypass zope's security because IEmailAddress.email is not public.
|
6596.1.1
by Guilherme Salgado
Fix https://launchpad.net/bugs/241332 |
391 |
naked_email = removeSecurityProxy(email) |
392 |
for author in RevisionAuthor.selectBy(email=naked_email.email): |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
393 |
author.personID = email.personID |
5743.2.2
by Tim Penhey
Listing now has revision number and codebrowse hyperlink. |
394 |
|
395 |
def getTipRevisionsForBranches(self, branches): |
|
396 |
"""See `IRevisionSet`."""
|
|
5743.2.6
by Tim Penhey
Updates following review |
397 |
# If there are no branch_ids, then return None.
|
5743.2.2
by Tim Penhey
Listing now has revision number and codebrowse hyperlink. |
398 |
branch_ids = [branch.id for branch in branches] |
399 |
if not branch_ids: |
|
5743.2.6
by Tim Penhey
Updates following review |
400 |
return None |
5743.2.2
by Tim Penhey
Listing now has revision number and codebrowse hyperlink. |
401 |
return Revision.select(""" |
402 |
Branch.id in %s AND |
|
403 |
Revision.revision_id = Branch.last_scanned_id
|
|
404 |
""" % quote(branch_ids), |
|
405 |
clauseTables=['Branch'], prejoins=['revision_author']) |
|
6096.5.5
by Tim Penhey
initial work |
406 |
|
7028.1.1
by Tim Penhey
Speed up the query that is used to count the number of new revisions shown on the +code-index and +branches pages for products. |
407 |
@staticmethod
|
408 |
def getRecentRevisionsForProduct(product, days): |
|
6096.5.5
by Tim Penhey
initial work |
409 |
"""See `IRevisionSet`."""
|
7028.1.1
by Tim Penhey
Speed up the query that is used to count the number of new revisions shown on the +code-index and +branches pages for products. |
410 |
# Here to stop circular imports.
|
8138.1.2
by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing. |
411 |
from lp.code.model.branch import Branch |
412 |
from lp.code.model.branchrevision import BranchRevision |
|
7028.1.1
by Tim Penhey
Speed up the query that is used to count the number of new revisions shown on the +code-index and +branches pages for products. |
413 |
|
7381.1.2
by Paul Hummer
Refactored Revision.getRecentRevisionsForProduct |
414 |
revision_subselect = Select( |
415 |
Min(Revision.id), revision_time_limit(days)) |
|
8079.2.2
by Tim Penhey
Only look in active branches for recent revisions. |
416 |
# Only look in active branches.
|
7381.1.2
by Paul Hummer
Refactored Revision.getRecentRevisionsForProduct |
417 |
result_set = Store.of(product).find( |
7028.1.1
by Tim Penhey
Speed up the query that is used to count the number of new revisions shown on the +code-index and +branches pages for products. |
418 |
(Revision, RevisionAuthor), |
7381.1.2
by Paul Hummer
Refactored Revision.getRecentRevisionsForProduct |
419 |
Revision.revision_author == RevisionAuthor.id, |
7028.1.1
by Tim Penhey
Speed up the query that is used to count the number of new revisions shown on the +code-index and +branches pages for products. |
420 |
revision_time_limit(days), |
7381.1.2
by Paul Hummer
Refactored Revision.getRecentRevisionsForProduct |
421 |
BranchRevision.revision == Revision.id, |
422 |
BranchRevision.branch == Branch.id, |
|
423 |
Branch.product == product, |
|
8079.2.2
by Tim Penhey
Only look in active branches for recent revisions. |
424 |
Branch.lifecycle_status.is_in(DEFAULT_BRANCH_STATUS_IN_LISTING), |
7675.747.1
by Jeroen Vermeulen
Intermediate state: stormifying BranchRevision. |
425 |
BranchRevision.revision_id >= revision_subselect) |
7381.1.2
by Paul Hummer
Refactored Revision.getRecentRevisionsForProduct |
426 |
result_set.config(distinct=True) |
427 |
return result_set.order_by(Desc(Revision.revision_date)) |
|
6736.3.1
by Tim Penhey
Initial work. |
428 |
|
429 |
@staticmethod
|
|
11693.1.1
by Stuart Bishop
Fix performance of revision karma allocator under PostgreSQL 8.4 |
430 |
def getRevisionsNeedingKarmaAllocated(limit=None): |
6623.4.15
by Tim Penhey
Method to get the revisions needing karma allocated. |
431 |
"""See `IRevisionSet`."""
|
432 |
# Here to stop circular imports.
|
|
8138.1.2
by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing. |
433 |
from lp.code.model.branch import Branch |
434 |
from lp.code.model.branchrevision import BranchRevision |
|
6623.4.15
by Tim Penhey
Method to get the revisions needing karma allocated. |
435 |
|
11693.1.1
by Stuart Bishop
Fix performance of revision karma allocator under PostgreSQL 8.4 |
436 |
store = IStore(Revision) |
437 |
results_with_dupes = store.find( |
|
6623.4.15
by Tim Penhey
Method to get the revisions needing karma allocated. |
438 |
Revision, |
439 |
Revision.revision_author == RevisionAuthor.id, |
|
6623.4.23
by Tim Penhey
Fix a bug with attempting to allocate karma to an inactive person, and fix logging in the cron script. |
440 |
RevisionAuthor.person == ValidPersonCache.id, |
6623.4.16
by Tim Penhey
Add a new cronscript to be run each night, and remove the creation of the historical karma revisions when a user validates an email address. This is now handled by the cronscript. |
441 |
Not(Revision.karma_allocated), |
11693.1.1
by Stuart Bishop
Fix performance of revision karma allocator under PostgreSQL 8.4 |
442 |
BranchRevision.revision == Revision.id, |
443 |
BranchRevision.branch == Branch.id, |
|
444 |
Or(Branch.product != None, Branch.distroseries != None))[:limit] |
|
445 |
# Eliminate duplicate rows, returning <= limit rows
|
|
446 |
return store.find( |
|
447 |
Revision, Revision.id.is_in( |
|
448 |
results_with_dupes.get_select_expr(Revision.id))) |
|
6623.4.15
by Tim Penhey
Method to get the revisions needing karma allocated. |
449 |
|
450 |
@staticmethod
|
|
6950.2.1
by Tim Penhey
Update the revision getting methods to be date bound to speed up queries. |
451 |
def getPublicRevisionsForPerson(person, day_limit=30): |
6736.3.1
by Tim Penhey
Initial work. |
452 |
"""See `IRevisionSet`."""
|
6736.3.4
by Tim Penhey
Added tests for addition Revision and RevisionSet methods. |
453 |
# Here to stop circular imports.
|
8138.1.2
by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing. |
454 |
from lp.code.model.branch import Branch |
455 |
from lp.code.model.branchrevision import BranchRevision |
|
7675.110.3
by Curtis Hovey
Ran the migration script to move registry code to lp.registry. |
456 |
from lp.registry.model.teammembership import ( |
6736.3.4
by Tim Penhey
Added tests for addition Revision and RevisionSet methods. |
457 |
TeamParticipation) |
458 |
||
6736.3.1
by Tim Penhey
Initial work. |
459 |
store = Store.of(person) |
460 |
||
7712.1.1
by Edwin Grubbs
Optimized queries. |
461 |
origin = [ |
462 |
Revision, |
|
463 |
Join(BranchRevision, BranchRevision.revision == Revision.id), |
|
464 |
Join(Branch, BranchRevision.branch == Branch.id), |
|
465 |
Join(RevisionAuthor, |
|
466 |
Revision.revision_author == RevisionAuthor.id), |
|
467 |
]
|
|
468 |
||
6736.3.1
by Tim Penhey
Initial work. |
469 |
if person.is_team: |
7712.1.1
by Edwin Grubbs
Optimized queries. |
470 |
origin.append( |
471 |
Join(TeamParticipation, |
|
472 |
RevisionAuthor.personID == TeamParticipation.personID)) |
|
473 |
person_condition = TeamParticipation.team == person |
|
6736.3.1
by Tim Penhey
Initial work. |
474 |
else: |
7712.1.1
by Edwin Grubbs
Optimized queries. |
475 |
person_condition = RevisionAuthor.person == person |
6736.3.1
by Tim Penhey
Initial work. |
476 |
|
7712.1.1
by Edwin Grubbs
Optimized queries. |
477 |
result_set = store.using(*origin).find( |
6736.3.10
by Tim Penhey
Clean up the query for getting a person's public revisions. |
478 |
Revision, |
7712.1.1
by Edwin Grubbs
Optimized queries. |
479 |
And(revision_time_limit(day_limit), |
480 |
person_condition, |
|
13931.4.3
by Ian Booth
Add new transitively_private attribute on branch |
481 |
Not(Branch.transitively_private))) |
7712.1.1
by Edwin Grubbs
Optimized queries. |
482 |
result_set.config(distinct=True) |
6736.3.4
by Tim Penhey
Added tests for addition Revision and RevisionSet methods. |
483 |
return result_set.order_by(Desc(Revision.revision_date)) |
6736.3.14
by Tim Penhey
Add getPublicRevisionsForProject to RevisionSet. |
484 |
|
485 |
@staticmethod
|
|
7712.1.2
by Edwin Grubbs
Refactored. |
486 |
def _getPublicRevisionsHelper(obj, day_limit): |
10724.1.1
by Henning Eggers
First batch of Project -> ProjectGrpoup renamings. |
487 |
"""Helper method for Products and ProjectGroups."""
|
6736.3.14
by Tim Penhey
Add getPublicRevisionsForProject to RevisionSet. |
488 |
# Here to stop circular imports.
|
8138.1.2
by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing. |
489 |
from lp.code.model.branch import Branch |
7675.110.3
by Curtis Hovey
Ran the migration script to move registry code to lp.registry. |
490 |
from lp.registry.model.product import Product |
8138.1.2
by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing. |
491 |
from lp.code.model.branchrevision import BranchRevision |
6736.3.14
by Tim Penhey
Add getPublicRevisionsForProject to RevisionSet. |
492 |
|
7712.1.1
by Edwin Grubbs
Optimized queries. |
493 |
origin = [ |
494 |
Revision, |
|
495 |
Join(BranchRevision, BranchRevision.revision == Revision.id), |
|
496 |
Join(Branch, BranchRevision.branch == Branch.id), |
|
497 |
]
|
|
498 |
||
13931.4.3
by Ian Booth
Add new transitively_private attribute on branch |
499 |
conditions = And(revision_time_limit(day_limit), |
500 |
Not(Branch.transitively_private)) |
|
501 |
||
7712.1.2
by Edwin Grubbs
Refactored. |
502 |
if IProduct.providedBy(obj): |
13931.4.3
by Ian Booth
Add new transitively_private attribute on branch |
503 |
conditions = And(conditions, Branch.product == obj) |
10326.1.1
by Henning Eggers
Mechanically renamed IProject* to IProjectGroup*. |
504 |
elif IProjectGroup.providedBy(obj): |
13931.4.3
by Ian Booth
Add new transitively_private attribute on branch |
505 |
origin.append(Join(Product, Branch.product == Product.id)) |
506 |
conditions = And(conditions, Product.project == obj) |
|
7712.1.2
by Edwin Grubbs
Refactored. |
507 |
else: |
508 |
raise AssertionError( |
|
10326.1.3
by Henning Eggers
Fixed too long lines. |
509 |
"Not an IProduct or IProjectGroup: %r" % obj) |
7712.1.2
by Edwin Grubbs
Refactored. |
510 |
|
511 |
result_set = Store.of(obj).using(*origin).find( |
|
512 |
Revision, conditions) |
|
7712.1.1
by Edwin Grubbs
Optimized queries. |
513 |
result_set.config(distinct=True) |
6736.3.14
by Tim Penhey
Add getPublicRevisionsForProject to RevisionSet. |
514 |
return result_set.order_by(Desc(Revision.revision_date)) |
6736.3.20
by Tim Penhey
Separate out the product and project method calls. |
515 |
|
7712.1.2
by Edwin Grubbs
Refactored. |
516 |
@classmethod
|
517 |
def getPublicRevisionsForProduct(cls, product, day_limit=30): |
|
518 |
"""See `IRevisionSet`."""
|
|
519 |
return cls._getPublicRevisionsHelper(product, day_limit) |
|
520 |
||
521 |
@classmethod
|
|
10724.1.1
by Henning Eggers
First batch of Project -> ProjectGrpoup renamings. |
522 |
def getPublicRevisionsForProjectGroup(cls, project, day_limit=30): |
7712.1.2
by Edwin Grubbs
Refactored. |
523 |
"""See `IRevisionSet`."""
|
524 |
return cls._getPublicRevisionsHelper(project, day_limit) |
|
525 |
||
7675.169.3
by Tim Penhey
Add methods to populate and prune the RevisionCache. |
526 |
@staticmethod
|
527 |
def updateRevisionCacheForBranch(branch): |
|
528 |
"""See `IRevisionSet`."""
|
|
529 |
# Hand crafting the sql insert statement as storm doesn't handle the
|
|
530 |
# INSERT INTO ... SELECT ... syntax. Also there is no public api yet
|
|
531 |
# for storm to get the select statement.
|
|
532 |
||
533 |
# Remove the security proxy to get access to the ID columns.
|
|
534 |
naked_branch = removeSecurityProxy(branch) |
|
535 |
||
536 |
insert_columns = ['Revision.id', 'revision_author', 'revision_date'] |
|
537 |
subselect_clauses = [] |
|
538 |
if branch.product is None: |
|
539 |
insert_columns.append('NULL') |
|
540 |
subselect_clauses.append('product IS NULL') |
|
541 |
else: |
|
542 |
insert_columns.append(str(naked_branch.productID)) |
|
543 |
subselect_clauses.append('product = %s' % naked_branch.productID) |
|
544 |
||
545 |
if branch.distroseries is None: |
|
546 |
insert_columns.extend(['NULL', 'NULL']) |
|
547 |
subselect_clauses.extend( |
|
548 |
['distroseries IS NULL', 'sourcepackagename IS NULL']) |
|
549 |
else: |
|
550 |
insert_columns.extend( |
|
551 |
[str(naked_branch.distroseriesID), |
|
552 |
str(naked_branch.sourcepackagenameID)]) |
|
553 |
subselect_clauses.extend( |
|
554 |
['distroseries = %s' % naked_branch.distroseriesID, |
|
555 |
'sourcepackagename = %s' % naked_branch.sourcepackagenameID]) |
|
556 |
||
557 |
insert_columns.append(str(branch.private)) |
|
558 |
if branch.private: |
|
7675.169.7
by Tim Penhey
Updates following review. |
559 |
subselect_clauses.append('private IS TRUE') |
7675.169.3
by Tim Penhey
Add methods to populate and prune the RevisionCache. |
560 |
else: |
7675.169.7
by Tim Penhey
Updates following review. |
561 |
subselect_clauses.append('private IS FALSE') |
7675.169.3
by Tim Penhey
Add methods to populate and prune the RevisionCache. |
562 |
|
563 |
insert_statement = """ |
|
564 |
INSERT INTO RevisionCache
|
|
565 |
(revision, revision_author, revision_date,
|
|
566 |
product, distroseries, sourcepackagename, private)
|
|
567 |
SELECT %(columns)s FROM Revision |
|
568 |
JOIN BranchRevision ON BranchRevision.revision = Revision.id
|
|
569 |
WHERE Revision.revision_date > (
|
|
570 |
CURRENT_TIMESTAMP AT TIME ZONE 'UTC' - interval '30 days')
|
|
571 |
AND BranchRevision.branch = %(branch_id)s |
|
572 |
AND Revision.id NOT IN (
|
|
573 |
SELECT revision FROM RevisionCache
|
|
574 |
WHERE %(subselect_where)s) |
|
575 |
""" % { |
|
576 |
'columns': ', '.join(insert_columns), |
|
577 |
'branch_id': branch.id, |
|
578 |
'subselect_where': ' AND '.join(subselect_clauses), |
|
579 |
}
|
|
580 |
Store.of(branch).execute(insert_statement) |
|
581 |
||
582 |
@staticmethod
|
|
7675.169.6
by Tim Penhey
Fix the pruning. |
583 |
def pruneRevisionCache(limit): |
7675.169.3
by Tim Penhey
Add methods to populate and prune the RevisionCache. |
584 |
"""See `IRevisionSet`."""
|
7675.169.6
by Tim Penhey
Fix the pruning. |
585 |
# Storm doesn't handle remove a limited result set:
|
586 |
# FeatureError: Can't remove a sliced result set
|
|
587 |
store = IMasterStore(RevisionCache) |
|
588 |
epoch = datetime.now(tz=pytz.UTC) - timedelta(days=30) |
|
589 |
subquery = Select( |
|
590 |
[RevisionCache.id], |
|
591 |
RevisionCache.revision_date < epoch, |
|
592 |
limit=limit) |
|
593 |
store.find(RevisionCache, RevisionCache.id.is_in(subquery)).remove() |
|
7675.169.3
by Tim Penhey
Add methods to populate and prune the RevisionCache. |
594 |
|
6950.2.2
by Tim Penhey
Factor out the time bound query fragment, and tweak the docstrings. |
595 |
|
596 |
def revision_time_limit(day_limit): |
|
597 |
"""The storm fragment to limit the revision_date field of the Revision."""
|
|
598 |
now = datetime.now(pytz.UTC) |
|
599 |
earliest = now - timedelta(days=day_limit) |
|
600 |
||
601 |
return And( |
|
602 |
Revision.revision_date <= now, |
|
603 |
Revision.revision_date > earliest) |
|
7675.169.3
by Tim Penhey
Add methods to populate and prune the RevisionCache. |
604 |
|
605 |
||
606 |
class RevisionCache(Storm): |
|
607 |
"""A cached version of a recent revision."""
|
|
608 |
||
609 |
__storm_table__ = 'RevisionCache' |
|
610 |
||
611 |
id = Int(primary=True) |
|
612 |
||
613 |
revision_id = Int(name='revision', allow_none=False) |
|
614 |
revision = Reference(revision_id, 'Revision.id') |
|
615 |
||
7675.169.7
by Tim Penhey
Updates following review. |
616 |
revision_author_id = Int(name='revision_author', allow_none=False) |
7675.169.3
by Tim Penhey
Add methods to populate and prune the RevisionCache. |
617 |
revision_author = Reference(revision_author_id, 'RevisionAuthor.id') |
618 |
||
11435.5.1
by Tim Penhey
Extract the branch cloud into its own module. |
619 |
revision_date = UtcDateTimeCol(notNull=True) |
7675.169.3
by Tim Penhey
Add methods to populate and prune the RevisionCache. |
620 |
|
621 |
product_id = Int(name='product', allow_none=True) |
|
622 |
product = Reference(product_id, 'Product.id') |
|
623 |
||
624 |
distroseries_id = Int(name='distroseries', allow_none=True) |
|
625 |
distroseries = Reference(distroseries_id, 'DistroSeries.id') |
|
626 |
||
627 |
sourcepackagename_id = Int(name='sourcepackagename', allow_none=True) |
|
628 |
sourcepackagename = Reference( |
|
629 |
sourcepackagename_id, 'SourcePackageName.id') |
|
630 |
||
631 |
private = Bool(allow_none=False, default=False) |
|
7675.169.6
by Tim Penhey
Fix the pruning. |
632 |
|
633 |
def __init__(self, revision): |
|
7675.169.7
by Tim Penhey
Updates following review. |
634 |
# Make the revision_author assignment first as traversing to the
|
635 |
# revision_author of the revision does a query which causes a store
|
|
636 |
# flush. If an assignment has been done already, the RevisionCache
|
|
637 |
# object would have been implicitly added to the store, and failes
|
|
638 |
# with an integrity check.
|
|
7675.169.6
by Tim Penhey
Fix the pruning. |
639 |
self.revision_author = revision.revision_author |
640 |
self.revision = revision |
|
641 |
self.revision_date = revision.revision_date |