Merge lp:~jelmer/bzr-hg/sprout into lp:~launchpad-pqm/bzr-hg/devel

Proposed by Jelmer Vernooij
Status: Superseded
Proposed branch: lp:~jelmer/bzr-hg/sprout
Merge into: lp:~launchpad-pqm/bzr-hg/devel
Diff against target: 2260 lines (+1096/-407)
27 files modified
.bzrignore (+1/-0)
.testr.conf (+4/-0)
HACKING (+1/-3)
Makefile (+1/-1)
NEWS (+23/-2)
__init__.py (+144/-272)
branch.py (+131/-11)
dir.py (+395/-0)
fetch.py (+133/-74)
info.py (+4/-3)
mapping.py (+11/-4)
overlay.py (+1/-1)
parsers.py (+33/-0)
repository.py (+47/-5)
setup.py (+1/-1)
tests/__init__.py (+1/-0)
tests/test_branch.py (+4/-4)
tests/test_dir.py (+13/-6)
tests/test_fetch.py (+53/-0)
tests/test_mapping.py (+33/-1)
tests/test_parsers.py (+12/-1)
tests/test_pull.py (+5/-5)
tests/test_repository.py (+3/-3)
ui.py (+4/-4)
util.py (+1/-1)
versionedfiles.py (+2/-0)
workingtree.py (+35/-5)
To merge this branch: bzr merge lp:~jelmer/bzr-hg/sprout
Reviewer Review Type Date Requested Status
Launchpad PQM Bot Pending
Review via email: mp+54614@code.launchpad.net

This proposal has been superseded by a proposal from 2011-03-23.

Description of the change

Provide a custom sprout implementation. Fixes compatibility with newer versions of bzr.

To post a comment you must log in.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2009-05-21 15:53:00 +0000
3+++ .bzrignore 2011-03-23 21:41:04 +0000
4@@ -1,3 +1,4 @@
5 *.pyc
6 build
7 .plugins
8+.testrepository
9
10=== added file '.testr.conf'
11--- .testr.conf 1970-01-01 00:00:00 +0000
12+++ .testr.conf 2011-03-23 21:41:04 +0000
13@@ -0,0 +1,4 @@
14+[DEFAULT]
15+test_command=BZR_PLUGINS_AT=hg@`pwd` bzr selftest ^bzrlib.plugins.hg. Hg Mercurial --subunit $IDOPTION $LISTOPT
16+test_id_option=--load-list $IDFILE
17+test_list_option=--list
18
19=== modified file 'HACKING'
20--- HACKING 2009-10-10 01:17:00 +0000
21+++ HACKING 2011-03-23 21:41:04 +0000
22@@ -3,8 +3,7 @@
23
24 Unit testing
25 ~~~~~~~~~~~~
26-To run the bzr-hg testsuite, simply run
27-'bzr selftest --starting-with=bazaar.plugins.hg' or
28+To run the bzr-hg testsuite, simply run 'bzr selftest -s bp.hg' or
29 run 'make check' in the top-level bzr-hg directory.
30
31 Unavailable bzrlib API's
32@@ -24,4 +23,3 @@
33 Coding Style, etc
34 ~~~~~~~~~~~~~~~~~
35 Please refer to HACKING in the Bazaar source distribution.
36-
37
38=== modified file 'Makefile'
39--- Makefile 2010-07-28 16:15:17 +0000
40+++ Makefile 2011-03-23 21:41:04 +0000
41@@ -6,7 +6,7 @@
42 CTAGS ?= ctags
43 PYLINT ?= pylint
44 RST2HTML ?= rst2html
45-TESTS ?= bzrlib.tests.per_foreign_vcs.*Hg bzrlib.plugins.hg
46+TESTS ?= -s bt.per_foreign_vcs.*Hg -s bp.hg
47
48 all:: build
49
50
51=== modified file 'NEWS'
52--- NEWS 2010-08-14 00:41:20 +0000
53+++ NEWS 2011-03-23 21:41:04 +0000
54@@ -27,9 +27,14 @@
55 * Put idmap information in a bzr-hg-specific directory. (Jelmer Vernooij,
56 #599397)
57
58+ * Add basic support for tags. (Jelmer Vernooij)
59+
60+ * Check for Mercurial repository before checking for Bazaar repository.
61+ (Jelmer Vernooij, #674581, #688455)
62+
63 BUG FIXES
64
65- * Mark as compatible with Bazaar 2.1. (Jelmer Vernooij)
66+ * Mark as compatible with Bazaar 2.1, 2.2, 2.3 and 2.4. (Jelmer Vernooij)
67
68 * Check for Mercurial version before attempting to import it.
69 (#432100, Jelmer Vernooij)
70@@ -38,7 +43,7 @@
71
72 * Remove empty directories during fetch. (Jelmer Vernooij)
73
74- * Mark Mercurial 1.6 as supported, drop support for earlier versions.
75+ * Mark Mercurial 1.6, 1.7 and 1.8 as supported, drop support for earlier versions.
76 (#486899, Jelmer Vernooij)
77
78 * Don't warn about development versions of Mercurial. (#492292, Jelmer
79@@ -59,6 +64,22 @@
80 * Don't allow probing for Mercurial repositories over HTTP to break
81 bzr. (Jelmer Vernooij)
82
83+ * Fix recursive removing of parent empty directories. (#691994,
84+ Leonid Borisenko)
85+
86+ * WorkingTree.commit()'s message argument is now optional, consistent
87+ with bzr.dev. (#692902, Jelmer Vernooij)
88+
89+ * Fix handling of unknown strings in converted_from extra.
90+ (#670870, Jelmer Vernooij)
91+
92+ * Fix handling of first revision without changes. (Jelmer Vernooij, #688459)
93+
94+ * Support stop_revision in Branch.pull(). (Jelmer Vernooij, #544701)
95+
96+ * Provide custom ControlDir.sprout() implementation. Required for
97+ compatibility with bzr 2.4. (Jelmer Vernooij, #717937)
98+
99 DOCUMENTATION
100
101 * Add some basic instructions in INSTALL. (Martin Pool)
102
103=== modified file '__init__.py'
104--- __init__.py 2010-08-06 10:51:08 +0000
105+++ __init__.py 2011-03-23 21:41:04 +0000
106@@ -25,13 +25,12 @@
107
108 from info import (
109 bzr_compatible_versions,
110- hg_compatible_versions,
111+ hg_compatible_version_strings,
112 bzr_plugin_version as version_info,
113 )
114
115 bzrlib.api.require_any_api(bzrlib, bzr_compatible_versions)
116
117-import bzrlib.bzrdir
118 from bzrlib import (
119 errors,
120 trace,
121@@ -39,13 +38,17 @@
122 from bzrlib.foreign import (
123 foreign_vcs_registry,
124 )
125-import bzrlib.lockable_files
126 from bzrlib.send import (
127 format_registry as send_format_registry,
128 )
129
130+from bzrlib.controldir import (
131+ network_format_registry as controldir_network_format_registry,
132+ ControlDirFormat,
133+ Prober,
134+ )
135+
136 _mercurial_loaded = False
137-LockWarner = bzrlib.lockable_files._LockWarner
138
139 def lazy_load_mercurial():
140 global _mercurial_loaded
141@@ -61,7 +64,7 @@
142 from mercurial.__version__ import version as hg_version
143 if hg_version != "unknown":
144 hg_major_version = ".".join(hg_version.split(".")[:2])
145- if hg_major_version not in hg_compatible_versions and not "+" in hg_version:
146+ if hg_major_version not in hg_compatible_version_strings and not "+" in hg_version:
147 raise errors.DependencyNotPresent("mercurial",
148 'bzr-hg: Mercurial version %s not supported.' % hg_version)
149 trace.mutter("bzr-hg: using Mercurial %s" % hg_version)
150@@ -70,266 +73,79 @@
151 foreign_vcs_registry.register_lazy("hg",
152 "bzrlib.plugins.hg.mapping", "foreign_hg", "Mercurial")
153
154-class HgDummyLock(object):
155- """Lock that doesn't actually lock."""
156-
157- def __init__(self, hgrepo):
158- self._hgrepo = hgrepo
159-
160- def lock_write(self, token=None):
161- if token is not None:
162- raise errors.TokenLockingNotSupported(self)
163- self._lock = self._hgrepo.wlock()
164-
165- def lock_read(self):
166- self._lock = None
167-
168- def peek(self):
169- raise NotImplementedError(self.peek)
170-
171- def unlock(self):
172- if self._lock is not None:
173- self._lock.release()
174-
175- def validate_token(self, token):
176- if token is not None:
177- raise errors.TokenLockingNotSupported(self)
178-
179-
180-class HgLock(object):
181- """A lock that thunks through to Hg."""
182-
183- def __init__(self, hgrepo):
184- self._hgrepo = hgrepo
185-
186- def lock_write(self, token=None):
187- if token is not None:
188- raise errors.TokenLockingNotSupported(self)
189- self._lock = self._hgrepo.wlock()
190-
191- def lock_read(self):
192- self._lock = self._hgrepo.lock()
193-
194- def peek(self):
195- raise NotImplementedError(self.peek)
196-
197- def unlock(self):
198- self._lock.release()
199-
200- def validate_token(self, token):
201- if token is not None:
202- raise errors.TokenLockingNotSupported(self)
203-
204- def break_lock(self):
205- pass
206-
207-
208-class HgLockableFiles(bzrlib.lockable_files.LockableFiles):
209- """Hg specific lockable files abstraction."""
210-
211- def __init__(self, lock, transport):
212- self._lock = lock
213- self._transaction = None
214- self._lock_mode = None
215- self._transport = transport
216- self._lock_warner = LockWarner(repr(self))
217-
218-
219-class HgDir(bzrlib.bzrdir.BzrDir):
220- """An adapter to the '.hg' dir used by mercurial."""
221-
222- def __init__(self, hgrepo, transport, lockfiles, format):
223- self._format = format
224- self.root_transport = transport
225- self.transport = transport.clone('.hg')
226- self._hgrepo = hgrepo
227- self._lockfiles = lockfiles
228-
229- def backup_bzrdir(self):
230- self.root_transport.copy_tree(".hg", ".hg.backup")
231- return (self.root_transport.abspath(".hg"),
232- self.root_transport.abspath(".hg.backup"))
233-
234- def break_lock(self):
235- """Mercurial locks never break."""
236- raise NotImplementedError(self.break_lock)
237-
238- def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
239- """Clone this hg dir to url."""
240- self._make_tail(url)
241- if url.startswith('file://'):
242- url = url[len('file://'):]
243- url = url.encode('utf8')
244- result = self._format.initialize(url)
245- result._hgrepo.pull(self._hgrepo)
246- return result
247-
248- def create_branch(self, name=None):
249- """'create' a branch for this dir."""
250- return self.open_branch(name=name)
251-
252- def create_repository(self, shared=False):
253- """'create' a repository for this dir."""
254- if shared:
255- # dont know enough about hg yet to do 'shared repositories' in it.
256- raise errors.IncompatibleFormat(self._format, self._format)
257- return self.open_repository()
258-
259- def create_workingtree(self, revision_id=None, from_branch=None,
260- accelerator_tree=None, hardlink=False):
261- """'create' a workingtree for this dir."""
262- if revision_id is not None:
263- raise NotImplementedError("revision_id argument not yet supported")
264- if from_branch is not None:
265- raise NotImplementedError("from_branch argument not yet supported")
266- return self.open_workingtree()
267-
268- def destroy_branch(self, name=None):
269- if name is not None:
270- raise errors.NoColocatedBranchSupport(self)
271- raise errors.UnsupportedOperation(self.destroy_branch, self)
272-
273- def destroy_workingtree(self):
274- raise errors.UnsupportedOperation(self.destroy_workingtree, self)
275-
276- def destroy_repository(self):
277- raise errors.UnsupportedOperation(self.destroy_repository, self)
278-
279- def get_branch_transport(self, branch_format, name=None):
280- if name is not None:
281- raise errors.NoColocatedBranchSupport(self)
282- if branch_format is None:
283- return self.transport
284- if isinstance(branch_format, HgBzrDirFormat):
285- return self.transport
286- raise errors.IncompatibleFormat(branch_format, self._format)
287-
288- get_repository_transport = get_branch_transport
289- get_workingtree_transport = get_branch_transport
290-
291- def is_supported(self):
292- return True
293-
294- def needs_format_conversion(self, format=None):
295- return (format is not HgBzrDirFormat)
296-
297- def open_branch(self, name=None, unsupported=False,
298- ignore_fallbacks=False):
299- """'create' a branch for this dir."""
300- if name is not None:
301- raise errors.NoColocatedBranchSupport(self)
302- from bzrlib.plugins.hg.branch import HgLocalBranch, HgRemoteBranch
303- if self._hgrepo.local():
304- branch_klass = HgLocalBranch
305- else:
306- branch_klass = HgRemoteBranch
307- return branch_klass(self._hgrepo, self, self._lockfiles)
308-
309- def open_repository(self, shared=False):
310- """'open' a repository for this dir."""
311- from bzrlib.plugins.hg.repository import (
312- HgLocalRepository,
313- HgRemoteRepository,
314- )
315- if self._hgrepo.local():
316- repo_klass = HgLocalRepository
317- else:
318- repo_klass = HgRemoteRepository
319- return repo_klass(self._hgrepo, self, self._lockfiles)
320-
321- def open_workingtree(self, shared=False, recommend_upgrade=False):
322- """'open' a workingtree for this dir."""
323- from bzrlib.plugins.hg.workingtree import HgWorkingTree
324- return HgWorkingTree(self._hgrepo, self.open_branch(), self,
325- self._lockfiles)
326-
327- def cloning_metadir(self, stacked=False):
328- return bzrlib.bzrdir.format_registry.make_bzrdir("default-rich-root")
329-
330-
331-class HgToSomethingConverter(bzrlib.bzrdir.Converter):
332- """A class to upgrade an hg dir to something else."""
333-
334- def __init__(self, format):
335- self.format = format
336- if self.format is None:
337- self.format = bzrlib.bzrdir.BzrDirFormat.get_default_format()
338-
339- def convert(self, bzrdir, pb):
340- source_repo = bzrdir.open_repository()
341- source_branch = bzrdir.open_branch()
342- target = self.format.initialize_on_transport(bzrdir.root_transport)
343- target_repo = target.create_repository()
344- target_repo.fetch(source_repo, pb=pb)
345- target_branch = target.create_branch()
346- target_branch.generate_revision_history(source_branch.last_revision())
347- target_wt = target.create_workingtree()
348- bzrdir.root_transport.delete_tree(".hg")
349- return target
350-
351-
352-class HgBzrDirFormat(bzrlib.bzrdir.BzrDirFormat):
353- """The .hg directory control format."""
354-
355- def __init__(self):
356- super(HgBzrDirFormat, self).__init__()
357- self.workingtree_format = None
358-
359- def get_converter(self, format):
360- """We should write a converter."""
361- return HgToSomethingConverter(format)
362-
363- def network_name(self):
364- return "hg"
365-
366- def get_format_description(self):
367- return "Mercurial Branch"
368-
369- def initialize_on_transport(self, transport):
370- """Initialize a new .not dir in the base directory of a Transport."""
371- return self.open(transport, _create=True)
372-
373- def __eq__(self, other):
374- return type(self) == type(other)
375-
376- @classmethod
377- def _known_formats(self):
378- return set([HgBzrDirFormat()])
379-
380- def open(self, transport, _create=False, _found=None):
381- """Open this directory.
382-
383- :param _create: create the hg dir on the fly. private to HgBzrDirFormat.
384- """
385- # we dont grok readonly - hg isn't integrated with transport.
386- if transport.base.startswith('readonly+'):
387- transport = transport._decorated
388- if transport.base.startswith('file://'):
389- path = transport.local_abspath('.').encode('utf-8')
390- lock_class = HgLock
391- else:
392- path = transport.base
393- lock_class = HgDummyLock
394- lazy_load_mercurial()
395- import mercurial.hg
396- from bzrlib.plugins.hg.ui import ui
397- repository = mercurial.hg.repository(ui(), path, create=_create)
398- lockfiles = HgLockableFiles(lock_class(repository), transport)
399- return HgDir(repository, transport, lockfiles, self)
400-
401- @classmethod
402- def probe_transport(klass, transport):
403- """Our format is present if the transport ends in '.not/'."""
404- # little ugly, but works
405- format = klass()
406- from bzrlib.transport.local import LocalTransport
407- lazy_load_mercurial()
408- from mercurial import error as hg_errors
409- if isinstance(transport, LocalTransport) and not transport.has(".hg"):
410+def has_hg_http_smart_server(transport, external_url):
411+ if not external_url.startswith("http:") and not external_url.startswith("https:"):
412+ return False
413+ url = external_url + "?pairs=%s-%s&cmd=between" % ("0" * 40, "0" * 40)
414+ from bzrlib.transport.http._urllib import HttpTransport_urllib, Request
415+ if isinstance(transport, HttpTransport_urllib):
416+ req = Request('GET', url, accepted_errors=[200, 403, 404, 405])
417+ req.follow_redirections = True
418+ resp = transport._perform(req)
419+ if resp.code == 404:
420+ return False
421+ headers = resp.headers
422+ else:
423+ try:
424+ from bzrlib.transport.http._pycurl import PyCurlTransport
425+ except errors.DependencyNotPresent:
426+ return False
427+ else:
428+ import pycurl
429+ from cStringIO import StringIO
430+ if isinstance(transport, PyCurlTransport):
431+ conn = transport._get_curl()
432+ conn.setopt(pycurl.URL, url)
433+ transport._set_curl_options(conn)
434+ conn.setopt(pycurl.HTTPGET, 1)
435+ conn.setopt(pycurl.NOBODY, 1)
436+ header = StringIO()
437+ data = StringIO()
438+ conn.setopt(pycurl.HEADERFUNCTION, header.write)
439+ conn.setopt(pycurl.WRITEFUNCTION, data.write)
440+ transport._curl_perform(conn, header)
441+ code = conn.getinfo(pycurl.HTTP_CODE)
442+ if code == 404:
443+ raise errors.NoSuchFile(transport._path)
444+ headers = transport._parse_headers(header)
445+ else:
446+ return False
447+ ct = headers.getheader("Content-Type")
448+ return ct.startswith("application/mercurial")
449+
450+
451+def has_hg_dumb_repository(transport):
452+ try:
453+ return transport.has(".hg/requires")
454+ except (errors.NoSuchFile, errors.PermissionDenied):
455+ return False
456+
457+
458+class HgProber(Prober):
459+
460+ # Perhaps retrieve list from mercurial.hg.schemes ?
461+ _supported_schemes = ["http", "https", "file", "ssh"]
462+
463+ def probe_transport(self, transport):
464+ try:
465+ external_url = transport.external_url()
466+ except errors.InProcessTransport:
467+ raise errors.NotBranchError(path=transport.base)
468+ scheme = external_url.split(":")[0]
469+ if scheme not in self._supported_schemes:
470+ raise errors.NotBranchError(path=transport.base)
471+ if (not has_hg_dumb_repository(transport) and
472+ not has_hg_http_smart_server(transport, external_url)):
473 # Explicitly check for .hg directories here, so we avoid
474 # loading foreign branches through Mercurial.
475 raise errors.NotBranchError(path=transport.base)
476+
477+ lazy_load_mercurial()
478+ from mercurial import error as hg_errors
479+
480 import urllib2
481+ from bzrlib.plugins.hg.dir import HgControlDirFormat
482+ format = HgControlDirFormat()
483 try:
484 format.open(transport)
485 except hg_errors.RepoError, e:
486@@ -342,11 +158,68 @@
487 raise errors.NotBranchError(path=transport.base)
488 return format
489
490-
491-bzrlib.bzrdir.BzrDirFormat.register_control_format(HgBzrDirFormat)
492-
493-bzrlib.bzrdir.format_registry.register("hg",
494- HgBzrDirFormat, "Mercurial repository. ", native=False, hidden=False)
495+ @classmethod
496+ def known_formats(cls):
497+ from bzrlib.plugins.hg.dir import HgControlDirFormat
498+ return set([HgControlDirFormat()])
499+
500+
501+ControlDirFormat.register_prober(HgProber)
502+ControlDirFormat._server_probers.insert(0, HgProber)
503+if not getattr(Prober, "known_formats", False): # bzr < 2.4
504+ from bzrlib.plugins.hg.dir import HgControlDirFormat
505+ ControlDirFormat.register_format(HgControlDirFormat())
506+
507+controldir_network_format_registry.register_lazy("hg",
508+ "bzrlib.plugins.hg.dir", "HgControlDirFormat")
509+
510+bzrlib.bzrdir.format_registry.register_lazy("hg",
511+ "bzrlib.plugins.hg.dir", "HgControlDirFormat",
512+ "Mercurial repository. ", native=False, hidden=False)
513+
514+from bzrlib.repository import (
515+ format_registry as repository_format_registry,
516+ network_format_registry as repository_network_format_registry,
517+ )
518+repository_network_format_registry.register_lazy('hg',
519+ 'bzrlib.plugins.hg.repository', 'HgRepositoryFormat')
520+
521+
522+from bzrlib.branch import (
523+ network_format_registry as branch_network_format_registry,
524+ )
525+branch_network_format_registry.register_lazy(
526+ "hg", "bzrlib.plugins.hg.branch", "HgBranchFormat")
527+
528+try:
529+ from bzrlib.branch import (
530+ format_registry as branch_format_registry,
531+ )
532+except ImportError: # bzr < 2.4
533+ pass
534+else:
535+ branch_format_registry.register_extra_lazy(
536+ "bzrlib.plugins.hg.branch", "HgBranchFormat")
537+
538+try:
539+ from bzrlib.workingtree import (
540+ format_registry as workingtree_format_registry,
541+ )
542+except ImportError: # bzr < 2.4
543+ pass
544+else:
545+ workingtree_format_registry.register_extra_lazy(
546+ "bzrlib.plugins.hg.workingtree", "HgWorkingTreeFormat")
547+
548+
549+try:
550+ register_extra_lazy_repository_format = getattr(repository_format_registry,
551+ "register_extra_lazy")
552+except AttributeError: # bzr < 2.4
553+ pass
554+else:
555+ register_extra_lazy_repository_format('bzrlib.plugins.hg.repository',
556+ 'HgRepositoryFormat')
557
558 send_format_registry.register_lazy('hg', 'bzrlib.plugins.hg.send',
559 'send_hg', 'Mecurial bundle format')
560@@ -360,15 +233,14 @@
561 )
562 plugin_cmds.register_lazy('cmd_hg_import', [], 'bzrlib.plugins.hg.commands')
563
564-try:
565- from bzrlib.revisionspec import dwim_revspecs
566-except ImportError:
567- pass
568-else:
569+from bzrlib.revisionspec import dwim_revspecs, RevisionSpec_dwim
570+if getattr(RevisionSpec_dwim, "append_possible_lazy_revspec", None):
571+ RevisionSpec_dwim.append_possible_lazy_revspec(
572+ "bzrlib.plugins.hg.revspec", "RevisionSpec_hg")
573+else: # bzr < 2.4
574 from bzrlib.plugins.hg.revspec import RevisionSpec_hg
575 dwim_revspecs.append(RevisionSpec_hg)
576
577-
578 def test_suite():
579 from unittest import TestSuite, TestLoader
580 from bzrlib.plugins.hg import tests
581
582=== modified file 'branch.py'
583--- branch.py 2010-08-14 00:38:48 +0000
584+++ branch.py 2011-03-23 21:41:04 +0000
585@@ -1,5 +1,5 @@
586-# Copyright (C) 2005, 2006 Canonical Ltd
587-# Copyright (C) 2008-2009 Jelmer Vernooij <jelmer@samba.org>
588+# Copyright (C) 2005, 2006, 2011 Canonical Ltd
589+# Copyright (C) 2008-2011 Jelmer Vernooij <jelmer@samba.org>
590 #
591 # This program is free software; you can redistribute it and/or modify
592 # it under the terms of the GNU General Public License as published by
593@@ -39,6 +39,10 @@
594 from bzrlib.repository import (
595 InterRepository,
596 )
597+from bzrlib.tag import (
598+ BasicTags,
599+ DisabledTags,
600+ )
601
602 from bzrlib.plugins.hg.changegroup import (
603 dchangegroup,
604@@ -48,6 +52,49 @@
605 _fmt = "Push is not yet supported for bzr-hg. Try dpush instead."
606
607
608+class HgTags(BasicTags):
609+
610+ def __init__(self, branch):
611+ self.branch = branch
612+
613+ def _get_hg_tags(self):
614+ raise NotImplementedError(self._get_hg_tags)
615+
616+ def get_tag_dict(self):
617+ ret = {}
618+ hgtags = self._get_hg_tags()
619+ for name, value in hgtags.iteritems():
620+ ret[name] = self.branch.repository.lookup_foreign_revision_id(value)
621+ return ret
622+
623+ def set_tag(self, name, value):
624+ self.branch.repository._hgrepo.tag([name],
625+ self.branch.repository.lookup_bzr_revision_id(value)[0],
626+ "Create tag %s" % name,
627+ True,
628+ self.branch.get_config().username(), None)
629+
630+
631+class LocalHgTags(HgTags):
632+
633+ def _get_hg_tags(self):
634+ return self.branch.repository._hgrepo.tags()
635+
636+
637+class FileHgTags(HgTags):
638+
639+ def __init__(self, branch, revid):
640+ self.branch = branch
641+ self.revid = revid
642+
643+ def _get_hg_tags(self):
644+ revtree = self.branch.repository.revision_tree(self.revid)
645+ f = revtree.get_file_text(revtree.path2id(".hgtags"), ".hgtags")
646+ for l in f.readlines():
647+ (name, hgtag) = l.strip().split(" ")
648+ yield name, hgtag
649+
650+
651 class HgBranchFormat(BranchFormat):
652 """Mercurial Branch Format.
653
654@@ -56,6 +103,11 @@
655 support the branch format.
656 """
657
658+ @property
659+ def _matchingbzrdir(self):
660+ from bzrlib.plugins.hg.dir import HgControlDirFormat
661+ return HgControlDirFormat()
662+
663 def get_format_description(self):
664 """See BranchFormat.get_format_description()."""
665 return "Mercurial Branch"
666@@ -67,6 +119,25 @@
667 from bzrlib.plugins.hg.tests.test_branch import ForeignTestsBranchFactory
668 return ForeignTestsBranchFactory()
669
670+ def initialize(self, a_bzrdir, name=None, repository=None):
671+ from bzrlib.plugins.hg.dir import HgDir
672+ if name is None:
673+ name = 'default'
674+ if not isinstance(a_bzrdir, HgDir):
675+ raise errors.IncompatibleFormat(self, a_bzrdir._format)
676+ bm = a_bzrdir._hgrepo.branchmap()
677+ if name in bm:
678+ raise errors.AlreadyBranchError(a_bzrdir.user_url)
679+ return a_bzrdir.open_branch(name=name)
680+
681+ def make_tags(self, branch):
682+ """See bzrlib.branch.BranchFormat.make_tags()."""
683+ if getattr(branch.repository._hgrepo, "tags", None) is not None:
684+ return LocalHgTags(branch)
685+ else:
686+ return DisabledTags(branch)
687+
688+
689
690 class HgBranchConfig(object):
691 """Access Branch Configuration data for an HgBranch.
692@@ -78,6 +149,9 @@
693 self._branch = branch
694 self._ui = branch.repository._hgrepo.ui
695
696+ def username(self):
697+ return self._ui.config("username", "default")
698+
699 def get_nickname(self):
700 # remove the trailing / and take the basename.
701 return os.path.basename(self._branch.base[:-1])
702@@ -92,13 +166,13 @@
703 return False
704
705 def get_user_option(self, name):
706- return None
707+ return self._ui.config(name, "bazaar")
708
709 def get_user_option_as_bool(self, name):
710 return False
711
712 def set_user_option(self, name, value, warn_masked=False):
713- pass # FIXME: Uhm?
714+ self._ui.setconfig(name, "bazaar", value)
715
716 def log_format(self):
717 """What log format should be used"""
718@@ -120,14 +194,22 @@
719 class HgBranch(ForeignBranch):
720 """An adapter to mercurial repositories for bzr Branch objects."""
721
722- def __init__(self, hgrepo, hgdir, lockfiles):
723- self._format = HgBranchFormat()
724+ @property
725+ def control_url(self):
726+ return self.bzrdir.control_url
727+
728+ @property
729+ def control_transport(self):
730+ return self.bzrdir.control_transport
731+
732+ def __init__(self, hgrepo, name, hgdir, lockfiles):
733 self.repository = hgdir.open_repository()
734 ForeignBranch.__init__(self, self.repository.get_mapping())
735 self._hgrepo = hgrepo
736 self.bzrdir = hgdir
737 self.control_files = lockfiles
738 self.base = hgdir.root_transport.base
739+ self.name = name
740
741 def _check(self):
742 # TODO: Call out to mercurial for consistency checking?
743@@ -195,21 +277,42 @@
744 revision_id = source_revision_id
745 destination.generate_revision_history(revision_id)
746
747+ def _tip(self):
748+ try:
749+ return self._hgrepo.branchmap()[self.name][0]
750+ except KeyError:
751+ import mercurial.node
752+ return mercurial.node.nullid
753+
754
755 class HgLocalBranch(HgBranch):
756
757+ def __init__(self, hgrepo, name, hgdir, lockfiles):
758+ self._format = HgBranchFormat()
759+ super(HgLocalBranch, self).__init__(hgrepo, name, hgdir, lockfiles)
760+
761+ def supports_tags(self):
762+ return True
763+
764 @needs_read_lock
765 def last_revision(self):
766- tip = self._hgrepo.lookup("tip")
767+ tip = self._tip()
768 return self.repository.lookup_foreign_revision_id(tip,
769 mapping=self.mapping)
770
771
772 class HgRemoteBranch(HgBranch):
773
774+ def __init__(self, hgrepo, name, hgdir, lockfiles):
775+ self._format = HgBranchFormat()
776+ super(HgRemoteBranch, self).__init__(hgrepo, name, hgdir, lockfiles)
777+
778+ def supports_tags(self):
779+ return getattr(self.repository._hgrepo, "tags", None) is not None
780+
781 @needs_read_lock
782 def last_revision(self):
783- tip = self._hgrepo.lookup("tip")
784+ tip = self._tip()
785 return self.mapping.revision_id_foreign_to_bzr(tip)
786
787
788@@ -234,7 +337,15 @@
789 result.old_revno, result.old_revid = self.target.last_revision_info()
790 inter = InterRepository.get(self.source.repository,
791 self.target.repository)
792+ if stop_revision is None:
793+ stop_revision = self.source.last_revision()
794 inter.fetch(revision_id=stop_revision)
795+ if overwrite:
796+ req_base = None
797+ else:
798+ req_base = self.target.last_revision()
799+ self.target.generate_revision_history(stop_revision,
800+ req_base, self.source)
801 result.new_revno, result.new_revid = self.target.last_revision_info()
802 return result
803
804@@ -284,6 +395,8 @@
805 self.target.generate_revision_history(self.source.last_revision(),
806 req_base, self.source)
807 result.new_revno, result.new_revid = self.target.last_revision_info()
808+ tags = FileHgTags(self.target, result.new_revid)
809+ result.tag_conflicts = tags.merge_to(self.target.tags, overwrite)
810 return result
811
812 def push(self, overwrite=False, stop_revision=None):
813@@ -294,11 +407,18 @@
814 result.old_revid = self.target.last_revision()
815 inter = InterRepository.get(self.source.repository,
816 self.target.repository)
817+ if stop_revision is not None:
818+ stop_revision = self.source.last_revision()
819 inter.fetch(revision_id=stop_revision)
820- self.target.generate_revision_history(self.source.last_revision(),
821- self.target.last_revision(),
822- self.source)
823+ if overwrite:
824+ req_base = None
825+ else:
826+ req_base = self.target.last_revision()
827+ self.target.generate_revision_history(stop_revision, req_base,
828+ self.source)
829 result.new_revid = self.target.last_revision()
830+ tags = FileHgTags(self.target, result.new_revid)
831+ result.tag_conflicts = tags.merge_to(self.target.tags, overwrite)
832 return result
833
834
835
836=== added file 'dir.py'
837--- dir.py 1970-01-01 00:00:00 +0000
838+++ dir.py 2011-03-23 21:41:04 +0000
839@@ -0,0 +1,395 @@
840+# Copyright (C) 2005-2011 Canonical Ltd
841+# Copyright (C) 2008-2010 Jelmer Vernooij <jelmer@samba.org>
842+#
843+# This program is free software; you can redistribute it and/or modify
844+# it under the terms of the GNU General Public License as published by
845+# the Free Software Foundation; either version 2 of the License, or
846+# (at your option) any later version.
847+#
848+# This program is distributed in the hope that it will be useful,
849+# but WITHOUT ANY WARRANTY; without even the implied warranty of
850+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
851+# GNU General Public License for more details.
852+#
853+# You should have received a copy of the GNU General Public License
854+# along with this program; if not, write to the Free Software
855+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
856+
857+"""Mercurial control directory support.
858+
859+"""
860+
861+import bzrlib.bzrdir
862+import bzrlib.lockable_files
863+from bzrlib import (
864+ errors,
865+ urlutils,
866+ )
867+
868+from bzrlib.controldir import (
869+ ControlDir,
870+ ControlDirFormat,
871+ )
872+try:
873+ from bzrlib.controldir import Converter
874+except ImportError: # bzr < 2.4
875+ from bzrlib.bzrdir import Converter
876+from bzrlib.plugins.hg import (
877+ lazy_load_mercurial,
878+ )
879+
880+
881+class HgControlDirConfig(object):
882+
883+ def get_default_stack_on(self):
884+ return None
885+
886+ def set_default_stack_on(self, value):
887+ raise errors.BzrError("Cannot set configuration")
888+
889+
890+class HgDir(ControlDir):
891+ """An adapter to the '.hg' dir used by mercurial."""
892+
893+ @property
894+ def user_transport(self):
895+ return self.root_transport
896+
897+ @property
898+ def control_transport(self):
899+ return self.transport
900+
901+ def is_control_filename(self, filename):
902+ return (filename == ".hg" or filename.startswith(".hg/"))
903+
904+ def __init__(self, hgrepo, transport, lockfiles, format):
905+ self._format = format
906+ self.root_transport = transport
907+ self.transport = transport.clone('.hg')
908+ self._hgrepo = hgrepo
909+ self._lockfiles = lockfiles
910+
911+ def backup_bzrdir(self):
912+ self.root_transport.copy_tree(".hg", ".hg.backup")
913+ return (self.root_transport.abspath(".hg"),
914+ self.root_transport.abspath(".hg.backup"))
915+
916+ def break_lock(self):
917+ """Mercurial locks never break."""
918+ raise NotImplementedError(self.break_lock)
919+
920+ def clone_on_transport(self, transport, revision_id=None,
921+ force_new_repo=False, preserve_stacking=False, stacked_on=None,
922+ create_prefix=False, use_existing_dir=True, no_tree=False):
923+ """See ControlDir.clone_on_transport."""
924+ path = transport.local_abspath(".")
925+ result = self._format.initialize(path)
926+ result._hgrepo.pull(self._hgrepo)
927+ return result
928+
929+ def create_branch(self, name=None, repository=None):
930+ """'create' a branch for this dir."""
931+ return self.open_branch(name=name)
932+
933+ def create_repository(self, shared=False):
934+ """'create' a repository for this dir."""
935+ if shared:
936+ raise errors.IncompatibleFormat(self._format, self._format)
937+ return self.open_repository()
938+
939+ def create_workingtree(self, revision_id=None, from_branch=None,
940+ accelerator_tree=None, hardlink=False):
941+ """'create' a workingtree for this dir."""
942+ if revision_id is not None:
943+ raise NotImplementedError("revision_id argument not yet supported")
944+ if from_branch is not None:
945+ raise NotImplementedError("from_branch argument not yet supported")
946+ return self.open_workingtree()
947+
948+ def destroy_branch(self, name=None):
949+ raise errors.UnsupportedOperation(self.destroy_branch, self)
950+
951+ def destroy_workingtree(self):
952+ raise errors.UnsupportedOperation(self.destroy_workingtree, self)
953+
954+ def destroy_repository(self):
955+ raise errors.UnsupportedOperation(self.destroy_repository, self)
956+
957+ def get_branch_transport(self, branch_format, name=None):
958+ if branch_format is None:
959+ return self.transport
960+ if isinstance(branch_format, HgControlDirFormat):
961+ return self.transport
962+ raise errors.IncompatibleFormat(branch_format, self._format)
963+
964+ get_repository_transport = get_branch_transport
965+ get_workingtree_transport = get_branch_transport
966+
967+ def is_supported(self):
968+ return True
969+
970+ def needs_format_conversion(self, format=None):
971+ return (format is not HgControlDirFormat)
972+
973+ def open_branch(self, name=None, unsupported=False,
974+ ignore_fallbacks=False):
975+ """'create' a branch for this dir."""
976+ if name is None:
977+ name = 'default'
978+ from bzrlib.plugins.hg.branch import HgLocalBranch, HgRemoteBranch
979+ if self._hgrepo.local():
980+ branch_klass = HgLocalBranch
981+ else:
982+ branch_klass = HgRemoteBranch
983+ return branch_klass(self._hgrepo, name, self, self._lockfiles)
984+
985+ def find_repository(self, shared=False):
986+ return self.open_repository(shared)
987+
988+ def open_repository(self, shared=False):
989+ """'open' a repository for this dir."""
990+ from bzrlib.plugins.hg.repository import (
991+ HgLocalRepository,
992+ HgRemoteRepository,
993+ )
994+ if self._hgrepo.local():
995+ repo_klass = HgLocalRepository
996+ else:
997+ repo_klass = HgRemoteRepository
998+ return repo_klass(self._hgrepo, self, self._lockfiles)
999+
1000+ def open_workingtree(self, shared=False, recommend_upgrade=False):
1001+ """'open' a workingtree for this dir."""
1002+ from bzrlib.plugins.hg.workingtree import HgWorkingTree
1003+ return HgWorkingTree(self._hgrepo, self.open_branch(), self,
1004+ self._lockfiles)
1005+
1006+ def cloning_metadir(self, stacked=False):
1007+ return bzrlib.bzrdir.format_registry.make_bzrdir("default-rich-root")
1008+
1009+ def get_config(self):
1010+ return HgControlDirConfig()
1011+
1012+ def sprout(self, url, revision_id=None, force_new_repo=False,
1013+ recurse='down', possible_transports=None,
1014+ accelerator_tree=None, hardlink=False, stacked=False,
1015+ source_branch=None, create_tree_if_local=True):
1016+ from bzrlib.repository import InterRepository
1017+ from bzrlib.transport.local import LocalTransport
1018+ from bzrlib.transport import get_transport
1019+ target_transport = get_transport(url, possible_transports)
1020+ target_transport.ensure_base()
1021+ cloning_format = self.cloning_metadir()
1022+ # Create/update the result branch
1023+ result = cloning_format.initialize_on_transport(target_transport)
1024+ source_branch = self.open_branch()
1025+ source_repository = self.find_repository()
1026+ try:
1027+ result_repo = result.find_repository()
1028+ except errors.NoRepositoryPresent:
1029+ result_repo = result.create_repository()
1030+ target_is_empty = True
1031+ else:
1032+ target_is_empty = None # Unknown
1033+ if stacked:
1034+ raise errors.IncompatibleRepositories(source_repository, result_repo)
1035+ interrepo = InterRepository.get(source_repository, result_repo)
1036+ interrepo.fetch(revision_id=revision_id)
1037+ result_branch = source_branch.sprout(result,
1038+ revision_id=revision_id, repository=result_repo)
1039+ if (create_tree_if_local and isinstance(target_transport, LocalTransport)
1040+ and (result_repo is None or result_repo.make_working_trees())):
1041+ wt = result.create_workingtree(accelerator_tree=accelerator_tree,
1042+ hardlink=hardlink, from_branch=result_branch)
1043+ wt.lock_write()
1044+ try:
1045+ if wt.path2id('') is None:
1046+ try:
1047+ wt.set_root_id(self.open_workingtree.get_root_id())
1048+ except errors.NoWorkingTree:
1049+ pass
1050+ finally:
1051+ wt.unlock()
1052+ return result
1053+
1054+
1055+class HgToSomethingConverter(Converter):
1056+ """A class to upgrade an hg dir to something else."""
1057+
1058+ def __init__(self, format):
1059+ self.format = format
1060+ if self.format is None:
1061+ self.format = ControlDirFormat.get_default_format()
1062+
1063+ def convert(self, bzrdir, pb):
1064+ source_repo = bzrdir.open_repository()
1065+ source_branch = bzrdir.open_branch()
1066+ target = self.format.initialize_on_transport(bzrdir.root_transport)
1067+ target_repo = target.create_repository()
1068+ target_repo.fetch(source_repo, pb=pb)
1069+ target_branch = target.create_branch()
1070+ target_branch.generate_revision_history(source_branch.last_revision())
1071+ target_wt = target.create_workingtree()
1072+ bzrdir.root_transport.delete_tree(".hg")
1073+ return target
1074+
1075+
1076+LockWarner = bzrlib.lockable_files._LockWarner
1077+
1078+
1079+class HgLockableFiles(bzrlib.lockable_files.LockableFiles):
1080+ """Hg specific lockable files abstraction."""
1081+
1082+ def __init__(self, lock, transport):
1083+ self.lock_name = "hg lock"
1084+ self._lock = lock
1085+ self._transaction = None
1086+ self._lock_mode = None
1087+ self._transport = transport
1088+ self._lock_warner = LockWarner(repr(self))
1089+
1090+
1091+class HgDummyLock(object):
1092+ """Lock that doesn't actually lock."""
1093+
1094+ def __init__(self, hgrepo):
1095+ self._hgrepo = hgrepo
1096+
1097+ def lock_write(self, token=None):
1098+ if token is not None:
1099+ raise errors.TokenLockingNotSupported(self)
1100+ self._lock = self._hgrepo.wlock()
1101+
1102+ def lock_read(self):
1103+ self._lock = None
1104+
1105+ def peek(self):
1106+ raise NotImplementedError(self.peek)
1107+
1108+ def unlock(self):
1109+ if self._lock is not None:
1110+ self._lock.release()
1111+
1112+ def validate_token(self, token):
1113+ if token is not None:
1114+ raise errors.TokenLockingNotSupported(self)
1115+
1116+
1117+class HgLock(object):
1118+ """A lock that thunks through to Hg."""
1119+
1120+ def __init__(self, hgrepo):
1121+ self._hgrepo = hgrepo
1122+
1123+ def lock_write(self, token=None):
1124+ if token is not None:
1125+ raise errors.TokenLockingNotSupported(self)
1126+ self._lock = self._hgrepo.wlock()
1127+
1128+ def lock_read(self):
1129+ self._lock = self._hgrepo.lock()
1130+
1131+ def peek(self):
1132+ raise NotImplementedError(self.peek)
1133+
1134+ def unlock(self):
1135+ self._lock.release()
1136+
1137+ def validate_token(self, token):
1138+ if token is not None:
1139+ raise errors.TokenLockingNotSupported(self)
1140+
1141+ def break_lock(self):
1142+ pass
1143+
1144+
1145+class HgControlDirFormat(ControlDirFormat):
1146+ """The .hg directory control format."""
1147+
1148+ colocated_branches = True
1149+ fixed_components = True
1150+
1151+ def __init__(self):
1152+ super(HgControlDirFormat, self).__init__()
1153+ self.workingtree_format = None
1154+
1155+ def get_converter(self, format):
1156+ """We should write a converter."""
1157+ return HgToSomethingConverter(format)
1158+
1159+ def network_name(self):
1160+ return "hg"
1161+
1162+ def get_format_description(self):
1163+ return "Mercurial Branch"
1164+
1165+ def initialize_on_transport_ex(self, transport, use_existing_dir=False,
1166+ create_prefix=False, force_new_repo=False, stacked_on=None,
1167+ stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
1168+ shared_repo=False, vfs_only=False):
1169+ from bzrlib import trace
1170+ from bzrlib.bzrdir import CreateRepository
1171+ from bzrlib.transport import do_catching_redirections
1172+ def make_directory(transport):
1173+ transport.mkdir('.')
1174+ return transport
1175+ def redirected(transport, e, redirection_notice):
1176+ trace.note(redirection_notice)
1177+ return transport._redirected_to(e.source, e.target)
1178+ try:
1179+ transport = do_catching_redirections(make_directory, transport,
1180+ redirected)
1181+ except errors.FileExists:
1182+ if not use_existing_dir:
1183+ raise
1184+ except errors.NoSuchFile:
1185+ if not create_prefix:
1186+ raise
1187+ transport.create_prefix()
1188+ controldir = self.initialize_on_transport(transport)
1189+ repository = controldir.open_repository()
1190+ repository.lock_write()
1191+ return (repository, controldir, False, CreateRepository(controldir))
1192+
1193+ def get_branch_format(self):
1194+ from bzrlib.plugins.hg.branch import HgBranchFormat
1195+ return HgBranchFormat()
1196+
1197+ @property
1198+ def repository_format(self):
1199+ from bzrlib.plugins.hg.repository import HgRepositoryFormat
1200+ return HgRepositoryFormat()
1201+
1202+ def initialize_on_transport(self, transport):
1203+ """Initialize a new .not dir in the base directory of a Transport."""
1204+ return self.open(transport, _create=True)
1205+
1206+ def __eq__(self, other):
1207+ return type(self) == type(other)
1208+
1209+ def __hash__(self):
1210+ return hash((self.__class__,))
1211+
1212+ def open(self, transport, _create=False, _found=None):
1213+ """Open this directory.
1214+
1215+ :param _create: create the hg dir on the fly. private to
1216+ HgControlDirFormat.
1217+ """
1218+ # we dont grok readonly - hg isn't integrated with transport.
1219+ try:
1220+ url = transport.external_url()
1221+ except errors.InProcessTransport:
1222+ raise errors.NotBranchError(transport.base)
1223+ if url.startswith('file://'):
1224+ path = transport.local_abspath('.').encode('utf-8')
1225+ lock_class = HgLock
1226+ else:
1227+ path = url
1228+ lock_class = HgDummyLock
1229+ lazy_load_mercurial()
1230+ import mercurial.hg
1231+ from bzrlib.plugins.hg.ui import ui
1232+ repository = mercurial.hg.repository(ui(), path, create=_create)
1233+ lockfiles = HgLockableFiles(lock_class(repository), transport)
1234+ return HgDir(repository, transport, lockfiles, self)
1235
1236=== modified file 'fetch.py'
1237--- fetch.py 2010-08-14 00:41:20 +0000
1238+++ fetch.py 2011-03-23 21:41:04 +0000
1239@@ -70,6 +70,8 @@
1240 get_overlay,
1241 )
1242 from bzrlib.plugins.hg.parsers import (
1243+ chunkiter,
1244+ deserialize_file_text,
1245 parse_changeset,
1246 parse_manifest,
1247 unpack_chunk_iter,
1248@@ -145,6 +147,12 @@
1249 # Set of directories that have been created in this delta, their file id
1250 # as value.
1251 directories = {}
1252+ if basis_inv is None:
1253+ # Root is mandatory
1254+ extra, root_id = inventory_create_directory(directories,
1255+ basis_inv, other_inv, "", lookup_file_id, revid)
1256+ for e in extra:
1257+ yield e
1258 # Dictionary of directories that could have been made empty in this delta,
1259 # with the set of removed children as value.
1260 maybe_empty_dirs = defaultdict(set)
1261@@ -209,17 +217,20 @@
1262 ie.symlink_target = lookup_symlink((fileid, ie.revision))
1263 yield (old_path, path, fileid, ie)
1264 # Remove empty directories
1265- for path in sorted(maybe_empty_dirs.keys(), reverse=True):
1266- if maybe_empty_dirs[path] is None:
1267- # Stuff was added to this directory in this revision, don't bother
1268- continue
1269- file_id = basis_inv.path2id(path)
1270- # Is this directory really empty ?
1271- if set(basis_inv[file_id].children.keys()) == maybe_empty_dirs[path]:
1272- yield (path, None, file_id, None)
1273- dirname = os.path.dirname(path)
1274- if maybe_empty_dirs[dirname] is not None:
1275- maybe_empty_dirs[dirname].add(basis_inv[file_id].name)
1276+ while maybe_empty_dirs:
1277+ for path in sorted(maybe_empty_dirs.keys(), reverse=True):
1278+ removed_children = maybe_empty_dirs.pop(path)
1279+ if removed_children is None:
1280+ # Stuff was added to this directory in this revision,
1281+ # don't bother
1282+ continue
1283+ file_id = basis_inv.path2id(path)
1284+ # Is this directory really empty ?
1285+ if set(basis_inv[file_id].children.keys()) == removed_children:
1286+ yield (path, None, file_id, None)
1287+ dirname = os.path.dirname(path)
1288+ if maybe_empty_dirs[dirname] is not None:
1289+ maybe_empty_dirs[dirname].add(basis_inv[file_id].name)
1290
1291
1292 def create_directory_texts(texts, invdelta):
1293@@ -231,7 +242,8 @@
1294 def generate_stream():
1295 for (old_path, new_path, fileid, ie) in invdelta:
1296 if old_path is None and ie.kind == "directory":
1297- record = FulltextContentFactory((fileid, ie.revision), (), None, "")
1298+ record = FulltextContentFactory((fileid, ie.revision), (),
1299+ None, "")
1300 record.parents = ()
1301 yield record
1302 texts.insert_record_stream(generate_stream())
1303@@ -264,7 +276,10 @@
1304 if expected_files != files:
1305 raise AssertionError
1306 lookup = [m.__getitem__ for m, f in manifest_parents[:2]]
1307- (manifest, flags) = manifest_and_flags_from_tree(parent_trees, tree,
1308+ for i in range(2):
1309+ if len(lookup) <= i:
1310+ lookup.append({}.__getitem__)
1311+ (manifest, flags, unusual_fileids) = manifest_and_flags_from_tree(parent_trees, tree,
1312 mapping, lookup)
1313 if set(manifest.keys()) != set(expected_manifest.keys()):
1314 raise AssertionError("Different contents in manifests: %r, %r" %
1315@@ -313,6 +328,18 @@
1316 ret.append(self._inventories[revid])
1317 return ret
1318
1319+ def _lookup_file_target(self, key):
1320+ return str(self._symlink_targets[key]).decode("utf-8")
1321+
1322+ def _lookup_file_metadata(self, key):
1323+ try:
1324+ return self._text_metadata[key]
1325+ except KeyError:
1326+ (fileid, revision) = key
1327+ stream = self.target.texts.get_record_stream([key], 'unordered', True)
1328+ record = stream.next()
1329+ return (record.sha1, len(record.get_bytes_as("fulltext")))
1330+
1331 def _import_manifest_delta(self, manifest, flags, files, rev,
1332 mapping):
1333 parent_invs = self._get_inventories(rev.parent_ids)
1334@@ -333,8 +360,8 @@
1335 invdelta = list(manifest_to_inventory_delta(mapping.generate_file_id,
1336 basis_inv, other_inv, (basis_manifest, basis_flags),
1337 (manifest, flags), rev.revision_id, files,
1338- self._text_metadata.__getitem__,
1339- self._symlink_targets.__getitem__))
1340+ self._lookup_file_metadata,
1341+ self._lookup_file_target))
1342 return basis_inv, invdelta
1343
1344 def _get_target_fulltext(self, key):
1345@@ -342,31 +369,37 @@
1346 return self._symlink_targets[key]
1347 return self._target_overlay.get_file_fulltext(key)
1348
1349- def _unpack_texts(self, cg, mapping, filetext_map, pb):
1350+ def _create_text_record(self, fileid, revision, parents, kind, fulltext):
1351+ key = (fileid, revision)
1352+ if kind == "symlink":
1353+ self._symlink_targets[key] = fulltext
1354+ bzr_fulltext = ""
1355+ else:
1356+ (meta, bzr_fulltext) = deserialize_file_text(str(fulltext))
1357+ record = FulltextContentFactory(key, [(fileid, p) for p in parents],
1358+ osutils.sha_string(bzr_fulltext), bzr_fulltext)
1359+ self._text_metadata[key] = (record.sha1, len(bzr_fulltext))
1360+ return record
1361+
1362+ def _unpack_texts(self, cg, mapping, kind_map, pb):
1363 i = 0
1364 # Texts
1365- while 1:
1366+ while True:
1367 f = mercurial.changegroup.getchunk(cg)
1368 if not f:
1369 break
1370 i += 1
1371- pb.update("fetching texts", i, len(filetext_map))
1372+ pb.update("fetching texts", i, len(kind_map))
1373 fileid = mapping.generate_file_id(f)
1374- chunkiter = mercurial.changegroup.chunkiter(cg)
1375+ itertextchunks = chunkiter(cg)
1376 def get_text(node):
1377- key = iter(filetext_map[fileid][node]).next()
1378+ key = iter(kind_map[fileid][node]).next()
1379 return self._get_target_fulltext(key)
1380- for fulltext, hgkey, hgparents, csid in unpack_chunk_iter(chunkiter, get_text):
1381- for revision, (kind, parents) in filetext_map[fileid][hgkey].iteritems():
1382- key = (fileid, revision)
1383- if kind == "symlink":
1384- self._symlink_targets[key] = fulltext
1385- bzr_fulltext = ""
1386- else:
1387- bzr_fulltext = fulltext
1388- record = FulltextContentFactory(key, [(fileid, p) for p in parents], osutils.sha_string(bzr_fulltext), bzr_fulltext)
1389- self._text_metadata[key] = (record.sha1, len(bzr_fulltext))
1390- yield record
1391+ for fulltext, hgkey, hgparents, csid in unpack_chunk_iter(itertextchunks, get_text):
1392+ for revision, kind in kind_map[fileid][hgkey].iteritems():
1393+ text_parents = ()
1394+ yield self._create_text_record(fileid, revision,
1395+ text_parents, kind, fulltext)
1396
1397 def _add_inventories(self, todo, mapping, pb):
1398 assert isinstance(todo, list)
1399@@ -444,7 +477,66 @@
1400 return r
1401 raise AssertionError
1402
1403- def _unpack_manifests(self, chunkiter, mapping, filetext_map, todo, pb):
1404+ def _determine_text_parents(self, parents, path, fileid, revid, kind_map):
1405+ ret = []
1406+ for parent in parents:
1407+ tp = self._determine_text_parent(parent, path, fileid, revid, kind_map)
1408+ if tp is not None:
1409+ ret.append(tp)
1410+ return ret
1411+
1412+ def _determine_text_parent(self, parent, path, fileid, revid, kind_map):
1413+ path2id = getattr(parent, "path2id", None)
1414+ if path2id is None: # manifest
1415+ parent_node = parent.get(path)
1416+ if parent_node is None:
1417+ # Didn't exist in parent
1418+ return None
1419+ revisions = kind_map[fileid][parent_node].keys()
1420+ tp = self._find_most_recent_ancestor(revisions, revid)
1421+ return tp
1422+ elif path2id(path) == fileid:
1423+ # FIXME: Handle situation where path is not actually in
1424+ # parent
1425+ return parent[fileid].revision
1426+ else:
1427+ return None
1428+
1429+ def _process_manifest(self, manifest, flags, revid, mapping, kind_map):
1430+ """Process a manifest.
1431+
1432+ :param manifest: Mercurial manifest (dict of path -> node)
1433+ :param flags: Mercurial manifest flags (dict of path -> mode)
1434+ :param revid: Bazaar revision id
1435+ :param mapping: Bzr<->Hg mapping to use
1436+ :param kind_map: Mapping of (fileid, node, revid) -> kind
1437+ """
1438+ self._target_overlay.remember_manifest(revid,
1439+ self._revisions[revid].parent_ids, (manifest, flags))
1440+ if not self._files[revid]:
1441+ # Avoid fetching inventories and parent manifests
1442+ # unnecessarily
1443+ return
1444+ rev = self._revisions[revid]
1445+ parents = []
1446+ for previd in rev.parent_ids:
1447+ try:
1448+ inv = self.target.get_inventory(previd)
1449+ except errors.NoSuchRevision:
1450+ parents.append(self._target_overlay.get_manifest_and_flags_by_revid(previd)[0])
1451+ else:
1452+ parents.append(inv)
1453+ for path in self._files[revid]:
1454+ assert type(path) is str
1455+ fileid = mapping.generate_file_id(path)
1456+ if not path in manifest:
1457+ # Path no longer exists
1458+ continue
1459+ kind = flags_kind(flags, path)
1460+ node = manifest[path]
1461+ kind_map[fileid][node][revid] = kind
1462+
1463+ def _unpack_manifests(self, chunkiter, mapping, kind_map, todo, pb):
1464 """Unpack the manifest deltas.
1465
1466 :param chunkiter: Iterator over delta chunks for the manifest.
1467@@ -458,42 +550,8 @@
1468 for revid in self._manifest2rev_map[hgkey]:
1469 todo.append(revid)
1470 yield (revid, self._revisions[revid].parent_ids, fulltext)
1471- self._target_overlay.remember_manifest(revid,
1472- self._revisions[revid].parent_ids, (manifest, flags))
1473- if not self._files[revid]:
1474- # Avoid fetching inventories and parent manifests
1475- # unnecessarily
1476- continue
1477- rev = self._revisions[revid]
1478- parents = []
1479- for previd in rev.parent_ids:
1480- try:
1481- inv = self.target.get_inventory(previd)
1482- except errors.NoSuchRevision:
1483- parents.append(self._target_overlay.get_manifest_and_flags_by_revid(previd)[0])
1484- else:
1485- parents.append(inv)
1486- for path in self._files[revid]:
1487- assert type(path) is str
1488- fileid = mapping.generate_file_id(path)
1489- if not path in manifest:
1490- # Path still has to actually exist..
1491- continue
1492- kind = flags_kind(flags, path)
1493- text_parents = []
1494- for parent in parents:
1495- path2id = getattr(parent, "path2id", None)
1496- if path2id is None: # manifest
1497- node = parent.get(path)
1498- if node is None:
1499- continue
1500- revisions = filetext_map[fileid][node]
1501- tp = self._find_most_recent_ancestor(revisions.keys(), revid)
1502- text_parents.append(tp)
1503- elif path2id(path) == fileid:
1504- # FIXME: Handle situation where path is not actually in parent
1505- text_parents.append(parent[fileid].revision)
1506- filetext_map[fileid][manifest[path]][revid] = (kind, text_parents)
1507+ self._process_manifest(manifest, flags, revid, mapping,
1508+ kind_map)
1509
1510 def addchangegroup(self, cg, mapping):
1511 """Import a Mercurial changegroup into the target repository.
1512@@ -502,26 +560,27 @@
1513 :param mapping: Mercurial mapping
1514 """
1515 # Changesets
1516- chunkiter = mercurial.changegroup.chunkiter(cg)
1517+ changesetchunks = chunkiter(cg)
1518 pb = ui.ui_factory.nested_progress_bar()
1519 try:
1520- self._unpack_changesets(chunkiter, mapping, pb)
1521+ self._unpack_changesets(changesetchunks, mapping, pb)
1522 finally:
1523 pb.finished()
1524 # Manifests
1525- manifestchunks = mercurial.changegroup.chunkiter(cg)
1526- filetext_map = defaultdict(lambda: defaultdict(dict))
1527+ manifestchunks = chunkiter(cg)
1528+ kind_map = defaultdict(lambda: defaultdict(dict))
1529 todo = []
1530 pb = ui.ui_factory.nested_progress_bar()
1531 try:
1532- self._target_overlay.remember_manifest_texts(self._unpack_manifests(manifestchunks, mapping, filetext_map, todo, pb))
1533+ self._target_overlay.remember_manifest_texts(
1534+ self._unpack_manifests(manifestchunks, mapping, kind_map, todo, pb))
1535 finally:
1536 pb.finished()
1537 # Texts
1538 pb = ui.ui_factory.nested_progress_bar()
1539 try:
1540 self.target.texts.insert_record_stream(
1541- self._unpack_texts(cg, mapping, filetext_map, pb))
1542+ self._unpack_texts(cg, mapping, kind_map, pb))
1543 finally:
1544 pb.finished()
1545 # Adding actual data
1546
1547=== modified file 'info.py'
1548--- info.py 2010-08-05 23:55:52 +0000
1549+++ info.py 2011-03-23 21:41:04 +0000
1550@@ -1,7 +1,6 @@
1551 bzr_plugin_name = 'hg'
1552
1553-bzr_compatible_versions = [(1, x, 0) for x in [13, 14, 15, 16, 17, 18]] + \
1554- [(2, x, 0) for x in [0, 1, 2]]
1555+bzr_compatible_versions = [(2, x, 0) for x in [3, 4]]
1556
1557 bzr_minimum_version = bzr_compatible_versions[0]
1558
1559@@ -11,4 +10,6 @@
1560
1561 bzr_control_formats = {"Mercurial": {'.hg/': None}}
1562
1563-hg_compatible_versions = ["1.6"]
1564+hg_compatible_versions = [(1, 6), (1, 7), (1, 8)]
1565+
1566+hg_compatible_version_strings = ["%d.%d" % x for x in hg_compatible_versions]
1567
1568=== modified file 'mapping.py'
1569--- mapping.py 2010-02-08 13:32:23 +0000
1570+++ mapping.py 2011-03-23 21:41:04 +0000
1571@@ -42,12 +42,15 @@
1572 """Convert a Mercurial 'convert_revision' extra to a Bazaar 'converted-from' revprop.
1573
1574 """
1575- (kind, revid) = rev.split(":", 1)
1576+ try:
1577+ (kind, revid) = rev.split(":", 1)
1578+ except ValueError:
1579+ return "unspecified %s\n" % rev
1580 if kind == "svn":
1581 url, revnum = revid.rsplit('@', 1)
1582 revnum = int(revnum)
1583 parts = url.split('/', 1)
1584- uuid = parts.pop(0)[4:]
1585+ uuid = parts.pop(0)
1586 mod = ''
1587 if parts:
1588 mod = parts[0]
1589@@ -65,7 +68,9 @@
1590 (uuid, revnumstr, branchpathstr) = revid.split(":", 2)
1591 revnum = int(revnumstr)
1592 branchpath = urllib.unquote(branchpathstr)
1593- return "svn:%s%s@%s" % (uuid, branchpath, revnum)
1594+ return "svn:%s/%s@%s" % (uuid, branchpath, revnum)
1595+ elif kind == "unspecified":
1596+ return revid
1597 else:
1598 raise KeyError("Unknown VCS '%s'" % kind)
1599
1600@@ -175,6 +180,7 @@
1601 :param parent_node_lookup: 2-tuple with functions to look up the nodes
1602 of paths in the tree's parents
1603 """
1604+ assert len(parent_node_lookup) == 2
1605 unusual_fileids = {}
1606 def get_text_parents(path):
1607 assert type(path) == str
1608@@ -184,6 +190,7 @@
1609 ret.append(lookup(path))
1610 except KeyError:
1611 ret.append(mercurial.node.nullid)
1612+ assert len(ret) == 2
1613 return tuple(ret)
1614 manifest = {}
1615 flags = {}
1616@@ -263,7 +270,7 @@
1617 elif len(revision_id) == 40:
1618 hexhgrevid = revision_id
1619 else:
1620- raise AssertionError
1621+ raise AssertionError("Invalid hg id %r" % revision_id)
1622 return "%s:%s" % (cls.revid_prefix, hexhgrevid)
1623
1624 @classmethod
1625
1626=== modified file 'overlay.py'
1627--- overlay.py 2009-11-26 21:33:32 +0000
1628+++ overlay.py 2011-03-23 21:41:04 +0000
1629@@ -122,7 +122,7 @@
1630 """
1631 if self.manifests_vf is not None:
1632 self.manifests_vf.insert_record_stream(
1633- ((FulltextContentFactory((revid,), [(p,) for p in parent_revids] , None, text) for (revid, parent_revids, text) in entries)))
1634+ ((FulltextContentFactory((revid,), [(p,) for p in parent_revids], None, str(text)) for (revid, parent_revids, text) in entries)))
1635
1636 def _get_cached_manifest(self, revid):
1637 """Attempt to retrieve a cached manifest.
1638
1639=== modified file 'parsers.py'
1640--- parsers.py 2010-02-08 13:32:23 +0000
1641+++ parsers.py 2011-03-23 21:41:04 +0000
1642@@ -25,6 +25,7 @@
1643 """
1644
1645 import mercurial.changelog
1646+import mercurial.changegroup
1647 import mercurial.manifest
1648 import mercurial.mdiff
1649 import mercurial.node
1650@@ -81,6 +82,7 @@
1651 :param text: Text to parse
1652 :return: Tuple with (manifest, user, (time, timezone), files, desc, extra)
1653 """
1654+ text = str(text)
1655 last = text.index("\n\n")
1656 desc = text[last + 2:].decode("utf-8")
1657 l = text[:last].split('\n')
1658@@ -179,3 +181,34 @@
1659 node = mercurial.node.hex(node)
1660 lines.append("%s\0%s%s\n" % (path, node, flags.get(path, "")))
1661 return "".join(lines)
1662+
1663+
1664+def serialize_file_text(meta, text):
1665+ if meta or text.startswith('\1\n'):
1666+ mt = ["%s: %s\n" % (k, v) for k, v in sorted(meta.iteritems())]
1667+ text = "\1\n%s\1\n%s" % ("".join(mt), text)
1668+ return text
1669+
1670+
1671+def deserialize_file_text(text):
1672+ if not text.startswith("\1\n"):
1673+ return ({}, text)
1674+ s = text.index('\1\n', 2)
1675+ mt = text[2:s]
1676+ meta = {}
1677+ for l in mt.splitlines():
1678+ k, v = l.split(": ", 1)
1679+ meta[k] = v
1680+ return meta, text[s+2:]
1681+
1682+
1683+def chunkiter(source, progress=None):
1684+ """iterate through the chunks in source, yielding a sequence of chunks
1685+ (strings)"""
1686+ while 1:
1687+ c = mercurial.changegroup.getchunk(source)
1688+ if not c:
1689+ break
1690+ elif progress is not None:
1691+ progress()
1692+ yield c
1693
1694=== modified file 'repository.py'
1695--- repository.py 2010-07-31 18:38:03 +0000
1696+++ repository.py 2011-03-23 21:41:04 +0000
1697@@ -62,8 +62,23 @@
1698 support the repository format.
1699 """
1700 rich_root_data = True
1701-
1702- def initialize(self, url, shared=False, _internal=False):
1703+ fast_deltas = True
1704+ supports_leaving_lock = False
1705+ supports_funky_characters = True
1706+ supports_external_lookups = False
1707+ supports_full_versioned_files = False
1708+
1709+ @property
1710+ def _matchingbzrdir(self):
1711+ from bzrlib.plugins.hg.dir import HgControlDirFormat
1712+ return HgControlDirFormat()
1713+
1714+ def initialize(self, controldir, shared=False, _internal=False):
1715+ from bzrlib.plugins.hg.dir import HgDir
1716+ if shared:
1717+ raise errors.IncompatibleFormat(self, controldir._format)
1718+ if isinstance(controldir, HgDir):
1719+ return controldir.open_repository()
1720 raise errors.UninitializableFormat(self)
1721
1722 def is_supported(self):
1723@@ -126,6 +141,7 @@
1724 return ancestry_cache[some_revision_id]
1725 except KeyError:
1726 pass
1727+ assert some_revision_id in all_relevant_revisions
1728 ancestry = set()
1729 # add what can be reached from some_revision_id
1730 # TODO: must factor this trivial iteration in bzrlib.graph cleanly.
1731@@ -287,6 +303,8 @@
1732
1733 _serializer = None
1734
1735+ chk_bytes = None
1736+
1737 def __init__(self, hgrepo, hgdir, lockfiles):
1738 ForeignRepository.__init__(self, HgRepositoryFormat(), hgdir, lockfiles)
1739 self._hgrepo = hgrepo
1740@@ -303,6 +321,9 @@
1741 self.inventories = None
1742 self.texts = None
1743
1744+ def revision_graph_can_have_wrong_parents(self):
1745+ return False
1746+
1747 def _warn_if_deprecated(self):
1748 # This class isn't deprecated
1749 pass
1750@@ -341,20 +362,33 @@
1751
1752 def lookup_bzr_revision_id(self, revision_id):
1753 """See ForeignRepository.lookup_bzr_revision_id()."""
1754- assert type(revision_id) is str
1755+ assert type(revision_id) is str, "invalid revid: %r" % revision_id
1756 # TODO: Handle round-tripped revisions
1757 try:
1758 return mapping_registry.revision_id_bzr_to_foreign(revision_id)
1759 except errors.InvalidRevisionId:
1760 raise errors.NoSuchRevision(self, revision_id)
1761
1762+ def get_parent_map(self, revids):
1763+ ret = {}
1764+ for revid in revids:
1765+ hgrevid, mapping = self.lookup_bzr_revision_id(revid)
1766+ # FIXME: what about extra parents?
1767+ hgparents = self._hgrepo.changelog.parents(hgrevid)
1768+ ret[revid] = as_bzr_parents(hgparents, self.lookup_foreign_revision_id)
1769+ return ret
1770+
1771 def get_revision(self, revision_id):
1772- assert type(revision_id) is str
1773+ if not type(revision_id) is str:
1774+ raise errors.InvalidRevisionId(revision_id, self)
1775 hgrevid, mapping = self.lookup_bzr_revision_id(revision_id)
1776+ assert mapping is not None
1777 hgchange = self._hgrepo.changelog.read(hgrevid)
1778 hgparents = self._hgrepo.changelog.parents(hgrevid)
1779 parent_ids = as_bzr_parents(hgparents, self.lookup_foreign_revision_id)
1780- return mapping.import_revision(revision_id, parent_ids, hgrevid, hgchange[0], hgchange[1].decode("utf-8"), hgchange[2], hgchange[4].decode("utf-8"), hgchange[5])[0]
1781+ return mapping.import_revision(revision_id, parent_ids, hgrevid,
1782+ hgchange[0], hgchange[1].decode("utf-8"), hgchange[2],
1783+ hgchange[4].decode("utf-8"), hgchange[5])[0]
1784
1785 def iter_inventories(self, revision_ids, ordering=None):
1786 for revid in revision_ids:
1787@@ -365,6 +399,7 @@
1788 log = self._hgrepo.changelog.read(hgid)
1789 manifest = self._hgrepo.manifest.read(log[0])
1790 all_relevant_revisions = self.get_ancestry(revision_id)[1:] + [NULL_REVISION]
1791+
1792 pb = ui.ui_factory.nested_progress_bar()
1793 try:
1794 inv = manifest_to_inventory(self._hgrepo, hgid, log, manifest,
1795@@ -379,11 +414,18 @@
1796 return foreign_revid in self._hgrepo.changelog.nodemap
1797
1798 def has_revision(self, revision_id):
1799+ if revision_id == NULL_REVISION:
1800+ return True
1801 try:
1802 return self.has_foreign_revision(self.lookup_bzr_revision_id(revision_id)[0])
1803 except errors.NoSuchRevision:
1804 return False
1805
1806+ def get_commit_builder(self, branch, parents, config, timestamp=None,
1807+ timezone=None, committer=None, revprops=None,
1808+ revision_id=None):
1809+ raise NotImplementedError(self.get_commit_builder)
1810+
1811
1812 class HgRemoteRepository(HgRepository):
1813
1814
1815=== modified file 'setup.py'
1816--- setup.py 2010-03-25 21:08:21 +0000
1817+++ setup.py 2011-03-23 21:41:04 +0000
1818@@ -1,4 +1,4 @@
1819-#!/usr/bin/env python2.4
1820+#!/usr/bin/env python
1821
1822 from info import *
1823
1824
1825=== modified file 'tests/__init__.py'
1826--- tests/__init__.py 2010-07-31 12:16:16 +0000
1827+++ tests/__init__.py 2011-03-23 21:41:04 +0000
1828@@ -30,6 +30,7 @@
1829 'test_mapping',
1830 'test_parsers',
1831 'test_pull',
1832+ 'test_fetch',
1833 ]
1834
1835 suite.addTest(loader.loadTestsFromModuleNames(["%s.%s" % (__name__, i) for i in testmod_names]))
1836
1837=== modified file 'tests/test_branch.py'
1838--- tests/test_branch.py 2009-10-12 09:41:47 +0000
1839+++ tests/test_branch.py 2011-03-23 21:41:04 +0000
1840@@ -18,8 +18,8 @@
1841 TestCase,
1842 )
1843
1844-from bzrlib.plugins.hg import (
1845- HgBzrDirFormat,
1846+from bzrlib.plugins.hg.dir import (
1847+ HgControlDirFormat,
1848 )
1849 from bzrlib.plugins.hg.branch import (
1850 HgBranchFormat,
1851@@ -28,13 +28,13 @@
1852 class BranchFormatTests(TestCase):
1853
1854 def test_description(self):
1855- self.assertEquals("Mercurial Branch",
1856+ self.assertEquals("Mercurial Branch",
1857 HgBranchFormat().get_format_description())
1858
1859
1860 class ForeignTestsBranchFactory(object):
1861
1862 def make_empty_branch(self, transport):
1863- return HgBzrDirFormat().initialize_on_transport(transport).open_branch()
1864+ return HgControlDirFormat().initialize_on_transport(transport).open_branch()
1865
1866 make_branch = make_empty_branch
1867
1868=== modified file 'tests/test_dir.py'
1869--- tests/test_dir.py 2010-07-31 12:25:11 +0000
1870+++ tests/test_dir.py 2011-03-23 21:41:04 +0000
1871@@ -22,21 +22,28 @@
1872 TestCase,
1873 )
1874
1875-from bzrlib.plugins.hg import (
1876- HgBzrDirFormat,
1877+from bzrlib.plugins.hg.dir import (
1878+ HgControlDirFormat,
1879 )
1880
1881
1882-class HgBzrDirFormatTests(TestCase):
1883+class HgControlDirFormatTests(TestCase):
1884
1885 def test_eq(self):
1886- format1 = HgBzrDirFormat()
1887- format2 = HgBzrDirFormat()
1888+ format1 = HgControlDirFormat()
1889+ format2 = HgControlDirFormat()
1890 self.assertEquals(format1, format2)
1891 self.assertEquals(format1, format1)
1892 bzr_format = format_registry.make_bzrdir("default")
1893 self.assertNotEquals(bzr_format, format1)
1894
1895+ def test_hash(self):
1896+ format1 = HgControlDirFormat()
1897+ format2 = HgControlDirFormat()
1898+ self.assertEquals(hash(format1), hash(format2))
1899+ bzr_format = format_registry.make_bzrdir("default")
1900+ self.assertNotEquals(hash(bzr_format), hash(format1))
1901+
1902 def test_network_name(self):
1903- format = HgBzrDirFormat()
1904+ format = HgControlDirFormat()
1905 self.assertEquals("hg", format.network_name())
1906
1907=== added file 'tests/test_fetch.py'
1908--- tests/test_fetch.py 1970-01-01 00:00:00 +0000
1909+++ tests/test_fetch.py 2011-03-23 21:41:04 +0000
1910@@ -0,0 +1,53 @@
1911+# Copyright (C) 2010 Leonid Borisenko <leo.borisenko@gmail.com>
1912+
1913+# This program is free software; you can redistribute it and/or modify
1914+# it under the terms of the GNU General Public License as published by
1915+# the Free Software Foundation; either version 2 of the License, or
1916+# (at your option) any later version.
1917+
1918+# This program is distributed in the hope that it will be useful,
1919+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1920+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1921+# GNU General Public License for more details.
1922+
1923+# You should have received a copy of the GNU General Public License
1924+# along with this program; if not, write to the Free Software
1925+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1926+
1927+"""Tests for fetching from Mercurial into Bazaar."""
1928+
1929+from bzrlib.plugins.hg.dir import HgControlDirFormat
1930+from bzrlib.plugins.hg.ui import ui as hgui
1931+from bzrlib.tests import TestCaseWithTransport
1932+
1933+import mercurial.localrepo
1934+
1935+class TestFetching(TestCaseWithTransport):
1936+
1937+ def test_recursive_removing_of_empty_directories(self):
1938+ # Create files in directory of Mercurial repository.
1939+ self.build_tree([
1940+ "hg/",
1941+ "hg/f1",
1942+ "hg/d1/",
1943+ "hg/d1/d2/",
1944+ "hg/d1/d2/f2",
1945+ ])
1946+
1947+ # Create Mercurial repository itself and fill it's history.
1948+ hgrepo = mercurial.localrepo.localrepository(hgui(), "hg", create=True)
1949+ hgrepo[None].add(["f1", "d1/d2/f2"])
1950+ hgrepo.commit("Initial commit")
1951+ hgrepo[None].remove(["d1/d2/f2"], unlink=True)
1952+ hgrepo.commit("Remove file f2, so parent directories d2, d1 are empty")
1953+
1954+ # Import history from Mercurial repository into Bazaar repository.
1955+ bzrtree = self.make_branch_and_tree('bzr')
1956+ hgdir = HgControlDirFormat().open(self.get_transport('hg'))
1957+ bzrtree.pull(hgdir.open_branch())
1958+
1959+ # As file f2 was deleted, directories d1 and d2 should not exists.
1960+ self.failIfExists('bzr/d1')
1961+
1962+ # Self-assurance check that history was really imported.
1963+ self.failUnlessExists('bzr/f1')
1964
1965=== modified file 'tests/test_mapping.py'
1966--- tests/test_mapping.py 2010-02-08 13:32:23 +0000
1967+++ tests/test_mapping.py 2011-03-23 21:41:04 +0000
1968@@ -25,6 +25,8 @@
1969 from bzrlib.plugins.hg.mapping import (
1970 ExperimentalHgMapping,
1971 HgMappingv1,
1972+ convert_converted_from,
1973+ generate_convert_revision,
1974 as_bzr_parents,
1975 as_hg_parents,
1976 escape_path,
1977@@ -41,7 +43,7 @@
1978 from bzrlib.tests import (
1979 TestCase,
1980 )
1981-
1982+
1983 class HgMappingTests(object):
1984
1985 def test_revid_foreign_to_bzr(self):
1986@@ -168,3 +170,33 @@
1987 m = {"a" * 20: "reva"}
1988 self.assertEquals(("reva",),
1989 as_bzr_parents(("a" * 20, nullid), m.__getitem__))
1990+
1991+
1992+class ConvertConvertedFromTests(TestCase):
1993+
1994+ def test_unspecified(self):
1995+ self.assertEquals("unspecified foo\n",
1996+ convert_converted_from("foo"))
1997+
1998+ def test_svn(self):
1999+ self.assertEquals("svn myuuid:42:path\n",
2000+ convert_converted_from("svn:myuuid/path@42"))
2001+
2002+ def test_unknown(self):
2003+ self.assertRaises(KeyError,
2004+ convert_converted_from, "unknown:myuuid:42:path")
2005+
2006+
2007+class GenerateConvertRevisionTests(TestCase):
2008+
2009+ def test_generate_convert_revision_svn(self):
2010+ self.assertEquals("svn:myuuid/path@42",
2011+ generate_convert_revision("svn myuuid:42:path"))
2012+
2013+ def test_generate_convert_revision_unspecified(self):
2014+ self.assertEquals("bla",
2015+ generate_convert_revision("unspecified bla"))
2016+
2017+ def test_generate_convert_revision_unknown(self):
2018+ self.assertRaises(KeyError,
2019+ generate_convert_revision, "bla bla")
2020
2021=== modified file 'tests/test_parsers.py'
2022--- tests/test_parsers.py 2010-02-08 13:32:23 +0000
2023+++ tests/test_parsers.py 2011-03-23 21:41:04 +0000
2024@@ -17,13 +17,15 @@
2025 import mercurial
2026
2027 from bzrlib.plugins.hg.parsers import (
2028+ deserialize_file_text,
2029 format_changeset,
2030 parse_changeset,
2031+ serialize_file_text,
2032 )
2033 from bzrlib.tests import (
2034 TestCase,
2035 )
2036-
2037+
2038 class ChangesetFormatterTests(TestCase):
2039
2040 def test_simple(self):
2041@@ -116,3 +118,12 @@
2042 Some
2043 commit
2044 message"""))
2045+
2046+
2047+class TextSerializers(TestCase):
2048+
2049+ def test_serialize(self):
2050+ self.assertEquals("\1\ncopy: bla\n\1\nfoo\n", serialize_file_text({"copy": "bla"}, "foo\n"))
2051+
2052+ def test_deserialize(self):
2053+ self.assertEquals(({"copy": "bla"}, "foo\n"), deserialize_file_text("\1\ncopy: bla\n\1\nfoo\n"))
2054
2055=== modified file 'tests/test_pull.py'
2056--- tests/test_pull.py 2009-10-18 21:57:36 +0000
2057+++ tests/test_pull.py 2011-03-23 21:41:04 +0000
2058@@ -15,7 +15,7 @@
2059 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2060
2061 from bzrlib.inventory import Inventory
2062-from bzrlib.plugins.hg import HgBzrDirFormat
2063+from bzrlib.plugins.hg.dir import HgControlDirFormat
2064 from bzrlib.tests import TestCaseWithTransport
2065
2066 import base64
2067@@ -30,7 +30,7 @@
2068 def setUp(self):
2069 super(TestPulling, self).setUp()
2070 self.build_tree(['hg/', 'hg/a', 'hg/b', 'hg/dir/', 'hg/dir/c'])
2071- hgdir = HgBzrDirFormat().initialize('hg')
2072+ hgdir = HgControlDirFormat().initialize('hg')
2073 self.tree = hgdir.open_workingtree()
2074 mode = os.lstat('hg/b').st_mode
2075 os.chmod('hg/b', mode | stat.S_IEXEC)
2076@@ -53,7 +53,7 @@
2077 file_id='hg:dir')
2078 entry.revision = tip
2079 entry = revone_inventory.add_path('dir/c', kind='file',
2080- file_id='hg:dir:c')
2081+ file_id='hg:dir_sc')
2082 entry.revision = tip
2083 entry.text_size = len('contents of hg/dir/c\n')
2084 entry.text_sha1 = "958be752affac0fee70471331b96fb3fc1809425"
2085@@ -68,7 +68,7 @@
2086 self.revtwo_inventory = copy.deepcopy(revone_inventory)
2087 tip = self.tree.last_revision()
2088 entry = self.revtwo_inventory.add_path('dir/d', kind='file',
2089- file_id='hg:dir:d')
2090+ file_id='hg:dir_sd')
2091 entry.revision = tip
2092 entry.text_size = len('contents of hg/dir/d\n')
2093 entry.text_sha1 = "f48fc342f707bfb4711790e1813c0df4d44e1a23"
2094@@ -91,7 +91,7 @@
2095 self.tree.commit('change dir/c')
2096 self.revfour_inventory = copy.deepcopy(self.revthree_inventory)
2097 tip = self.tree.last_revision()
2098- entry = self.revfour_inventory['hg:dir:c']
2099+ entry = self.revfour_inventory['hg:dir_sc']
2100 entry.revision = tip
2101 entry.text_size = len('new contents')
2102 entry.text_sha1 = "7ffa72b76d5d66da37f4b614b7a822c01f23c183"
2103
2104=== modified file 'tests/test_repository.py'
2105--- tests/test_repository.py 2009-11-12 22:57:40 +0000
2106+++ tests/test_repository.py 2011-03-23 21:41:04 +0000
2107@@ -15,11 +15,11 @@
2108 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2109
2110
2111-from bzrlib.plugins.hg import (
2112- HgBzrDirFormat,
2113+from bzrlib.plugins.hg.dir import (
2114+ HgControlDirFormat,
2115 )
2116
2117 class ForeignTestsRepositoryFactory(object):
2118
2119 def make_repository(self, transport):
2120- return HgBzrDirFormat().initialize_on_transport(transport).open_repository()
2121+ return HgControlDirFormat().initialize_on_transport(transport).open_repository()
2122
2123=== modified file 'ui.py'
2124--- ui.py 2009-09-29 14:41:23 +0000
2125+++ ui.py 2011-03-23 21:41:04 +0000
2126@@ -27,19 +27,19 @@
2127
2128 def debug(self, *msg):
2129 for x in msg:
2130- trace.mutter("hg: %s" % x)
2131+ trace.mutter("hg: %s" % x.rstrip())
2132
2133 def warn(self, *msg):
2134 for x in msg:
2135- trace.warning("hg: %s" % x)
2136+ trace.warning("hg: %s" % x.rstrip())
2137
2138 def note(self, *msg):
2139 for x in msg:
2140- trace.mutter("hg: %s" % x)
2141+ trace.mutter("hg: %s" % x.rstrip())
2142
2143 def status(self, *msg):
2144 for x in msg:
2145- trace.mutter("hg: %s" % x)
2146+ trace.mutter("hg: %s" % x.rstrip())
2147
2148 def username(self):
2149 return config.GlobalConfig().username()
2150
2151=== modified file 'util.py'
2152--- util.py 2009-10-15 17:27:10 +0000
2153+++ util.py 2011-03-23 21:41:04 +0000
2154@@ -1,4 +1,4 @@
2155-# Copyright (C) 2009 Jelmer Vernooij <jelmer@samba.org>
2156+# Copyright (C) 2009-2010 Jelmer Vernooij <jelmer@samba.org>
2157 #
2158 # This program is free software; you can redistribute it and/or modify
2159 # it under the terms of the GNU General Public License as published by
2160
2161=== modified file 'versionedfiles.py'
2162--- versionedfiles.py 2009-12-05 01:58:37 +0000
2163+++ versionedfiles.py 2011-03-23 21:41:04 +0000
2164@@ -75,6 +75,8 @@
2165 ret[(revid, )] = tuple((x,) for x in as_bzr_parents(self._revlog.parents(hg_ref), self._reverse_lookup_id))
2166 except LookupError:
2167 ret[(revid, )] = None
2168+ if ret[(revid,)] == ():
2169+ ret[(revid,)] = (NULL_REVISION,)
2170 return ret
2171
2172 def keys(self):
2173
2174=== modified file 'workingtree.py'
2175--- workingtree.py 2010-08-05 23:55:52 +0000
2176+++ workingtree.py 2011-03-23 21:41:04 +0000
2177@@ -16,6 +16,11 @@
2178
2179 """Mercurial working tree support."""
2180
2181+from bzrlib import osutils
2182+
2183+from bzrlib.errors import (
2184+ IncompatibleFormat,
2185+ )
2186 from bzrlib.inventory import (
2187 Inventory,
2188 )
2189@@ -38,10 +43,21 @@
2190 support the working tree format.
2191 """
2192
2193+ @property
2194+ def _matchingbzrdir(self):
2195+ from bzrlib.plugins.hg.dir import HgControlDirFormat
2196+ return HgControlDirFormat()
2197+
2198 def get_format_description(self):
2199 """See WorkingTreeFormat.get_format_description()."""
2200 return "Mercurial Working Tree"
2201
2202+ def initialize(self, to_bzrdir):
2203+ from bzrlib.plugins.hg.dir import HgDir
2204+ if not isinstance(to_bzrdir, HgDir):
2205+ raise IncompatibleFormat(self, to_bzrdir._format)
2206+ return to_bzrdir.create_workingtree()
2207+
2208
2209 class HgWorkingTree(bzrlib.workingtree.WorkingTree):
2210 """An adapter to mercurial repositories for bzr WorkingTree obejcts."""
2211@@ -50,30 +66,44 @@
2212 self._inventory = Inventory()
2213 self._hgrepo = hgrepo
2214 self.bzrdir = hgdir
2215+ self.repository = hgdir.open_repository()
2216 self._control_files = lockfiles
2217 self._branch = hgbranch
2218 self._format = HgWorkingTreeFormat()
2219 self._transport = hgdir.get_workingtree_transport(None)
2220 self.basedir = hgdir.root_transport.local_abspath(".")
2221+ self._detect_case_handling()
2222+ self._rules_searcher = None
2223 self.views = self._make_views()
2224
2225+ def flush(self):
2226+ pass
2227+
2228 @needs_write_lock
2229- def add(self, files, ids=None):
2230+ def add(self, files, ids=None, kinds=None):
2231 # hg does not use ids, toss them out
2232 if isinstance(files, basestring):
2233 files = [files]
2234+ if kinds is None:
2235+ kinds = [osutils.file_kind(self.abspath(f)) for f in files]
2236+ hg_files = []
2237+ for file, kind in zip(files, kinds):
2238+ if kind == "directory":
2239+ continue
2240+ hg_files.append(file.encode('utf-8'))
2241+
2242 # hg does not canonicalise paths : make them absolute
2243- paths = [(file).encode('utf8') for file in files]
2244- self._hgrepo[None].add(paths)
2245+ self._hgrepo[None].add(hg_files)
2246
2247 @needs_write_lock
2248- def commit(self, message, revprops=None, *args, **kwargs):
2249+ def commit(self, message=None, revprops=None, *args, **kwargs):
2250 # TODO: selected file lists -> match function
2251 if revprops is None:
2252 extra = {}
2253 else:
2254 extra = revprops
2255- self._hgrepo.commit(message, extra=extra)
2256+ hgid = self._hgrepo.commit(message.encode("utf-8"), extra=extra, force=True)
2257+ return self.bzrdir.open_repository().lookup_foreign_revision_id(hgid)
2258
2259 def _reset_data(self):
2260 """Reset all cached data."""

Subscribers

People subscribed via source and target branches