Merge lp:~thumper/launchpad/hg-imports-using-url into lp:launchpad/db-devel

Proposed by Tim Penhey
Status: Merged
Merged at revision: 8859
Proposed branch: lp:~thumper/launchpad/hg-imports-using-url
Merge into: lp:launchpad/db-devel
Prerequisite: lp:~thumper/launchpad/imports-urls
Diff against target: 1027 lines (+372/-71)
22 files modified
lib/canonical/config/schema-lazr.conf (+5/-0)
lib/lp/code/browser/codeimport.py (+26/-5)
lib/lp/code/doc/codeimport-event.txt (+14/-0)
lib/lp/code/doc/codeimport.txt (+46/-1)
lib/lp/code/mail/codeimport.py (+2/-1)
lib/lp/code/model/codeimport.py (+9/-4)
lib/lp/code/model/codeimportevent.py (+2/-1)
lib/lp/code/model/tests/test_codeimport.py (+29/-33)
lib/lp/code/stories/codeimport/xx-admin-codeimport.txt (+22/-1)
lib/lp/code/stories/codeimport/xx-codeimport-list.txt (+7/-2)
lib/lp/code/stories/codeimport/xx-create-codeimport.txt (+28/-10)
lib/lp/code/templates/branch-import-details.pt (+7/-1)
lib/lp/code/templates/codeimport-new.pt (+16/-0)
lib/lp/codehosting/codeimport/tests/servers.py (+21/-0)
lib/lp/codehosting/codeimport/tests/test_worker.py (+55/-5)
lib/lp/codehosting/codeimport/tests/test_workermonitor.py (+22/-1)
lib/lp/codehosting/codeimport/worker.py (+44/-2)
lib/lp/testing/factory.py (+10/-3)
scripts/code-import-worker.py (+4/-1)
setup.py (+1/-0)
utilities/sourcedeps.conf (+1/-0)
versions.cfg (+1/-0)
To merge this branch: bzr merge lp:~thumper/launchpad/hg-imports-using-url
Reviewer Review Type Date Requested Status
Michael Hudson-Doyle Approve
Review via email: mp+17187@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

This branch merges jelmer's hg import work into the imports-urls branch.

Revision history for this message
Tim Penhey (thumper) wrote :

We are now ready to go.

Local import succeeded \o/

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :
Download full text (14.0 KiB)

Hi Tim,

Cool to see this arrive! Sadly, I'm not sure it can land yet, because
of the new sourcecode branch. Let's talk to a LOSA about that.

> === modified file 'lib/lp/code/stories/codeimport/xx-create-codeimport.txt'

I think it would be good to create a Hg import in this test.

> --- lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2010-01-11 22:40:42 +0000
> +++ lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2010-01-12 22:39:29 +0000
> @@ -32,6 +32,7 @@
>
> >>> print_radio_button_field(browser.contents, "rcs_type")
> ( ) Git
> + ( ) Mercurial
> (*) Subversion
> ( ) CVS
>
> @@ -66,21 +67,14 @@
> Requesting a Git import
> =======================
>
> -The default foreign VCS type is Subversion.
> -
> - >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
> - >>> print_radio_button_field(browser.contents, "rcs_type")
> - ( ) Git
> - (*) Subversion
> - ( ) CVS
> -
> The user is required to enter a project that the import is for,
> a name for the import branch, and a subversion branch location.
>
> + >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
> >>> browser.getControl('Project').value = "firefox"
> >>> browser.getControl('Branch Name').value = "git-import"
> >>> browser.getControl('Git').click()
> - >>> browser.getControl('Repo URL').value = (
> + >>> browser.getControl('Repo URL', index=0).value = (
> ... "git://example.com/firefox.git")
> >>> browser.getControl('Request Import').click()
>

> === modified file 'lib/lp/code/templates/branch-import-details.pt'
> --- lib/lp/code/templates/branch-import-details.pt 2010-01-11 21:21:00 +0000
> +++ lib/lp/code/templates/branch-import-details.pt 2010-01-12 22:39:29 +0000
> @@ -45,6 +45,12 @@
> </p>
> </tal:git-import>
>
> + <tal:hg-import condition="code_import/rcs_type/enumvalue:HG">
> + <p>This branch is an import of the Mercurial repo at

I think I overheard some conversation about this, but it's not really
an import of the repo, but of the tip branch or whatever it's called
for Mercurial. Should we try to explain this?

> + <span tal:replace="code_import/url" />.
> + </p>
> + </tal:hg-import>
> +
> <tal:svn-import condition="view/is_svn_import">
> <p id="svn-import-details">
> This branch is an import of the

> === modified file 'lib/lp/codehosting/codeimport/tests/servers.py'
> --- lib/lp/codehosting/codeimport/tests/servers.py 2009-12-04 04:08:12 +0000
> +++ lib/lp/codehosting/codeimport/tests/servers.py 2010-01-12 22:39:29 +0000
> @@ -8,6 +8,7 @@
> __all__ = [
> 'CVSServer',
> 'GitServer',
> + 'MercurialServer',
> 'SubversionServer',
> ]
>
> @@ -210,3 +211,23 @@
> builder.finish()
> finally:
> os.chdir(wd)
> +
> +
> +class MercurialServer(Server):
> +
> + def __init__(self, repo_url):
> + super(MercurialServer, self).__init__()
> + self.repo_url = repo_url
> +
> + def makeRepo(self, tree_contents):
> + from mercurial.ui import ui
> + from mercurial.localrepo import localreposit...

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/config/schema-lazr.conf'
2--- lib/canonical/config/schema-lazr.conf 2009-11-17 11:27:51 +0000
3+++ lib/canonical/config/schema-lazr.conf 2010-01-13 02:53:16 +0000
4@@ -402,6 +402,11 @@
5 default_interval_git: 21600
6
7 # The default value of the update interval of a code import from
8+# Mercurial, in seconds.
9+# datatype: integer
10+default_interval_hg: 21600
11+
12+# The default value of the update interval of a code import from
13 # CVS, in seconds.
14 # datatype: integer
15 default_interval_cvs: 43200
16
17=== modified file 'lib/lp/code/browser/codeimport.py'
18--- lib/lp/code/browser/codeimport.py 2010-01-13 02:53:14 +0000
19+++ lib/lp/code/browser/codeimport.py 2010-01-13 02:53:16 +0000
20@@ -213,7 +213,7 @@
21 git_repo_url = URIField(
22 title=_("Repo URL"), required=False,
23 description=_(
24- "The URL of the git repository. The MASTER branch will be "
25+ "The URL of the git repository. The HEAD branch will be "
26 "imported."),
27 allowed_schemes=["git"],
28 allow_userinfo=False, # Only anonymous access is supported.
29@@ -222,6 +222,18 @@
30 allow_fragment=False,
31 trailing_slash=False)
32
33+ hg_repo_url = URIField(
34+ title=_("Repo URL"), required=False,
35+ description=_(
36+ "The URL of the Mercurial repository. The tip branch will be "
37+ "imported."),
38+ allowed_schemes=["http", "https"],
39+ allow_userinfo=False, # Only anonymous access is supported.
40+ allow_port=True,
41+ allow_query=False, # Query makes no sense in Mercurial
42+ allow_fragment=False, # Fragment makes no sense in Mercurial
43+ trailing_slash=False) # See http://launchpad.net/bugs/56357.
44+
45 branch_name = copy_field(
46 IBranch['name'],
47 __name__='branch_name',
48@@ -258,16 +270,18 @@
49 # display them separately in the form.
50 soup = BeautifulSoup(self.widgets['rcs_type']())
51 fields = soup.findAll('input')
52- [cvs_button, svn_button, git_button, empty_marker] = [
53+ [cvs_button, svn_button, git_button, hg_button, empty_marker] = [
54 field for field in fields
55- if field.get('value') in ['CVS', 'BZR_SVN', 'GIT', '1']]
56+ if field.get('value') in ['CVS', 'BZR_SVN', 'GIT', 'HG', '1']]
57 cvs_button['onclick'] = 'updateWidgets()'
58 svn_button['onclick'] = 'updateWidgets()'
59 git_button['onclick'] = 'updateWidgets()'
60+ hg_button['onclick'] = 'updateWidgets()'
61 # The following attributes are used only in the page template.
62 self.rcs_type_cvs = str(cvs_button)
63 self.rcs_type_svn = str(svn_button)
64 self.rcs_type_git = str(git_button)
65+ self.rcs_type_hg = str(hg_button)
66 self.rcs_type_emptymarker = str(empty_marker)
67
68 def _getImportLocation(self, data):
69@@ -279,6 +293,8 @@
70 return None, None, data.get('svn_branch_url')
71 elif rcs_type == RevisionControlSystems.GIT:
72 return None, None, data.get('git_repo_url')
73+ elif rcs_type == RevisionControlSystems.HG:
74+ return None, None, data.get('hg_repo_url')
75 else:
76 raise AssertionError(
77 'Unexpected revision control type %r.' % rcs_type)
78@@ -379,6 +395,9 @@
79 elif rcs_type == RevisionControlSystems.GIT:
80 self._validateURL(
81 data.get('git_repo_url'), field_name='git_repo_url')
82+ elif rcs_type == RevisionControlSystems.HG:
83+ self._validateURL(
84+ data.get('hg_repo_url'), field_name='hg_repo_url')
85 else:
86 raise AssertionError(
87 'Unexpected revision control type %r.' % rcs_type)
88@@ -469,7 +488,8 @@
89 self.form_fields = self.form_fields.omit('url')
90 elif self.code_import.rcs_type in (RevisionControlSystems.SVN,
91 RevisionControlSystems.BZR_SVN,
92- RevisionControlSystems.GIT):
93+ RevisionControlSystems.GIT,
94+ RevisionControlSystems.HG):
95 self.form_fields = self.form_fields.omit(
96 'cvs_root', 'cvs_module')
97 else:
98@@ -503,7 +523,8 @@
99 self.code_import)
100 elif self.code_import.rcs_type in (RevisionControlSystems.SVN,
101 RevisionControlSystems.BZR_SVN,
102- RevisionControlSystems.GIT):
103+ RevisionControlSystems.GIT,
104+ RevisionControlSystems.HG):
105 self._validateURL(data.get('url'), self.code_import)
106 else:
107 raise AssertionError('Unknown rcs_type for code import.')
108
109=== modified file 'lib/lp/code/doc/codeimport-event.txt'
110--- lib/lp/code/doc/codeimport-event.txt 2010-01-13 02:53:14 +0000
111+++ lib/lp/code/doc/codeimport-event.txt 2010-01-13 02:53:16 +0000
112@@ -165,6 +165,20 @@
113 URL u'git://git.example.org/main.git'
114
115
116+And for a Mercurial import, the hg details are recorded.
117+
118+ >>> hg_import = factory.makeCodeImport(
119+ ... hg_repo_url='http://hg.example.org/main')
120+ >>> hg_create_event = event_set.newCreate(hg_import, nopriv)
121+ >>> print_items(hg_create_event)
122+ CODE_IMPORT <muted>
123+ OWNER ...
124+ REVIEW_STATUS u'REVIEWED'
125+ ASSIGNEE None
126+ UPDATE_INTERVAL None
127+ URL u'http://hg.example.org/main'
128+
129+
130 == MODIFY ==
131
132 When a code import is modified, the ICodeImportEventSet utility should
133
134=== modified file 'lib/lp/code/doc/codeimport.txt'
135--- lib/lp/code/doc/codeimport.txt 2010-01-13 02:53:14 +0000
136+++ lib/lp/code/doc/codeimport.txt 2010-01-13 02:53:16 +0000
137@@ -197,6 +197,33 @@
138 >>> git_import == existing_import
139 True
140
141+Import from Mercurial
142++++++++++++++++++++++
143+
144+Code imports from Mercurial specify the URL used with "hg clone" to
145+retrieve the branch to import.
146+
147+ >>> hg = RevisionControlSystems.HG
148+ >>> hg_url = 'http://hg.example.com/metallic'
149+ >>> hg_import = code_import_set.new(
150+ ... registrant=nopriv, product=product, branch_name='trunk-hg',
151+ ... rcs_type=hg, url=hg_url)
152+ >>> verifyObject(ICodeImport, removeSecurityProxy(hg_import))
153+ True
154+
155+Creating a CodeImport object creates a corresponding CodeImportEvent.
156+
157+ >>> hg_events = event_set.getEventsForCodeImport(hg_import)
158+ >>> [event.event_type.name for event in hg_events]
159+ ['CREATE']
160+
161+The CodeImportSet is also able to retrieve the code imports with the
162+specified hg repo url.
163+
164+ >>> existing_import = code_import_set.getByURL(url=hg_url)
165+ >>> hg_import == existing_import
166+ True
167+
168
169 Updating code import details
170 ----------------------------
171@@ -316,6 +343,8 @@
172 ... seconds=config.codeimport.default_interval_subversion)
173 >>> default_interval_git = timedelta(
174 ... seconds=config.codeimport.default_interval_git)
175+ >>> default_interval_hg = timedelta(
176+ ... seconds=config.codeimport.default_interval_hg)
177
178 By default, code imports are created with an unspecified update interval.
179
180@@ -345,6 +374,12 @@
181 >>> git_import.effective_update_interval
182 datetime.timedelta(0, 21600)
183
184+ >>> default_interval_hg
185+ datetime.timedelta(0, 21600)
186+ >>> hg_import.effective_update_interval
187+ datetime.timedelta(0, 21600)
188+
189+
190 If the update interval is set, then it overrides the default value.
191
192 As explained in the "Modify CodeImports" section, the interface does not allow
193@@ -477,7 +512,7 @@
194 instead of:
195 hello from :pserver:anonymous@cvs.example.com:/cvsroot
196
197-And for Git.
198+For Git.
199
200 >>> data = {'url': 'git://git.example.com/goodbye.git'}
201 >>> modify_event = git_import.updateFromData(data, nopriv)
202@@ -499,6 +534,16 @@
203 instead of:
204 svn://svn.example.com/for-bzr-svn/trunk
205
206+And for Mercurial.
207+
208+ >>> data = {'url': 'http://metal.example.com/byebye.hg'}
209+ >>> modify_event = hg_import.updateFromData(data, nopriv)
210+ >>> print make_email_body_for_code_import_update(
211+ ... hg_import, modify_event, None)
212+ ~no-priv/firefox/trunk-hg is now being imported from:
213+ http://metal.example.com/byebye.hg
214+ instead of:
215+ http://hg.example.com/metallic
216
217 In addition, updateFromData can be used to set the branch whiteboard,
218 which is also described in the email that is sent.
219
220=== modified file 'lib/lp/code/mail/codeimport.py'
221--- lib/lp/code/mail/codeimport.py 2010-01-13 02:53:14 +0000
222+++ lib/lp/code/mail/codeimport.py 2010-01-13 02:53:16 +0000
223@@ -97,7 +97,8 @@
224 "\ninstead of:\n" + old_details)
225 elif code_import.rcs_type in (RevisionControlSystems.SVN,
226 RevisionControlSystems.BZR_SVN,
227- RevisionControlSystems.GIT):
228+ RevisionControlSystems.GIT,
229+ RevisionControlSystems.HG):
230 if CodeImportEventDataType.OLD_URL in event_data:
231 old_url = event_data[CodeImportEventDataType.OLD_URL]
232 body.append(
233
234=== modified file 'lib/lp/code/model/codeimport.py'
235--- lib/lp/code/model/codeimport.py 2010-01-13 02:53:13 +0000
236+++ lib/lp/code/model/codeimport.py 2010-01-13 02:53:16 +0000
237@@ -106,6 +106,8 @@
238 config.codeimport.default_interval_subversion,
239 RevisionControlSystems.GIT:
240 config.codeimport.default_interval_git,
241+ RevisionControlSystems.HG:
242+ config.codeimport.default_interval_hg,
243 }
244 seconds = default_interval_dict[self.rcs_type]
245 return timedelta(seconds=seconds)
246@@ -122,7 +124,8 @@
247 elif self.rcs_type in (
248 RevisionControlSystems.SVN,
249 RevisionControlSystems.GIT,
250- RevisionControlSystems.BZR_SVN):
251+ RevisionControlSystems.BZR_SVN,
252+ RevisionControlSystems.HG):
253 return self.url
254 else:
255 raise AssertionError(
256@@ -218,7 +221,8 @@
257 assert url is None
258 elif rcs_type in (RevisionControlSystems.SVN,
259 RevisionControlSystems.BZR_SVN,
260- RevisionControlSystems.GIT):
261+ RevisionControlSystems.GIT,
262+ RevisionControlSystems.HG):
263 assert cvs_root is None and cvs_module is None
264 assert url is not None
265 else:
266@@ -226,8 +230,9 @@
267 "Don't know how to sanity check source details for unknown "
268 "rcs_type %s"%rcs_type)
269 if review_status is None:
270- # Auto approve git imports.
271- if rcs_type == RevisionControlSystems.GIT:
272+ # Auto approve git and hg imports.
273+ if rcs_type in (
274+ RevisionControlSystems.GIT, RevisionControlSystems.HG):
275 review_status = CodeImportReviewStatus.REVIEWED
276 else:
277 review_status = CodeImportReviewStatus.NEW
278
279=== modified file 'lib/lp/code/model/codeimportevent.py'
280--- lib/lp/code/model/codeimportevent.py 2010-01-13 02:53:14 +0000
281+++ lib/lp/code/model/codeimportevent.py 2010-01-13 02:53:16 +0000
282@@ -258,7 +258,8 @@
283 """Yield key-value tuples describing the source of the import."""
284 if code_import.rcs_type in (RevisionControlSystems.SVN,
285 RevisionControlSystems.BZR_SVN,
286- RevisionControlSystems.GIT):
287+ RevisionControlSystems.GIT,
288+ RevisionControlSystems.HG):
289 yield 'URL', code_import.url
290 elif code_import.rcs_type == RevisionControlSystems.CVS:
291 yield 'CVS_ROOT', code_import.cvs_root
292
293=== modified file 'lib/lp/code/model/tests/test_codeimport.py'
294--- lib/lp/code/model/tests/test_codeimport.py 2010-01-13 02:53:14 +0000
295+++ lib/lp/code/model/tests/test_codeimport.py 2010-01-13 02:53:16 +0000
296@@ -24,26 +24,17 @@
297 from lp.code.interfaces.codeimportjob import ICodeImportJobWorkflow
298 from lp.testing import (
299 login, login_person, logout, TestCaseWithFactory, time_counter)
300-from lp.testing.factory import LaunchpadObjectFactory
301 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
302 from canonical.testing import (
303 DatabaseFunctionalLayer, LaunchpadFunctionalLayer,
304 LaunchpadZopelessLayer)
305
306
307-class TestCodeImportCreation(unittest.TestCase):
308+class TestCodeImportCreation(TestCaseWithFactory):
309 """Test the creation of CodeImports."""
310
311 layer = DatabaseFunctionalLayer
312
313- def setUp(self):
314- unittest.TestCase.setUp(self)
315- self.factory = LaunchpadObjectFactory()
316- login('no-priv@canonical.com')
317-
318- def tearDown(self):
319- logout()
320-
321 def test_new_svn_import(self):
322 """A new subversion code import should have NEW status."""
323 code_import = CodeImportSet().new(
324@@ -56,7 +47,7 @@
325 CodeImportReviewStatus.NEW,
326 code_import.review_status)
327 # No job is created for the import.
328- self.assertTrue(code_import.import_job is None)
329+ self.assertIs(None, code_import.import_job)
330
331 def test_reviewed_svn_import(self):
332 """A specific review status can be set for a new import."""
333@@ -71,7 +62,7 @@
334 CodeImportReviewStatus.REVIEWED,
335 code_import.review_status)
336 # A job is created for the import.
337- self.assertTrue(code_import.import_job is not None)
338+ self.assertIsNot(None, code_import.import_job)
339
340 def test_new_cvs_import(self):
341 """A new CVS code import should have NEW status."""
342@@ -86,7 +77,7 @@
343 CodeImportReviewStatus.NEW,
344 code_import.review_status)
345 # No job is created for the import.
346- self.assertTrue(code_import.import_job is None)
347+ self.assertIs(None, code_import.import_job)
348
349 def test_reviewed_cvs_import(self):
350 """A specific review status can be set for a new import."""
351@@ -102,7 +93,7 @@
352 CodeImportReviewStatus.REVIEWED,
353 code_import.review_status)
354 # A job is created for the import.
355- self.assertTrue(code_import.import_job is not None)
356+ self.assertIsNot(None, code_import.import_job)
357
358 def test_git_import_reviewed(self):
359 """A new git import is always reviewed by default."""
360@@ -117,23 +108,29 @@
361 CodeImportReviewStatus.REVIEWED,
362 code_import.review_status)
363 # A job is created for the import.
364- self.assertTrue(code_import.import_job is not None)
365-
366-
367-class TestCodeImportDeletion(unittest.TestCase):
368+ self.assertIsNot(None, code_import.import_job)
369+
370+ def test_hg_import_reviewed(self):
371+ """A new hg import is always reviewed by default."""
372+ code_import = CodeImportSet().new(
373+ registrant=self.factory.makePerson(),
374+ product=self.factory.makeProduct(),
375+ branch_name='imported',
376+ rcs_type=RevisionControlSystems.HG,
377+ url=self.factory.getUniqueURL(),
378+ review_status=None)
379+ self.assertEqual(
380+ CodeImportReviewStatus.REVIEWED,
381+ code_import.review_status)
382+ # No job is created for the import.
383+ self.assertIsNot(None, code_import.import_job)
384+
385+
386+class TestCodeImportDeletion(TestCaseWithFactory):
387 """Test the deletion of CodeImports."""
388
389 layer = LaunchpadFunctionalLayer
390
391- def setUp(self):
392- unittest.TestCase.setUp(self)
393- self.factory = LaunchpadObjectFactory()
394- # Log in a vcs import member.
395- login('david.allouche@canonical.com')
396-
397- def tearDown(self):
398- logout()
399-
400 def test_delete(self):
401 """Ensure CodeImport objects can be deleted via CodeImportSet."""
402 code_import = self.factory.makeCodeImport()
403@@ -302,15 +299,13 @@
404 CodeImportReviewStatus.FAILING, code_import.review_status)
405
406
407-class TestCodeImportResultsAttribute(unittest.TestCase):
408+class TestCodeImportResultsAttribute(TestCaseWithFactory):
409 """Test the results attribute of a CodeImport."""
410
411 layer = LaunchpadFunctionalLayer
412
413 def setUp(self):
414- unittest.TestCase.setUp(self)
415- login('no-priv@canonical.com')
416- self.factory = LaunchpadObjectFactory()
417+ TestCaseWithFactory.setUp(self)
418 self.code_import = self.factory.makeCodeImport()
419
420 def tearDown(self):
421@@ -534,7 +529,7 @@
422 def make_active_import(factory, project_name=None, product_name=None,
423 branch_name=None, svn_branch_url=None,
424 cvs_root=None, cvs_module=None, git_repo_url=None,
425- last_update=None, rcs_type=None):
426+ hg_repo_url=None, last_update=None, rcs_type=None):
427 """Make a new CodeImport for a new Product, maybe in a new Project.
428
429 The import will be 'active' in the sense used by
430@@ -549,7 +544,8 @@
431 code_import = factory.makeCodeImport(
432 product=product, branch_name=branch_name,
433 svn_branch_url=svn_branch_url, cvs_root=cvs_root,
434- cvs_module=cvs_module, git_repo_url=git_repo_url, rcs_type=rcs_type)
435+ cvs_module=cvs_module, git_repo_url=git_repo_url,
436+ hg_repo_url=hg_repo_url, rcs_type=None)
437 make_import_active(factory, code_import, last_update)
438 return code_import
439
440
441=== modified file 'lib/lp/code/stories/codeimport/xx-admin-codeimport.txt'
442--- lib/lp/code/stories/codeimport/xx-admin-codeimport.txt 2010-01-13 02:53:14 +0000
443+++ lib/lp/code/stories/codeimport/xx-admin-codeimport.txt 2010-01-13 02:53:16 +0000
444@@ -32,6 +32,12 @@
445 ... git_repo_url="git://git.example.org/fooix")
446 >>> git_import_location = str(canonical_url(git_import.branch))
447 >>> git_import_branch_unique_name = git_import.branch.unique_name
448+
449+ >>> hg_import = factory.makeCodeImport(
450+ ... hg_repo_url="http://hg.example.org/bar")
451+ >>> hg_import_location = str(canonical_url(hg_import.branch))
452+ >>> hg_import_branch_unique_name = hg_import.branch.unique_name
453+
454 >>> logout()
455
456 >>> import_browser = setupBrowser(
457@@ -97,6 +103,12 @@
458 >>> print_form_fields(import_browser)
459 field.url: git://git.example.org/fooix
460
461+ >>> import_browser.open(hg_import_location)
462+ >>> import_browser.getLink('Edit import source or review import').click()
463+ >>> print_form_fields(import_browser)
464+ field.url: http://hg.example.org/bar
465+
466+
467 Editing the import location
468 +++++++++++++++++++++++++++
469
470@@ -135,7 +147,7 @@
471 ... print extract_text(message)
472 The code import has been updated.
473
474-and Git imports.
475+Git imports,
476
477 >>> import_browser.open(git_import_location + '/+edit-import')
478 >>> import_browser.getControl('URL').value = \
479@@ -145,6 +157,15 @@
480 ... print extract_text(message)
481 The code import has been updated.
482
483+and Mercurial imports.
484+
485+ >>> import_browser.open(hg_import_location + '/+edit-import')
486+ >>> import_browser.getControl('URL').value = \
487+ ... 'http://metal.example.org/bar'
488+ >>> import_browser.getControl('Update').click()
489+ >>> for message in get_feedback_messages(import_browser.contents):
490+ ... print extract_text(message)
491+ The code import has been updated.
492
493 Approving an import
494 +++++++++++++++++++
495
496=== modified file 'lib/lp/code/stories/codeimport/xx-codeimport-list.txt'
497--- lib/lp/code/stories/codeimport/xx-codeimport-list.txt 2009-12-08 04:31:34 +0000
498+++ lib/lp/code/stories/codeimport/xx-codeimport-list.txt 2010-01-13 02:53:16 +0000
499@@ -34,14 +34,18 @@
500 ... factory, product_name="herproj", branch_name="master",
501 ... git_repo_url="git://git.example.org/herproj",
502 ... last_update=datetime.datetime(2007, 1, 4, tzinfo=pytz.UTC))
503+ >>> active_hg_import = make_active_import(
504+ ... factory, product_name="hg-proj", branch_name="tip",
505+ ... hg_repo_url="http://hg.example.org/proj",
506+ ... last_update=datetime.datetime(2007, 1, 5, tzinfo=pytz.UTC))
507 >>> len(list(code_import_set.getActiveImports()))
508- 4
509+ 5
510 >>> logout()
511
512 The page is linked to from the "$N imported branches" text.
513
514 >>> browser.open('http://code.launchpad.dev')
515- >>> browser.getLink('4 imported branches').click()
516+ >>> browser.getLink('5 imported branches').click()
517 >>> print browser.title
518 Available code imports
519
520@@ -54,6 +58,7 @@
521 >>> print_import_table()
522 Project Branch Name Source Details Last Updated
523 herproj master git://git.example.org/herproj 2007-01-04
524+ hg-proj tip http://hg.example.org/proj 2007-01-05
525 hisproj main :pserver:anon@example.com:/cvs hisproj 2007-01-03
526 myproject trunk http://example.com/svn/myproject/trunk 2007-01-01
527 ourproject trunk http://example.com/bzr-svn/myproject/trunk 2007-01-02
528
529=== modified file 'lib/lp/code/stories/codeimport/xx-create-codeimport.txt'
530--- lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2010-01-13 02:53:14 +0000
531+++ lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2010-01-13 02:53:16 +0000
532@@ -32,6 +32,7 @@
533
534 >>> print_radio_button_field(browser.contents, "rcs_type")
535 ( ) Git
536+ ( ) Mercurial
537 (*) Subversion
538 ( ) CVS
539
540@@ -63,24 +64,18 @@
541 >>> print svn_span['title']
542 Subversion via bzr-svn
543
544+
545 Requesting a Git import
546 =======================
547
548-The default foreign VCS type is Subversion.
549-
550- >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
551- >>> print_radio_button_field(browser.contents, "rcs_type")
552- ( ) Git
553- (*) Subversion
554- ( ) CVS
555-
556 The user is required to enter a project that the import is for,
557 a name for the import branch, and a subversion branch location.
558
559+ >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
560 >>> browser.getControl('Project').value = "firefox"
561 >>> browser.getControl('Branch Name').value = "git-import"
562 >>> browser.getControl('Git').click()
563- >>> browser.getControl('Repo URL').value = (
564+ >>> browser.getControl('Repo URL', index=0).value = (
565 ... "git://example.com/firefox.git")
566 >>> browser.getControl('Request Import').click()
567
568@@ -88,11 +83,34 @@
569
570 >>> print extract_text(find_tag_by_id(browser.contents, "import-details"))
571 Import Status: Reviewed
572- This branch is an import of the MASTER branch from the Git repo at
573+ This branch is an import of the HEAD branch of the Git repository at
574 git://example.com/firefox.git.
575 The next import is scheduled to run as soon as possible.
576
577
578+Requesting a Mercurial import
579+=======================
580+
581+The user is required to enter a project that the import is for,
582+a name for the import branch, and a mercurial branch location.
583+
584+ >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
585+ >>> browser.getControl('Project').value = "firefox"
586+ >>> browser.getControl('Branch Name').value = "hg-import"
587+ >>> browser.getControl('Mercurial').click()
588+ >>> browser.getControl('Repo URL', index=1).value = (
589+ ... "http://example.com/firefox.hg")
590+ >>> browser.getControl('Request Import').click()
591+
592+When the user clicks continue, the approved import branch is created
593+
594+ >>> print extract_text(find_tag_by_id(browser.contents, "import-details"))
595+ Import Status: Reviewed
596+ This branch is an import of the tip branch of the Mercurial repository at
597+ http://example.com/firefox.hg.
598+ The next import is scheduled to run as soon as possible.
599+
600+
601 Requesting a CVS import
602 =======================
603
604
605=== modified file 'lib/lp/code/templates/branch-import-details.pt'
606--- lib/lp/code/templates/branch-import-details.pt 2010-01-13 02:53:14 +0000
607+++ lib/lp/code/templates/branch-import-details.pt 2010-01-13 02:53:16 +0000
608@@ -40,11 +40,17 @@
609 </div>
610
611 <tal:git-import condition="code_import/rcs_type/enumvalue:GIT">
612- <p>This branch is an import of the MASTER branch from the Git repo at
613+ <p>This branch is an import of the HEAD branch of the Git repository at
614 <span tal:replace="code_import/url" />.
615 </p>
616 </tal:git-import>
617
618+ <tal:hg-import condition="code_import/rcs_type/enumvalue:HG">
619+ <p>This branch is an import of the tip branch of the Mercurial repository at
620+ <span tal:replace="code_import/url" />.
621+ </p>
622+ </tal:hg-import>
623+
624 <tal:svn-import condition="view/is_svn_import">
625 <p id="svn-import-details">
626 This branch is an import of the
627
628=== modified file 'lib/lp/code/templates/codeimport-new.pt'
629--- lib/lp/code/templates/codeimport-new.pt 2009-12-08 02:32:03 +0000
630+++ lib/lp/code/templates/codeimport-new.pt 2010-01-13 02:53:16 +0000
631@@ -62,6 +62,20 @@
632 <tr>
633 <td>
634 <label>
635+ <input tal:replace="structure view/rcs_type_hg" />
636+ Mercurial
637+ </label>
638+ <table class="importdetails">
639+ <tal:widget define="widget nocall:view/widgets/hg_repo_url">
640+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
641+ </tal:widget>
642+ </table>
643+ </td>
644+ </tr>
645+
646+ <tr>
647+ <td>
648+ <label>
649 <input tal:replace="structure view/rcs_type_svn" />
650 Subversion
651 </label>
652@@ -108,6 +122,8 @@
653 }
654 // GIT
655 updateField(form['field.git_repo_url'], rcs_type == 'GIT');
656+ // HG
657+ updateField(form['field.hg_repo_url'], rcs_type == 'HG');
658 // CVS
659 updateField(form['field.cvs_root'], rcs_type == 'CVS');
660 updateField(form['field.cvs_module'], rcs_type == 'CVS');
661
662=== modified file 'lib/lp/codehosting/codeimport/tests/servers.py'
663--- lib/lp/codehosting/codeimport/tests/servers.py 2009-12-04 04:08:12 +0000
664+++ lib/lp/codehosting/codeimport/tests/servers.py 2010-01-13 02:53:16 +0000
665@@ -8,6 +8,7 @@
666 __all__ = [
667 'CVSServer',
668 'GitServer',
669+ 'MercurialServer',
670 'SubversionServer',
671 ]
672
673@@ -210,3 +211,23 @@
674 builder.finish()
675 finally:
676 os.chdir(wd)
677+
678+
679+class MercurialServer(Server):
680+
681+ def __init__(self, repo_url):
682+ super(MercurialServer, self).__init__()
683+ self.repo_url = repo_url
684+
685+ def makeRepo(self, tree_contents):
686+ from mercurial.ui import ui
687+ from mercurial.localrepo import localrepository
688+ repo = localrepository(ui(), self.repo_url, create=1)
689+ for filename, contents in tree_contents:
690+ f = open(os.path.join(self.repo_url, filename), 'w')
691+ try:
692+ f.write(contents)
693+ finally:
694+ f.close()
695+ repo.add([filename])
696+ repo.commit(text='<The commit message>', user='jane Foo <joe@foo.com>')
697
698=== modified file 'lib/lp/codehosting/codeimport/tests/test_worker.py'
699--- lib/lp/codehosting/codeimport/tests/test_worker.py 2010-01-13 02:53:14 +0000
700+++ lib/lp/codehosting/codeimport/tests/test_worker.py 2010-01-13 02:53:16 +0000
701@@ -9,7 +9,6 @@
702 import os
703 import shutil
704 import subprocess
705-import sys
706 import tempfile
707 import time
708 import unittest
709@@ -32,10 +31,10 @@
710 from lp.codehosting import load_optional_plugin
711 from lp.codehosting.codeimport.worker import (
712 BazaarBranchStore, BzrSvnImportWorker, CSCVSImportWorker,
713- ForeignTreeStore, GitImportWorker, ImportDataStore, ImportWorker,
714- get_default_bazaar_branch_store)
715+ ForeignTreeStore, GitImportWorker, HgImportWorker, ImportDataStore,
716+ ImportWorker, get_default_bazaar_branch_store)
717 from lp.codehosting.codeimport.tests.servers import (
718- CVSServer, GitServer, SubversionServer)
719+ CVSServer, GitServer, MercurialServer, SubversionServer)
720 from lp.codehosting.tests.helpers import (
721 create_branch_with_one_revision)
722 from lp.testing.factory import LaunchpadObjectFactory
723@@ -822,7 +821,7 @@
724 # import should be rejected.
725 args = {'rcstype': self.rcstype}
726 reference_url = self.createBranchReference()
727- if self.rcstype in ('git', 'bzr-svn'):
728+ if self.rcstype in ('git', 'bzr-svn', 'hg'):
729 args['url'] = reference_url
730 else:
731 raise AssertionError("unexpected rcs_type %r" % self.rcs_type)
732@@ -886,6 +885,57 @@
733 rcstype='git', url=repository_path)
734
735
736+class TestMercurialImport(WorkerTest, TestActualImportMixin,
737+ PullingImportWorkerTests):
738+
739+ rcstype = 'hg'
740+
741+ def setUp(self):
742+ super(TestMercurialImport, self).setUp()
743+ load_optional_plugin('hg')
744+ self.setUpImport()
745+
746+ def tearDown(self):
747+ """Clear bzr-hg's cache of sqlite connections.
748+
749+ This is rather obscure: different test runs tend to re-use the same
750+ paths on disk, which confuses bzr-hg as it keeps a cache that maps
751+ paths to database connections, which happily returns the connection
752+ that corresponds to a path that no longer exists.
753+ """
754+ from bzrlib.plugins.hg.idmap import mapdbs
755+ mapdbs().clear()
756+ WorkerTest.tearDown(self)
757+
758+ def makeImportWorker(self, source_details):
759+ """Make a new `ImportWorker`."""
760+ return HgImportWorker(
761+ source_details, self.get_transport('import_data'),
762+ self.bazaar_store, logging.getLogger())
763+
764+ def makeForeignCommit(self, source_details):
765+ """Change the foreign tree, generating exactly one commit."""
766+ from mercurial.ui import ui
767+ from mercurial.localrepo import localrepository
768+ repo = localrepository(ui(), source_details.url)
769+ repo.commit(text="hello world!", user="Jane Random Hacker", force=1)
770+ self.foreign_commit_count += 1
771+
772+ def makeSourceDetails(self, branch_name, files):
773+ """Make a Mercurial `CodeImportSourceDetails` pointing at a real repo.
774+ """
775+ repository_path = self.makeTemporaryDirectory()
776+ hg_server = MercurialServer(repository_path)
777+ hg_server.setUp()
778+ self.addCleanup(hg_server.tearDown)
779+
780+ hg_server.makeRepo(files)
781+ self.foreign_commit_count = 1
782+
783+ return self.factory.makeCodeImportSourceDetails(
784+ rcstype='hg', url=repository_path)
785+
786+
787 class TestBzrSvnImport(WorkerTest, SubversionImportHelpers,
788 TestActualImportMixin, PullingImportWorkerTests):
789
790
791=== modified file 'lib/lp/codehosting/codeimport/tests/test_workermonitor.py'
792--- lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2010-01-13 02:53:14 +0000
793+++ lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2010-01-13 02:53:16 +0000
794@@ -43,7 +43,7 @@
795 CodeImportWorkerMonitor, CodeImportWorkerMonitorProtocol, ExitQuietly,
796 read_only_transaction)
797 from lp.codehosting.codeimport.tests.servers import (
798- CVSServer, GitServer, SubversionServer)
799+ CVSServer, GitServer, MercurialServer, SubversionServer)
800 from lp.codehosting.codeimport.tests.test_worker import (
801 clean_up_default_stores_for_import)
802 from lp.testing import login, logout
803@@ -501,6 +501,18 @@
804
805 return self.factory.makeCodeImport(git_repo_url=self.repo_path)
806
807+ def makeHgCodeImport(self):
808+ """Make a `CodeImport` that points to a real Mercurial repository."""
809+ load_optional_plugin('hg')
810+ self.hg_server = MercurialServer(self.repo_path)
811+ self.hg_server.setUp()
812+ self.addCleanup(self.hg_server.tearDown)
813+
814+ self.hg_server.makeRepo([('README', 'contents')])
815+ self.foreign_commit_count = 1
816+
817+ return self.factory.makeCodeImport(hg_repo_url=self.repo_path)
818+
819 def getStartedJobForImport(self, code_import):
820 """Get a started `CodeImportJob` for `code_import`.
821
822@@ -592,6 +604,15 @@
823 result = self.performImport(job_id)
824 return result.addCallback(self.assertImported, code_import_id)
825
826+ def test_import_hg(self):
827+ # Create a Mercurial CodeImport and import it.
828+ job = self.getStartedJobForImport(self.makeHgCodeImport())
829+ code_import_id = job.code_import.id
830+ job_id = job.id
831+ self.layer.txn.commit()
832+ result = self.performImport(job_id)
833+ return result.addCallback(self.assertImported, code_import_id)
834+
835 def test_import_bzrsvn(self):
836 # Create a Subversion-via-bzr-svn CodeImport and import it.
837 job = self.getStartedJobForImport(self.makeBzrSvnCodeImport())
838
839=== modified file 'lib/lp/codehosting/codeimport/worker.py'
840--- lib/lp/codehosting/codeimport/worker.py 2010-01-13 02:53:14 +0000
841+++ lib/lp/codehosting/codeimport/worker.py 2010-01-13 02:53:16 +0000
842@@ -11,6 +11,7 @@
843 'CodeImportSourceDetails',
844 'ForeignTreeStore',
845 'GitImportWorker',
846+ 'HgImportWorker',
847 'ImportWorker',
848 'get_default_bazaar_branch_store',
849 ]
850@@ -126,7 +127,7 @@
851 """Convert command line-style arguments to an instance."""
852 branch_id = int(arguments.pop(0))
853 rcstype = arguments.pop(0)
854- if rcstype in ['svn', 'bzr-svn', 'git']:
855+ if rcstype in ['svn', 'bzr-svn', 'git', 'hg']:
856 [url] = arguments
857 cvs_root = cvs_module = None
858 elif rcstype == 'cvs':
859@@ -151,6 +152,8 @@
860 cvs_module=str(code_import.cvs_module))
861 elif code_import.rcs_type == RevisionControlSystems.GIT:
862 return cls(branch_id, 'git', str(code_import.url))
863+ elif code_import.rcs_type == RevisionControlSystems.HG:
864+ return cls(branch_id, 'hg', str(code_import.url))
865 else:
866 raise AssertionError("Unknown rcstype %r." % code_import.rcs_type)
867
868@@ -158,7 +161,7 @@
869 """Return a list of arguments suitable for passing to a child process.
870 """
871 result = [str(self.branch_id), self.rcstype]
872- if self.rcstype in ['svn', 'bzr-svn', 'git']:
873+ if self.rcstype in ['svn', 'bzr-svn', 'git', 'hg']:
874 result.append(self.url)
875 elif self.rcstype == 'cvs':
876 result.append(self.cvs_root)
877@@ -544,6 +547,45 @@
878 'git.db', bazaar_tree.branch.repository._transport)
879
880
881+class HgImportWorker(PullingImportWorker):
882+ """An import worker for Mercurial imports.
883+
884+ The only behaviour we add is preserving the id-sha map between runs.
885+ """
886+
887+ db_file = 'hg-v2.db'
888+
889+ @property
890+ def format_classes(self):
891+ """See `PullingImportWorker.opening_format`."""
892+ # We only return HgLocalRepository for tests.
893+ from bzrlib.plugins.hg import HgBzrDirFormat
894+ return [HgBzrDirFormat]
895+
896+ def getBazaarWorkingTree(self):
897+ """See `ImportWorker.getBazaarWorkingTree`.
898+
899+ In addition to the superclass' behaviour, we retrieve the 'hg-v2.db'
900+ map from the import data store and put it where bzr-hg will find
901+ it in the Bazaar tree, that is at '.bzr/repository/hg-v2.db'.
902+ """
903+ tree = PullingImportWorker.getBazaarWorkingTree(self)
904+ self.import_data_store.fetch(
905+ self.db_file, tree.branch.repository._transport)
906+ return tree
907+
908+ def pushBazaarWorkingTree(self, bazaar_tree):
909+ """See `ImportWorker.pushBazaarWorkingTree`.
910+
911+ In addition to the superclass' behaviour, we store the 'hg-v2.db' shamap
912+ that bzr-hg will have created at .bzr/repository/hg-v2.db into the
913+ import data store.
914+ """
915+ PullingImportWorker.pushBazaarWorkingTree(self, bazaar_tree)
916+ self.import_data_store.put(
917+ self.db_file, bazaar_tree.branch.repository._transport)
918+
919+
920 class BzrSvnImportWorker(PullingImportWorker):
921 """An import worker for importing Subversion via bzr-svn."""
922
923
924=== modified file 'lib/lp/testing/factory.py'
925--- lib/lp/testing/factory.py 2010-01-13 02:53:14 +0000
926+++ lib/lp/testing/factory.py 2010-01-13 02:53:16 +0000
927@@ -1216,14 +1216,16 @@
928
929 def makeCodeImport(self, svn_branch_url=None, cvs_root=None,
930 cvs_module=None, product=None, branch_name=None,
931- git_repo_url=None, registrant=None, rcs_type=None):
932+ git_repo_url=None, hg_repo_url=None, registrant=None,
933+ rcs_type=None):
934 """Create and return a new, arbitrary code import.
935
936 The type of code import will be inferred from the source details
937 passed in, but defaults to a Subversion import from an arbitrary
938 unique URL.
939 """
940- if svn_branch_url is cvs_root is cvs_module is git_repo_url is None:
941+ if (svn_branch_url is cvs_root is cvs_module is git_repo_url is hg_repo_url
942+ is None):
943 svn_branch_url = self.getUniqueURL()
944
945 if product is None:
946@@ -1249,6 +1251,11 @@
947 registrant, product, branch_name,
948 rcs_type=RevisionControlSystems.GIT,
949 url=git_repo_url)
950+ elif hg_repo_url is not None:
951+ return code_import_set.new(
952+ registrant, product, branch_name,
953+ rcs_type=RevisionControlSystems.HG,
954+ url=hg_repo_url)
955 else:
956 assert rcs_type in (None, RevisionControlSystems.CVS)
957 return code_import_set.new(
958@@ -1322,7 +1329,7 @@
959 branch_id = self.getUniqueInteger()
960 if rcstype is None:
961 rcstype = 'svn'
962- if rcstype in ['svn', 'bzr-svn']:
963+ if rcstype in ['svn', 'bzr-svn', 'hg']:
964 assert cvs_root is cvs_module is None
965 if url is None:
966 url = self.getUniqueURL()
967
968=== added symlink 'lib/mercurial'
969=== target is u'../sourcecode/mercurial/mercurial'
970=== added symlink 'optionalbzrplugins/hg'
971=== target is u'../sourcecode/bzr-hg'
972=== modified file 'scripts/code-import-worker.py'
973--- scripts/code-import-worker.py 2009-11-17 02:49:41 +0000
974+++ scripts/code-import-worker.py 2010-01-13 02:53:16 +0000
975@@ -26,7 +26,7 @@
976 from lp.codehosting import load_optional_plugin
977 from lp.codehosting.codeimport.worker import (
978 BzrSvnImportWorker, CSCVSImportWorker, CodeImportSourceDetails,
979- GitImportWorker, get_default_bazaar_branch_store)
980+ GitImportWorker, HgImportWorker, get_default_bazaar_branch_store)
981 from canonical.launchpad import scripts
982
983
984@@ -46,6 +46,9 @@
985 elif source_details.rcstype == 'bzr-svn':
986 load_optional_plugin('svn')
987 import_worker_cls = BzrSvnImportWorker
988+ elif source_details.rcstype == 'hg':
989+ load_optional_plugin('hg')
990+ import_worker_cls = HgImportWorker
991 else:
992 if source_details.rcstype not in ['cvs', 'svn']:
993 raise AssertionError(
994
995=== modified file 'setup.py'
996--- setup.py 2010-01-12 01:45:22 +0000
997+++ setup.py 2010-01-13 02:53:16 +0000
998@@ -43,6 +43,7 @@
999 'lazr.uri',
1000 'lazr-js',
1001 'mechanize',
1002+ 'mercurial',
1003 'mocker',
1004 'oauth',
1005 'paramiko',
1006
1007=== modified file 'utilities/sourcedeps.conf'
1008--- utilities/sourcedeps.conf 2009-12-02 02:46:05 +0000
1009+++ utilities/sourcedeps.conf 2010-01-13 02:53:16 +0000
1010@@ -1,4 +1,5 @@
1011 bzr-git lp:~launchpad-pqm/bzr-git/devel;revno=248
1012+bzr-hg lp:~launchpad-pqm/bzr-hg/devel;revno=281
1013 bzr-loom lp:~launchpad-pqm/bzr-loom/trunk;revno=47
1014 bzr-svn lp:~launchpad-pqm/bzr-svn/devel;revno=2706
1015 cscvs lp:~launchpad-pqm/launchpad-cscvs/devel;revno=430
1016
1017=== modified file 'versions.cfg'
1018--- versions.cfg 2010-01-12 01:45:22 +0000
1019+++ versions.cfg 2010-01-13 02:53:16 +0000
1020@@ -40,6 +40,7 @@
1021 lazr-js = 0.9.2DEV
1022 martian = 0.11
1023 mechanize = 0.1.11
1024+mercurial = 1.3.1
1025 mocker = 0.10.1
1026 mozrunner = 1.3.4
1027 oauth = 1.0

Subscribers

People subscribed via source and target branches

to status/vote changes: