Merge lp:~mwhudson/launchpad/less-import-requestMirrors-bug-487357 into lp:launchpad

Proposed by Michael Hudson-Doyle
Status: Merged
Approved by: Michael Hudson-Doyle
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~mwhudson/launchpad/less-import-requestMirrors-bug-487357
Merge into: lp:launchpad
Diff against target: 613 lines (+171/-53)
13 files modified
lib/lp/code/browser/branch.py (+9/-1)
lib/lp/code/enums.py (+9/-0)
lib/lp/code/model/codeimport.py (+2/-1)
lib/lp/code/model/codeimportjob.py (+6/-3)
lib/lp/code/model/tests/test_codeimport.py (+16/-3)
lib/lp/code/model/tests/test_codeimportjob.py (+4/-12)
lib/lp/code/stories/codeimport/xx-codeimport-results.txt (+3/-0)
lib/lp/code/templates/codeimport-macros.pt (+2/-4)
lib/lp/codehosting/codeimport/tests/test_worker.py (+24/-2)
lib/lp/codehosting/codeimport/tests/test_workermonitor.py (+35/-2)
lib/lp/codehosting/codeimport/worker.py (+40/-10)
lib/lp/codehosting/codeimport/workermonitor.py (+18/-13)
scripts/code-import-worker.py (+3/-2)
To merge this branch: bzr merge lp:~mwhudson/launchpad/less-import-requestMirrors-bug-487357
Reviewer Review Type Date Requested Status
Paul Hummer (community) Approve
Review via email: mp+18361@code.launchpad.net

Commit message

Extend the contract between the code import 'worker' and 'monitor' scripts by specifying that an exit code of 2 means that the import was successful but didn't import any new revisions so there's no need to request a mirror of the import branch.

To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Hi,

This branch extends the contract between the code import 'worker' and 'monitor' scripts by specifying that an exit code of 2 means that the import was successful but didn't import any new revisions so there's no need to request a mirror of the import branch. I expect this to substantially reduce the load on the puller, as currently about half of all branches pulled are import branches with no new revisions.

I've had some pre-imp discussion with Tim, Jono and Aaron about this.

Cheers,
mwh

Revision history for this message
Paul Hummer (rockstar) wrote :

This all looks good. Thanks for doing this.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/branch.py'
2--- lib/lp/code/browser/branch.py 2010-01-29 17:15:31 +0000
3+++ lib/lp/code/browser/branch.py 2010-02-01 05:56:15 +0000
4@@ -81,7 +81,8 @@
5 latest_proposals_for_each_branch)
6 from lp.code.enums import (
7 BranchLifecycleStatus, BranchType, CodeImportJobState,
8- CodeImportReviewStatus, RevisionControlSystems, UICreatableBranchType)
9+ CodeImportResultStatus, CodeImportReviewStatus, RevisionControlSystems,
10+ UICreatableBranchType)
11 from lp.code.errors import InvalidBranchMergeProposal
12 from lp.code.interfaces.branch import (
13 BranchCreationForbidden, BranchExists, IBranch,
14@@ -509,6 +510,13 @@
15 """Return the last 10 CodeImportResults."""
16 return list(self.context.code_import.results[:10])
17
18+ def iconForCodeImportResultStatus(self, status):
19+ """The icon to represent the `CodeImportResultStatus` `status`."""
20+ if status in CodeImportResultStatus.successes:
21+ return "/@@/yes"
22+ else:
23+ return "/@@/no"
24+
25 @property
26 def is_svn_import(self):
27 """True if an imported branch is a SVN import."""
28
29=== modified file 'lib/lp/code/enums.py'
30--- lib/lp/code/enums.py 2010-01-12 02:07:38 +0000
31+++ lib/lp/code/enums.py 2010-02-01 05:56:15 +0000
32@@ -799,6 +799,13 @@
33 Import job completed successfully.
34 """)
35
36+ SUCCESS_NOCHANGE = DBItem(110, """
37+ Success with no changes
38+
39+ Import job completed successfully, but there were no new revisions to
40+ import.
41+ """)
42+
43 FAILURE = DBItem(200, """
44 Failure
45
46@@ -857,6 +864,8 @@
47 job, or the deletion of a CodeImport which had a running job.
48 """)
49
50+ successes = [SUCCESS, SUCCESS_NOCHANGE]
51+
52
53 class CodeReviewVote(DBEnumeratedType):
54 """Code Review Votes
55
56=== modified file 'lib/lp/code/model/codeimport.py'
57--- lib/lp/code/model/codeimport.py 2010-01-13 02:51:30 +0000
58+++ lib/lp/code/model/codeimport.py 2010-02-01 05:56:15 +0000
59@@ -160,7 +160,8 @@
60 "coalesce",
61 Select(
62 CodeImportResult.id,
63- And(CodeImportResult.status == CodeImportResultStatus.SUCCESS,
64+ And(CodeImportResult.status.is_in(
65+ CodeImportResultStatus.successes),
66 CodeImportResult.code_import == self),
67 order_by=Desc(CodeImportResult.id),
68 limit=1),
69
70=== modified file 'lib/lp/code/model/codeimportjob.py'
71--- lib/lp/code/model/codeimportjob.py 2009-07-17 00:26:05 +0000
72+++ lib/lp/code/model/codeimportjob.py 2010-02-01 05:56:15 +0000
73@@ -287,11 +287,14 @@
74 # Only start a new one if the import is still in the REVIEWED state.
75 if code_import.review_status == CodeImportReviewStatus.REVIEWED:
76 self.newJob(code_import)
77- # If the status was successful, update the date_last_successful and
78- # arrange for the branch to be mirrored.
79- if status == CodeImportResultStatus.SUCCESS:
80+ # If the status was successful, update date_last_successful.
81+ if status in [CodeImportResultStatus.SUCCESS,
82+ CodeImportResultStatus.SUCCESS_NOCHANGE]:
83 naked_import = removeSecurityProxy(code_import)
84 naked_import.date_last_successful = result.date_created
85+ # If the status was successful and revisions were imported, arrange
86+ # for the branch to be mirrored.
87+ if status == CodeImportResultStatus.SUCCESS:
88 code_import.branch.requestMirror()
89 getUtility(ICodeImportEventSet).newFinish(
90 code_import, machine)
91
92=== modified file 'lib/lp/code/model/tests/test_codeimport.py'
93--- lib/lp/code/model/tests/test_codeimport.py 2010-01-15 23:55:45 +0000
94+++ lib/lp/code/model/tests/test_codeimport.py 2010-02-01 05:56:15 +0000
95@@ -394,11 +394,15 @@
96 getUtility(ICodeImportJobWorkflow).finishJob(
97 running_job, CodeImportResultStatus.FAILURE, None)
98
99- def succeedImport(self, code_import):
100+ def succeedImport(self, code_import,
101+ status=CodeImportResultStatus.SUCCESS):
102 """Create if necessary a job for `code_import` and have it succeed."""
103+ if status not in CodeImportResultStatus.successes:
104+ raise AssertionError(
105+ "succeedImport() should be called with a successful status!")
106 running_job = self.makeRunningJob(code_import)
107 getUtility(ICodeImportJobWorkflow).finishJob(
108- running_job, CodeImportResultStatus.SUCCESS, None)
109+ running_job, status, None)
110
111 def test_consecutive_failure_count_zero_initially(self):
112 # A new code import has a consecutive_failure_count of 0.
113@@ -407,7 +411,7 @@
114
115 def test_consecutive_failure_count_succeed(self):
116 # A code import that has succeeded once has a
117- # consecutive_failure_count of 1.
118+ # consecutive_failure_count of 0.
119 code_import = self.factory.makeCodeImport()
120 self.succeedImport(code_import)
121 self.assertEqual(0, code_import.consecutive_failure_count)
122@@ -419,6 +423,15 @@
123 self.failImport(code_import)
124 self.assertEqual(1, code_import.consecutive_failure_count)
125
126+ def test_consecutive_failure_count_succeed_succeed_no_changes(self):
127+ # A code import that has succeeded then succeeded with no changes has
128+ # a consecutive_failure_count of 0.
129+ code_import = self.factory.makeCodeImport()
130+ self.succeedImport(code_import)
131+ self.succeedImport(
132+ code_import, CodeImportResultStatus.SUCCESS_NOCHANGE)
133+ self.assertEqual(0, code_import.consecutive_failure_count)
134+
135 def test_consecutive_failure_count_fail_fail(self):
136 # A code import that has failed twice has a consecutive_failure_count
137 # of 2.
138
139=== modified file 'lib/lp/code/model/tests/test_codeimportjob.py'
140--- lib/lp/code/model/tests/test_codeimportjob.py 2009-07-17 00:26:05 +0000
141+++ lib/lp/code/model/tests/test_codeimportjob.py 2010-02-01 05:56:15 +0000
142@@ -32,8 +32,7 @@
143 from lp.code.interfaces.codeimportevent import ICodeImportEventSet
144 from lp.code.interfaces.codeimportjob import (
145 ICodeImportJobSet, ICodeImportJobWorkflow)
146-from lp.code.interfaces.codeimportresult import (
147- ICodeImportResult, ICodeImportResultSet)
148+from lp.code.interfaces.codeimportresult import ICodeImportResult
149 from lp.registry.interfaces.person import IPersonSet
150 from lp.testing import ANONYMOUS, login, logout, TestCaseWithFactory
151 from canonical.launchpad.testing.codeimporthelpers import (
152@@ -274,7 +273,7 @@
153 self.assertReclaimableJobs([stale_job])
154 machine = self.factory.makeCodeImportMachine(set_online=True)
155 login(ANONYMOUS)
156- job = getUtility(ICodeImportJobSet).getJobForMachine(
157+ getUtility(ICodeImportJobSet).getJobForMachine(
158 machine.hostname)
159 login_for_code_imports()
160 # Now there are no reclaimable jobs.
161@@ -456,7 +455,7 @@
162 # of the job that was deleted when the CodeImport review status
163 # changed from REVIEWED. That is the date_job_started of the most
164 # recent CodeImportResult plus the effective update interval.
165- job = getUtility(ICodeImportJobWorkflow).newJob(code_import)
166+ getUtility(ICodeImportJobWorkflow).newJob(code_import)
167 self.assertSqlAttributeEqualsDate(
168 code_import.import_job, 'date_due',
169 recent_result.date_job_started + interval)
170@@ -690,7 +689,6 @@
171 def test_wrongJobState(self):
172 # Calling updateHeartbeat with a job whose state is not RUNNING is an
173 # error.
174- machine = self.factory.makeCodeImportMachine()
175 code_import = self.factory.makeCodeImport()
176 job = self.factory.makeCodeImportJob(code_import)
177 self.assertFailure(
178@@ -740,7 +738,6 @@
179
180 def test_wrongJobState(self):
181 # Calling finishJob with a job whose state is not RUNNING is an error.
182- machine = self.factory.makeCodeImportMachine()
183 code_import = self.factory.makeCodeImport()
184 job = self.factory.makeCodeImportJob(code_import)
185 self.switchDbUser()
186@@ -767,7 +764,6 @@
187 # finishJob() creates a new CodeImportJob for the given CodeImport,
188 # scheduled appropriately far in the future.
189 running_job = self.makeRunningJob()
190- running_job_date_due = running_job.date_due
191 code_import = running_job.code_import
192 self.switchDbUser()
193 getUtility(ICodeImportJobWorkflow).finishJob(
194@@ -784,7 +780,6 @@
195 # finishJob() creates a new CodeImportJob for the given CodeImport,
196 # unless the CodeImport has been suspended or marked invalid.
197 running_job = self.makeRunningJob()
198- running_job_date_due = running_job.date_due
199 code_import = running_job.code_import
200 ddaa = getUtility(IPersonSet).getByEmail(
201 'david.allouche@canonical.com')
202@@ -798,9 +793,7 @@
203 def test_createsResultObject(self):
204 # finishJob() creates a CodeImportResult object for the given import.
205 running_job = self.makeRunningJob()
206- running_job_date_due = running_job.date_due
207 code_import = running_job.code_import
208- result_set = getUtility(ICodeImportResultSet)
209 # Before calling finishJob() there are no CodeImportResults for the
210 # given import...
211 self.assertEqual(len(list(code_import.results)), 0)
212@@ -903,7 +896,6 @@
213
214 self.switchDbUser()
215 log_data = 'several\nlines\nof\nlog data'
216- log_excerpt = log_data.splitlines()[-1]
217 log_alias_id = getUtility(ILibrarianClient).addFile(
218 'import_log.txt', len(log_data),
219 StringIO.StringIO(log_data), 'text/plain')
220@@ -939,7 +931,7 @@
221 code_import = job.code_import
222 self.assertTrue(code_import.date_last_successful is None)
223 getUtility(ICodeImportJobWorkflow).finishJob(job, status, None)
224- if status == CodeImportResultStatus.SUCCESS:
225+ if status in CodeImportResultStatus.successes:
226 self.assertTrue(code_import.date_last_successful is not None)
227 else:
228 self.assertTrue(code_import.date_last_successful is None)
229
230=== modified file 'lib/lp/code/stories/codeimport/xx-codeimport-results.txt'
231--- lib/lp/code/stories/codeimport/xx-codeimport-results.txt 2009-03-24 12:43:49 +0000
232+++ lib/lp/code/stories/codeimport/xx-codeimport-results.txt 2010-02-01 05:56:15 +0000
233@@ -23,6 +23,8 @@
234 >>> browser.open(branch_url)
235 >>> import_results = find_tag_by_id(browser.contents, 'import-results')
236 >>> print extract_text(import_results).replace('—', '--')
237+ Import started on 2007-12-10 on odin and finished on 2007-12-10
238+ taking ten hours -- see the log
239 Import started on 2007-12-09 on odin and finished on 2007-12-09
240 taking nine hours -- see the log
241 Import started on 2007-12-08 on odin and finished on 2007-12-08
242@@ -58,4 +60,5 @@
243 <img src="/@@/no" title="Source Checkout Failed" />
244 <img src="/@@/no" title="Internal Failure" />
245 <img src="/@@/no" title="Failure" />
246+ <img src="/@@/yes" title="Success with no changes" />
247 <img src="/@@/yes" title="Success" />
248
249=== modified file 'lib/lp/code/templates/codeimport-macros.pt'
250--- lib/lp/code/templates/codeimport-macros.pt 2009-07-17 17:59:07 +0000
251+++ lib/lp/code/templates/codeimport-macros.pt 2010-02-01 05:56:15 +0000
252@@ -8,10 +8,8 @@
253 Expects a parameter called 'result'.
254 </tal:comment>
255
256- <img src="/@@/yes" tal:condition="result/status/enumvalue:SUCCESS"
257- title="Success"/>
258- <img src="/@@/no" tal:condition="not: result/status/enumvalue:SUCCESS"
259- tal:attributes="title result/status/title"/>
260+ <img tal:attributes="src python:view.iconForCodeImportResultStatus(result.status);
261+ title result/status/title"/>
262 Import started
263 <tal:when replace="result/date_job_started/fmt:displaydate">
264 4 hours ago
265
266=== modified file 'lib/lp/codehosting/codeimport/tests/test_worker.py'
267--- lib/lp/codehosting/codeimport/tests/test_worker.py 2010-01-13 02:09:34 +0000
268+++ lib/lp/codehosting/codeimport/tests/test_worker.py 2010-02-01 05:56:15 +0000
269@@ -30,9 +30,9 @@
270
271 from lp.codehosting import load_optional_plugin
272 from lp.codehosting.codeimport.worker import (
273- BazaarBranchStore, BzrSvnImportWorker, CSCVSImportWorker,
274+ BazaarBranchStore, BzrSvnImportWorker, CodeImportWorkerExitCode,
275 ForeignTreeStore, GitImportWorker, HgImportWorker, ImportDataStore,
276- ImportWorker, get_default_bazaar_branch_store)
277+ ImportWorker, CSCVSImportWorker, get_default_bazaar_branch_store)
278 from lp.codehosting.codeimport.tests.servers import (
279 CVSServer, GitServer, MercurialServer, SubversionServer)
280 from lp.codehosting.tests.helpers import (
281@@ -710,6 +710,28 @@
282 self.assertEqual(
283 self.foreign_commit_count, len(branch.revision_history()))
284
285+ def test_script_exit_codes(self):
286+ # After a successful import that imports revisions, the worker exits
287+ # with a code of CodeImportWorkerExitCode.SUCCESS. After a successful
288+ # import that does not import revisions, the worker exits with a code
289+ # of CodeImportWorkerExitCode.SUCCESS_NOCHANGE.
290+ source_details = self.makeSourceDetails(
291+ 'trunk', [('README', 'Original contents')])
292+
293+ clean_up_default_stores_for_import(source_details)
294+
295+ script_path = os.path.join(
296+ config.root, 'scripts', 'code-import-worker.py')
297+ output = tempfile.TemporaryFile()
298+ retcode = subprocess.call(
299+ [script_path] + source_details.asArguments(),
300+ stderr=output, stdout=output)
301+ self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS)
302+ retcode = subprocess.call(
303+ [script_path] + source_details.asArguments(),
304+ stderr=output, stdout=output)
305+ self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS_NOCHANGE)
306+
307
308 class CSCVSActualImportMixin(TestActualImportMixin):
309
310
311=== modified file 'lib/lp/codehosting/codeimport/tests/test_workermonitor.py'
312--- lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2010-01-13 02:09:34 +0000
313+++ lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2010-02-01 05:56:15 +0000
314@@ -38,7 +38,8 @@
315 from lp.code.model.codeimportjob import CodeImportJob
316 from lp.codehosting import load_optional_plugin
317 from lp.codehosting.codeimport.worker import (
318- CodeImportSourceDetails, get_default_bazaar_branch_store)
319+ CodeImportSourceDetails, CodeImportWorkerExitCode,
320+ get_default_bazaar_branch_store)
321 from lp.codehosting.codeimport.workermonitor import (
322 CodeImportWorkerMonitor, CodeImportWorkerMonitorProtocol, ExitQuietly,
323 read_only_transaction)
324@@ -292,7 +293,39 @@
325
326 def test_callFinishJobCallsFinishJobFailure(self):
327 # callFinishJob calls finishJob with CodeImportResultStatus.FAILURE
328- # and swallows the failure if its argument is a Failure.
329+ # and swallows the failure if its argument indicates that the
330+ # subprocess exited with an exit code of
331+ # CodeImportWorkerExitCode.FAILURE.
332+ calls = self.patchOutFinishJob()
333+ ret = self.worker_monitor.callFinishJob(
334+ makeFailure(
335+ error.ProcessTerminated,
336+ exitCode=CodeImportWorkerExitCode.FAILURE))
337+ self.assertEqual(calls, [CodeImportResultStatus.FAILURE])
338+ self.assertOopsesLogged([error.ProcessTerminated])
339+ # We return the deferred that callFinishJob returns -- if
340+ # callFinishJob did not swallow the error, this will fail the test.
341+ return ret
342+
343+ def test_callFinishJobCallsFinishJobSuccessNoChange(self):
344+ # If the argument to callFinishJob indicates that the subprocess
345+ # exited with a code of CodeImportWorkerExitCode.SUCCESS_NOCHANGE, it
346+ # calls finishJob with a status of SUCCESS_NOCHANGE.
347+ calls = self.patchOutFinishJob()
348+ ret = self.worker_monitor.callFinishJob(
349+ makeFailure(
350+ error.ProcessTerminated,
351+ exitCode=CodeImportWorkerExitCode.SUCCESS_NOCHANGE))
352+ self.assertEqual(calls, [CodeImportResultStatus.SUCCESS_NOCHANGE])
353+ self.assertOopsesLogged([])
354+ # We return the deferred that callFinishJob returns -- if
355+ # callFinishJob did not swallow the error, this will fail the test.
356+ return ret
357+
358+ def test_callFinishJobCallsFinishJobArbitraryFailure(self):
359+ # If the argument to callFinishJob indicates that there was some other
360+ # failure that had nothing to do with the subprocess, it records
361+ # failure.
362 calls = self.patchOutFinishJob()
363 ret = self.worker_monitor.callFinishJob(makeFailure(RuntimeError))
364 self.assertEqual(calls, [CodeImportResultStatus.FAILURE])
365
366=== modified file 'lib/lp/codehosting/codeimport/worker.py'
367--- lib/lp/codehosting/codeimport/worker.py 2010-01-13 02:09:34 +0000
368+++ lib/lp/codehosting/codeimport/worker.py 2010-02-01 05:56:15 +0000
369@@ -9,6 +9,7 @@
370 'BzrSvnImportWorker',
371 'CSCVSImportWorker',
372 'CodeImportSourceDetails',
373+ 'CodeImportWorkerExitCode',
374 'ForeignTreeStore',
375 'GitImportWorker',
376 'HgImportWorker',
377@@ -29,13 +30,13 @@
378 from bzrlib.upgrade import upgrade
379
380 from canonical.cachedproperty import cachedproperty
381+from lp.code.enums import RevisionControlSystems
382 from lp.codehosting.codeimport.foreigntree import (
383 CVSWorkingTree, SubversionWorkingTree)
384 from lp.codehosting.codeimport.tarball import (
385 create_tarball, extract_tarball)
386 from lp.codehosting.codeimport.uifactory import LoggingUIFactory
387 from canonical.config import config
388-from lp.code.enums import RevisionControlSystems
389
390 from cscvs.cmds import totla
391 import cscvs
392@@ -43,6 +44,14 @@
393 import SCM
394
395
396+class CodeImportWorkerExitCode:
397+ """Exit codes used by the code import worker script."""
398+
399+ SUCCESS = 0
400+ FAILURE = 1
401+ SUCCESS_NOCHANGE = 2
402+
403+
404 class BazaarBranchStore:
405 """A place where Bazaar branches of code imports are kept."""
406
407@@ -77,7 +86,11 @@
408 return BzrDir.open(target_path).open_workingtree()
409
410 def push(self, db_branch_id, bzr_tree, required_format):
411- """Push up `bzr_tree` as the Bazaar branch for `code_import`."""
412+ """Push up `bzr_tree` as the Bazaar branch for `code_import`.
413+
414+ :return: A boolean that is true if the push was non-trivial
415+ (i.e. actually transferred revisions).
416+ """
417 self.transport.create_prefix()
418 branch_from = bzr_tree.branch
419 target_url = self._getMirrorURL(db_branch_id)
420@@ -86,7 +99,8 @@
421 except NotBranchError:
422 branch_to = BzrDir.create_branch_and_repo(
423 target_url, format=required_format)
424- branch_to.pull(branch_from, overwrite=True)
425+ pull_result = branch_to.pull(branch_from, overwrite=True)
426+ return pull_result.old_revid != pull_result.new_revid
427
428
429 def get_default_bazaar_branch_store():
430@@ -356,8 +370,11 @@
431 self.required_format)
432
433 def pushBazaarWorkingTree(self, bazaar_tree):
434- """Push the updated Bazaar working tree to the server."""
435- self.bazaar_branch_store.push(
436+ """Push the updated Bazaar working tree to the server.
437+
438+ :return: True if revisions were transferred.
439+ """
440+ return self.bazaar_branch_store.push(
441 self.source_details.branch_id, bazaar_tree, self.required_format)
442
443 def getWorkingDirectory(self):
444@@ -390,12 +407,20 @@
445 saved_pwd = os.getcwd()
446 os.chdir(working_directory)
447 try:
448- self._doImport()
449+ non_trivial = self._doImport()
450+ if non_trivial:
451+ return CodeImportWorkerExitCode.SUCCESS
452+ else:
453+ return CodeImportWorkerExitCode.SUCCESS_NOCHANGE
454 finally:
455 shutil.rmtree(working_directory)
456 os.chdir(saved_pwd)
457
458 def _doImport(self):
459+ """Perform the import.
460+
461+ :return: True if the import actually imported some new revisions.
462+ """
463 raise NotImplementedError()
464
465
466@@ -470,8 +495,9 @@
467 foreign_tree = self.getForeignTree()
468 bazaar_tree = self.getBazaarWorkingTree()
469 self.importToBazaar(foreign_tree, bazaar_tree)
470- self.pushBazaarWorkingTree(bazaar_tree)
471+ non_trivial = self.pushBazaarWorkingTree(bazaar_tree)
472 self.foreign_tree_store.archive(foreign_tree)
473+ return non_trivial
474
475
476 class PullingImportWorker(ImportWorker):
477@@ -506,7 +532,7 @@
478 bazaar_tree.branch.pull(foreign_branch, overwrite=True)
479 finally:
480 bzrlib.ui.ui_factory = saved_factory
481- self.pushBazaarWorkingTree(bazaar_tree)
482+ return self.pushBazaarWorkingTree(bazaar_tree)
483
484
485 class GitImportWorker(PullingImportWorker):
486@@ -542,9 +568,11 @@
487 that bzr-git will have created at .bzr/repository/bzr.git into the
488 import data store.
489 """
490- PullingImportWorker.pushBazaarWorkingTree(self, bazaar_tree)
491+ non_trivial = PullingImportWorker.pushBazaarWorkingTree(
492+ self, bazaar_tree)
493 self.import_data_store.put(
494 'git.db', bazaar_tree.branch.repository._transport)
495+ return non_trivial
496
497
498 class HgImportWorker(PullingImportWorker):
499@@ -581,9 +609,11 @@
500 that bzr-hg will have created at .bzr/repository/hg-v2.db into the
501 import data store.
502 """
503- PullingImportWorker.pushBazaarWorkingTree(self, bazaar_tree)
504+ non_trivial = PullingImportWorker.pushBazaarWorkingTree(
505+ self, bazaar_tree)
506 self.import_data_store.put(
507 self.db_file, bazaar_tree.branch.repository._transport)
508+ return non_trivial
509
510
511 class BzrSvnImportWorker(PullingImportWorker):
512
513=== modified file 'lib/lp/codehosting/codeimport/workermonitor.py'
514--- lib/lp/codehosting/codeimport/workermonitor.py 2009-06-25 04:06:00 +0000
515+++ lib/lp/codehosting/codeimport/workermonitor.py 2010-02-01 05:56:15 +0000
516@@ -10,10 +10,9 @@
517
518
519 import os
520-import sys
521 import tempfile
522
523-from twisted.internet import defer, reactor, task
524+from twisted.internet import defer, error, reactor, task
525 from twisted.python import failure
526 from twisted.python.util import mergeFunctionMetadata
527
528@@ -32,7 +31,8 @@
529 from lp.code.enums import CodeImportResultStatus
530 from lp.code.interfaces.codeimportjob import (
531 ICodeImportJobSet, ICodeImportJobWorkflow)
532-from lp.codehosting.codeimport.worker import CodeImportSourceDetails
533+from lp.codehosting.codeimport.worker import (
534+ CodeImportSourceDetails, CodeImportWorkerExitCode)
535 from lp.testing import login, logout, ANONYMOUS
536
537
538@@ -243,19 +243,15 @@
539 def finishJob(self, status):
540 """Call the finishJob method for the job we are working on.
541
542- This method uploads the log file to the librarian first. If this
543- fails, we still try to call finishJob, but return the librarian's
544- failure if finishJob succeeded (if finishJob fails, that exception
545- 'wins').
546+ This method uploads the log file to the librarian first.
547 """
548 job = self.getJob()
549 log_file_size = self._log_file.tell()
550- librarian_failure = None
551 if log_file_size > 0:
552 self._log_file.seek(0)
553 branch = job.code_import.branch
554- log_file_name = '%s-%s-log.txt' % (
555- branch.product.name, branch.name)
556+ log_file_name = '%s-%s-%s-log.txt' % (
557+ branch.owner.name, branch.product.name, branch.name)
558 try:
559 log_file_alias = self._createLibrarianFileAlias(
560 log_file_name, log_file_size, self._log_file,
561@@ -300,16 +296,25 @@
562 failure.trap(ExitQuietly)
563 return None
564
565+ def _reasonToStatus(self, reason):
566+ if isinstance(reason, failure.Failure):
567+ if reason.check(error.ProcessTerminated):
568+ if reason.value.exitCode == \
569+ CodeImportWorkerExitCode.SUCCESS_NOCHANGE:
570+ return CodeImportResultStatus.SUCCESS_NOCHANGE
571+ return CodeImportResultStatus.FAILURE
572+ else:
573+ return CodeImportResultStatus.SUCCESS
574+
575 def callFinishJob(self, reason):
576 """Call finishJob() with the appropriate status."""
577 if not self._call_finish_job:
578 return reason
579- if isinstance(reason, failure.Failure):
580+ status = self._reasonToStatus(reason)
581+ if status == CodeImportResultStatus.FAILURE:
582 self._log_file.write("Import failed:\n")
583 reason.printTraceback(self._log_file)
584 self._logOopsFromFailure(reason)
585- status = CodeImportResultStatus.FAILURE
586 else:
587 self._logger.info('Import succeeded.')
588- status = CodeImportResultStatus.SUCCESS
589 return self.finishJob(status)
590
591=== modified file 'scripts/code-import-worker.py'
592--- scripts/code-import-worker.py 2009-11-20 07:09:54 +0000
593+++ scripts/code-import-worker.py 2010-02-01 05:56:15 +0000
594@@ -19,6 +19,7 @@
595 import _pythonpath
596
597 from optparse import OptionParser
598+import sys
599
600 from bzrlib.transport import get_transport
601
602@@ -58,9 +59,9 @@
603 source_details,
604 get_transport(config.codeimport.foreign_tree_store),
605 get_default_bazaar_branch_store(), self.logger)
606- import_worker.run()
607+ return import_worker.run()
608
609
610 if __name__ == '__main__':
611 script = CodeImportWorker()
612- script.main()
613+ sys.exit(script.main())