14134.5.2
by Jeroen Vermeulen
Request branch scan on the master store, not the slave store. |
1 |
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
|
8687.15.34
by Karl Fogel
Add license header blocks to .py, .zcml, and .pt files that don't have it |
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
3 |
|
4 |
"""Export translation snapshots to bzr branches where requested."""
|
|
5 |
||
6 |
__metaclass__ = type |
|
7 |
__all__ = ['ExportTranslationsToBranch'] |
|
8 |
||
9 |
||
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
10 |
from datetime import ( |
11 |
datetime, |
|
12 |
timedelta, |
|
13 |
)
|
|
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
14 |
import os.path |
10100.1.4
by Jonathan Lange
More pytz. |
15 |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
16 |
from bzrlib.errors import NotBranchError |
13261.7.4
by Jelmer Vernooij
Fix use of deprecated iter_reverse_revision_history in translations code. |
17 |
from bzrlib.revision import NULL_REVISION |
10100.1.4
by Jonathan Lange
More pytz. |
18 |
import pytz |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
19 |
from storm.expr import ( |
7675.913.8
by j.c.sackett
Lint fixes. |
20 |
And, |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
21 |
Join, |
22 |
)
|
|
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
23 |
from zope.component import getUtility |
24 |
||
14550.1.2
by Steve Kowalik
Revert lib/lp/translations/scripts/translations_to_branch.py |
25 |
# Load the normal plugin set. Your linter may complain, and automated
|
26 |
# imports formatting tools will rearrange this, but keep it above the
|
|
27 |
# other Launchpad imports.
|
|
28 |
import lp.codehosting |
|
29 |
||
10914.2.1
by Jeroen Vermeulen
Email notifications for unpushed translations branches. |
30 |
from canonical.config import config |
14593.2.12
by Curtis Hovey
Move mail helpers to lp.services.mail.helpers. |
31 |
|
14593.2.15
by Curtis Hovey
Moved helpers to lp.services. |
32 |
from lp.services.helpers import shortlist |
14593.2.12
by Curtis Hovey
Move mail helpers to lp.services.mail.helpers. |
33 |
from lp.services.mail.helpers import ( |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
34 |
get_contact_email_addresses, |
35 |
get_email_template, |
|
36 |
)
|
|
14560.2.29
by Curtis Hovey
Restored lpstorm module name because it lp engineers know that name. |
37 |
from lp.services.database.lpstorm import IMasterStore |
12143.1.2
by Danilo Segan
Clean-up imports. |
38 |
from canonical.launchpad.webapp import errorlog |
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
39 |
from canonical.launchpad.webapp.interfaces import ( |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
40 |
IStoreSelector, |
41 |
MAIN_STORE, |
|
42 |
SLAVE_FLAVOR, |
|
43 |
)
|
|
7675.913.13
by j.c.sackett
Added comments. |
44 |
from lp.app.enums import ServiceUsage |
13084.5.4
by Aaron Bentley
Handle StaleLastMirrored by scheduling scan. |
45 |
from lp.code.errors import StaleLastMirrored |
46 |
from lp.code.interfaces.branch import get_db_branch_info |
|
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
47 |
from lp.code.interfaces.branchjob import IRosettaUploadJobSource |
14134.5.2
by Jeroen Vermeulen
Request branch scan on the master store, not the slave store. |
48 |
from lp.code.model.branch import Branch |
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
49 |
from lp.code.model.directbranchcommit import ( |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
50 |
ConcurrentUpdateError, |
51 |
DirectBranchCommit, |
|
52 |
)
|
|
53 |
from lp.codehosting.vfs import get_rw_server |
|
54 |
from lp.services.mail.sendmail import ( |
|
55 |
format_address, |
|
56 |
simple_sendmail, |
|
57 |
)
|
|
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
58 |
from lp.services.scripts.base import LaunchpadCronScript |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
59 |
from lp.translations.interfaces.potemplate import IPOTemplateSet |
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
60 |
|
61 |
||
62 |
class ExportTranslationsToBranch(LaunchpadCronScript): |
|
63 |
"""Commit translations to translations_branches where requested."""
|
|
64 |
||
9426.6.1
by Jeroen Vermeulen
Don't export files that clearly haven't changed. |
65 |
commit_message = "Launchpad automatic translations update." |
66 |
||
67 |
# Don't bother looking for a previous translations commit if it's
|
|
68 |
# longer than this ago.
|
|
69 |
previous_commit_cutoff_age = timedelta(days=7) |
|
70 |
||
71 |
# We can find out when the last translations commit to a branch
|
|
72 |
# completed, and we can find out when the last transaction changing
|
|
73 |
# a POFile started. This is exactly the wrong way around for
|
|
74 |
# figuring out which POFiles need a fresh export, so assume a fudge
|
|
75 |
# factor.
|
|
76 |
fudge_factor = timedelta(hours=6) |
|
77 |
||
7675.328.1
by Jeroen Vermeulen
Fix failure in export-to-branches: bzr revision timestamp is a float. |
78 |
def add_my_options(self): |
79 |
"""See `LaunchpadScript`."""
|
|
80 |
self.parser.add_option( |
|
81 |
'-n', '--no-fudge', action='store_true', dest='no_fudge', |
|
82 |
default=False, |
|
83 |
help="For testing: no fudge period for POFile changes.") |
|
84 |
||
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
85 |
def _checkForObjections(self, source): |
86 |
"""Check for reasons why we can't commit to this branch.
|
|
87 |
||
88 |
Raises `ConcurrentUpdateError` if there is such a reason.
|
|
89 |
||
90 |
:param source: the series being exported to its
|
|
91 |
translations_branch.
|
|
92 |
"""
|
|
93 |
if source.translations_branch is None: |
|
94 |
raise ConcurrentUpdateError( |
|
95 |
"Translations export for %s was just disabled." % ( |
|
96 |
source.title)) |
|
97 |
||
9729.1.1
by Jeroen Vermeulen
Don't block on unfinished branch jobs that are clearly out of date. |
98 |
branch = source.translations_branch |
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
99 |
jobsource = getUtility(IRosettaUploadJobSource) |
9729.1.1
by Jeroen Vermeulen
Don't block on unfinished branch jobs that are clearly out of date. |
100 |
unfinished_jobs = jobsource.findUnfinishedJobs( |
10100.1.4
by Jonathan Lange
More pytz. |
101 |
branch, since=datetime.now(pytz.UTC) - timedelta(days=1)) |
9729.1.1
by Jeroen Vermeulen
Don't block on unfinished branch jobs that are clearly out of date. |
102 |
|
103 |
if unfinished_jobs.any(): |
|
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
104 |
raise ConcurrentUpdateError( |
105 |
"Translations branch for %s has pending translations " |
|
106 |
"changes. Not committing." % source.title) |
|
107 |
||
9426.6.1
by Jeroen Vermeulen
Don't export files that clearly haven't changed. |
108 |
def _makeDirectBranchCommit(self, db_branch): |
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
109 |
"""Create a `DirectBranchCommit`.
|
110 |
||
9426.6.1
by Jeroen Vermeulen
Don't export files that clearly haven't changed. |
111 |
:param db_branch: A `Branch` object as defined in Launchpad.
|
112 |
:return: A `DirectBranchCommit` for `db_branch`.
|
|
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
113 |
"""
|
11592.1.5
by Jeroen Vermeulen
Missed a spot on the changes for Noodles. |
114 |
committer_id = 'Launchpad Translations on behalf of %s' % ( |
11592.1.3
by Jeroen Vermeulen
Review changes. |
115 |
db_branch.owner.name) |
11592.1.5
by Jeroen Vermeulen
Missed a spot on the changes for Noodles. |
116 |
return DirectBranchCommit(db_branch, committer_id=committer_id) |
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
117 |
|
118 |
def _commit(self, source, committer): |
|
119 |
"""Commit changes to branch. Check for race conditions."""
|
|
120 |
self._checkForObjections(source) |
|
9375.3.3
by Jeroen Vermeulen
Merge devel |
121 |
committer.commit(self.commit_message, txn=self.txn) |
9426.6.1
by Jeroen Vermeulen
Don't export files that clearly haven't changed. |
122 |
|
123 |
def _isTranslationsCommit(self, revision): |
|
124 |
"""Is `revision` an automatic translations commit?"""
|
|
125 |
return revision.message == self.commit_message |
|
126 |
||
7675.328.1
by Jeroen Vermeulen
Fix failure in export-to-branches: bzr revision timestamp is a float. |
127 |
def _getRevisionTime(self, revision): |
128 |
"""Get timestamp of `revision`."""
|
|
129 |
# The bzr timestamp is a float representing UTC-based seconds
|
|
130 |
# since the epoch. It stores the timezone as well, but we can
|
|
131 |
# ignore it here.
|
|
10100.1.4
by Jonathan Lange
More pytz. |
132 |
return datetime.fromtimestamp(revision.timestamp, pytz.UTC) |
7675.328.1
by Jeroen Vermeulen
Fix failure in export-to-branches: bzr revision timestamp is a float. |
133 |
|
9426.6.1
by Jeroen Vermeulen
Don't export files that clearly haven't changed. |
134 |
def _getLatestTranslationsCommit(self, branch): |
135 |
"""Get date of last translations commit to `branch`, if any."""
|
|
10100.1.4
by Jonathan Lange
More pytz. |
136 |
cutoff_date = datetime.now(pytz.UTC) - self.previous_commit_cutoff_age |
9426.6.1
by Jeroen Vermeulen
Don't export files that clearly haven't changed. |
137 |
|
138 |
revno, current_rev = branch.last_revision_info() |
|
139 |
repository = branch.repository |
|
13261.7.4
by Jelmer Vernooij
Fix use of deprecated iter_reverse_revision_history in translations code. |
140 |
graph = repository.get_graph() |
141 |
for rev_id in graph.iter_lefthand_ancestry( |
|
142 |
current_rev, (NULL_REVISION, )): |
|
9426.6.1
by Jeroen Vermeulen
Don't export files that clearly haven't changed. |
143 |
revision = repository.get_revision(rev_id) |
7675.328.1
by Jeroen Vermeulen
Fix failure in export-to-branches: bzr revision timestamp is a float. |
144 |
revision_date = self._getRevisionTime(revision) |
9426.6.1
by Jeroen Vermeulen
Don't export files that clearly haven't changed. |
145 |
if self._isTranslationsCommit(revision): |
146 |
return revision_date |
|
147 |
||
148 |
if revision_date < cutoff_date: |
|
149 |
# Going too far back in history. Give up.
|
|
150 |
return None |
|
151 |
||
152 |
return None |
|
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
153 |
|
11942.1.1
by Danilo Segan
Factor out POFile gathering for bzr exports. |
154 |
def _findChangedPOFiles(self, source, changed_since): |
155 |
"""Return an iterator of POFiles changed since `changed_since`.
|
|
156 |
||
157 |
:param source: a `ProductSeries`.
|
|
158 |
:param changed_since: a datetime object.
|
|
159 |
"""
|
|
160 |
subset = getUtility(IPOTemplateSet).getSubset( |
|
161 |
productseries=source, iscurrent=True) |
|
162 |
for template in subset: |
|
163 |
for pofile in template.pofiles: |
|
164 |
if (changed_since is None or |
|
11942.1.2
by Danilo Segan
Export all POFiles for a template that has been updated as well. |
165 |
pofile.date_changed > changed_since or |
166 |
template.date_last_updated > changed_since): |
|
11942.1.1
by Danilo Segan
Factor out POFile gathering for bzr exports. |
167 |
yield pofile |
168 |
||
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
169 |
def _exportToBranch(self, source): |
170 |
"""Export translations for source into source.translations_branch.
|
|
171 |
||
172 |
:param source: a `ProductSeries`.
|
|
173 |
"""
|
|
174 |
self.logger.info("Exporting %s." % source.title) |
|
175 |
self._checkForObjections(source) |
|
14150.1.1
by Jeroen Vermeulen
Remove workaround for old bzr bug with stacked branches.d |
176 |
branch = source.translations_branch |
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
177 |
|
14134.5.2
by Jeroen Vermeulen
Request branch scan on the master store, not the slave store. |
178 |
branch = source.translations_branch |
179 |
||
13084.5.4
by Aaron Bentley
Handle StaleLastMirrored by scheduling scan. |
180 |
try: |
14150.1.1
by Jeroen Vermeulen
Remove workaround for old bzr bug with stacked branches.d |
181 |
committer = self._makeDirectBranchCommit(branch) |
13084.5.6
by Aaron Bentley
Update from review. |
182 |
except StaleLastMirrored as e: |
14134.5.2
by Jeroen Vermeulen
Request branch scan on the master store, not the slave store. |
183 |
# Request a rescan of the branch. Do this on the master
|
184 |
# store, or we won't be able to modify the branch object.
|
|
185 |
# (The master copy may also be more recent, in which case
|
|
186 |
# the rescan won't be necessary).
|
|
187 |
master_branch = IMasterStore(branch).get(Branch, branch.id) |
|
188 |
master_branch.branchChanged(**get_db_branch_info(**e.info)) |
|
13084.5.4
by Aaron Bentley
Handle StaleLastMirrored by scheduling scan. |
189 |
self.logger.warning( |
14434.1.1
by Jeroen Vermeulen
Log which branch the translations exporter fails to export to. And cosmetic changes. |
190 |
"Skipped %s due to stale DB info, and scheduled a new scan.", |
14134.5.2
by Jeroen Vermeulen
Request branch scan on the master store, not the slave store. |
191 |
branch.bzr_identity) |
13084.5.4
by Aaron Bentley
Handle StaleLastMirrored by scheduling scan. |
192 |
if self.txn: |
193 |
self.txn.commit() |
|
194 |
return
|
|
9375.3.1
by Jeroen Vermeulen
Extra logging & committing. |
195 |
self.logger.debug("Created DirectBranchCommit.") |
196 |
if self.txn: |
|
197 |
self.txn.commit() |
|
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
198 |
|
9426.6.1
by Jeroen Vermeulen
Don't export files that clearly haven't changed. |
199 |
bzr_branch = committer.bzrbranch |
200 |
||
201 |
last_commit_date = self._getLatestTranslationsCommit(bzr_branch) |
|
202 |
||
203 |
if last_commit_date is None: |
|
204 |
self.logger.debug("No previous translations commit found.") |
|
205 |
changed_since = None |
|
206 |
else: |
|
207 |
# Export files that have been touched since the last export.
|
|
208 |
# Subtract a fudge factor because the last-export date marks
|
|
209 |
# the end of the previous export, and the POFiles'
|
|
210 |
# last-touched timestamp marks the beginning of the last
|
|
211 |
# transaction that changed them.
|
|
212 |
self.logger.debug("Last commit was at %s." % last_commit_date) |
|
213 |
changed_since = last_commit_date - self.fudge_factor |
|
214 |
||
7675.328.1
by Jeroen Vermeulen
Fix failure in export-to-branches: bzr revision timestamp is a float. |
215 |
change_count = 0 |
216 |
||
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
217 |
try: |
11942.1.1
by Danilo Segan
Factor out POFile gathering for bzr exports. |
218 |
for pofile in self._findChangedPOFiles(source, changed_since): |
219 |
base_path = os.path.dirname(pofile.potemplate.path) |
|
220 |
||
221 |
language_code = pofile.getFullLanguageCode() |
|
222 |
self.logger.debug("Exporting %s." % language_code) |
|
223 |
||
224 |
pofile_path = os.path.join( |
|
225 |
base_path, language_code + '.po') |
|
226 |
pofile_contents = pofile.export() |
|
227 |
||
228 |
committer.writeFile(pofile_path, pofile_contents) |
|
229 |
change_count += 1 |
|
230 |
||
231 |
# We're not actually writing any changes to the
|
|
232 |
# database, but it's not polite to stay in one
|
|
233 |
# transaction for too long.
|
|
234 |
if self.txn: |
|
235 |
self.txn.commit() |
|
236 |
||
237 |
# We're done with this POFile. Don't bother caching
|
|
238 |
# anything about it any longer.
|
|
239 |
pofile.potemplate.clearPOFileCache() |
|
9426.6.1
by Jeroen Vermeulen
Don't export files that clearly haven't changed. |
240 |
|
7675.328.1
by Jeroen Vermeulen
Fix failure in export-to-branches: bzr revision timestamp is a float. |
241 |
if change_count > 0: |
9375.3.3
by Jeroen Vermeulen
Merge devel |
242 |
self.logger.debug("Writing to branch.") |
7675.328.1
by Jeroen Vermeulen
Fix failure in export-to-branches: bzr revision timestamp is a float. |
243 |
self._commit(source, committer) |
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
244 |
finally: |
245 |
committer.unlock() |
|
246 |
||
8871.3.5
by Jeroen Vermeulen
As per mwhudson's suggestion, keep bzrserver out of DirectBranchCommit entirely. |
247 |
def _exportToBranches(self, productseries_iter): |
248 |
"""Loop over `productseries_iter` and export their translations."""
|
|
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
249 |
items_done = 0 |
250 |
items_failed = 0 |
|
10914.2.1
by Jeroen Vermeulen
Email notifications for unpushed translations branches. |
251 |
unpushed_branches = 0 |
9019.1.1
by Jeroen Vermeulen
Commit more frequently, and don't iterate over result set across transactions. |
252 |
|
253 |
productseries = shortlist(productseries_iter, longest_expected=2000) |
|
254 |
||
255 |
for source in productseries: |
|
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
256 |
try: |
257 |
self._exportToBranch(source) |
|
9019.1.2
by Jeroen Vermeulen
Better keep committing after every productseries as well, just to be sure. |
258 |
|
259 |
if self.txn: |
|
260 |
self.txn.commit() |
|
7675.238.1
by Jeroen Vermeulen
Don't swallow KeyboardInterrupt/SystemExit. |
261 |
except (KeyboardInterrupt, SystemExit): |
262 |
raise
|
|
10914.2.1
by Jeroen Vermeulen
Email notifications for unpushed translations branches. |
263 |
except NotBranchError: |
264 |
unpushed_branches += 1 |
|
265 |
if self.txn: |
|
266 |
self.txn.abort() |
|
267 |
self._handleUnpushedBranch(source) |
|
268 |
if self.txn: |
|
269 |
self.txn.commit() |
|
14134.5.2
by Jeroen Vermeulen
Request branch scan on the master store, not the slave store. |
270 |
except Exception as e: |
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
271 |
items_failed += 1 |
14434.1.1
by Jeroen Vermeulen
Log which branch the translations exporter fails to export to. And cosmetic changes. |
272 |
self.logger.error( |
273 |
"Failure in %s/%s: %s", source.product.name, source.name, |
|
274 |
repr(e)) |
|
8657.8.1
by Jeroen Vermeulen
Script to commit translations to a branch. |
275 |
if self.txn: |
276 |
self.txn.abort() |
|
277 |
||
278 |
items_done += 1 |
|
279 |
||
10914.2.1
by Jeroen Vermeulen
Email notifications for unpushed translations branches. |
280 |
self.logger.info( |
281 |
"Processed %d item(s); %d failure(s), %d unpushed branch(es)." % ( |
|
282 |
items_done, items_failed, unpushed_branches)) |
|
283 |
||
284 |
def _sendMail(self, sender, recipients, subject, text): |
|
10914.2.2
by Jeroen Vermeulen
Integration test. |
285 |
"""Wrapper for `simple_sendmail`. Fakeable for easy testing."""
|
10914.2.1
by Jeroen Vermeulen
Email notifications for unpushed translations branches. |
286 |
simple_sendmail(sender, recipients, subject, text) |
287 |
||
288 |
def _handleUnpushedBranch(self, productseries): |
|
289 |
"""Branch has never been scanned. Notify owner.
|
|
290 |
||
291 |
This means that as far as the Launchpad database knows, there is
|
|
292 |
no actual bzr branch behind this `IBranch` yet.
|
|
293 |
"""
|
|
294 |
branch = productseries.translations_branch |
|
10914.2.3
by Jeroen Vermeulen
More verbose logging. |
295 |
self.logger.info("Notifying %s of unpushed branch %s." % ( |
296 |
branch.owner.name, branch.bzr_identity)) |
|
297 |
||
10914.2.1
by Jeroen Vermeulen
Email notifications for unpushed translations branches. |
298 |
template = get_email_template('unpushed-branch.txt', 'translations') |
299 |
text = template % { |
|
300 |
'productseries': productseries.title, |
|
301 |
'branch_url': branch.bzr_identity, |
|
302 |
}
|
|
303 |
recipients = get_contact_email_addresses(branch.owner) |
|
304 |
sender = format_address( |
|
305 |
"Launchpad Translations", config.canonical.noreply_from_address) |
|
306 |
subject = "Launchpad: translations branch has not been set up." |
|
307 |
self._sendMail(sender, recipients, subject, text) |
|
8871.3.1
by Jeroen Vermeulen
Implemented changes whispered in my ear by mwhudson. |
308 |
|
309 |
def main(self): |
|
310 |
"""See `LaunchpadScript`."""
|
|
311 |
# Avoid circular imports.
|
|
312 |
from lp.registry.model.product import Product |
|
313 |
from lp.registry.model.productseries import ProductSeries |
|
314 |
||
12143.1.1
by Danilo Segan
Set config section and OOPS prefix for translations_export_to_branch. |
315 |
errorlog.globalErrorUtility.configure(self.config_name) |
7675.328.1
by Jeroen Vermeulen
Fix failure in export-to-branches: bzr revision timestamp is a float. |
316 |
if self.options.no_fudge: |
317 |
self.fudge_factor = timedelta(0) |
|
318 |
||
12143.1.2
by Danilo Segan
Clean-up imports. |
319 |
self.logger.info("Exporting to translations branches.") |
8871.3.1
by Jeroen Vermeulen
Implemented changes whispered in my ear by mwhudson. |
320 |
|
321 |
self.store = getUtility(IStoreSelector).get(MAIN_STORE, SLAVE_FLAVOR) |
|
322 |
||
323 |
product_join = Join( |
|
324 |
ProductSeries, Product, ProductSeries.product == Product.id) |
|
325 |
productseries = self.store.using(product_join).find( |
|
7675.909.5
by j.c.sackett
Slew of fixes per review. |
326 |
ProductSeries, |
7675.913.9
by j.c.sackett
Lint fixes. |
327 |
And( |
14120.2.4
by Danilo Segan
Replace all references to _translation_usage with translation_usage. |
328 |
Product.translations_usage == ServiceUsage.LAUNCHPAD, |
7675.913.15
by j.c.sackett
Fixed conditions on one method. |
329 |
ProductSeries.translations_branch != None)) |
7675.909.14
by j.c.sackett
Fixed a test. |
330 |
|
8871.3.1
by Jeroen Vermeulen
Implemented changes whispered in my ear by mwhudson. |
331 |
# Anything deterministic will do, and even that is only for
|
332 |
# testing.
|
|
333 |
productseries = productseries.order_by(ProductSeries.id) |
|
334 |
||
9590.1.103
by Michael Hudson
fix the other use of map_branch_contents |
335 |
bzrserver = get_rw_server() |
10197.5.10
by Michael Hudson
more... |
336 |
bzrserver.start_server() |
8871.3.1
by Jeroen Vermeulen
Implemented changes whispered in my ear by mwhudson. |
337 |
try: |
338 |
self._exportToBranches(productseries) |
|
339 |
finally: |
|
10197.5.10
by Michael Hudson
more... |
340 |
bzrserver.stop_server() |