##// END OF EJS Templates
merge with stable
Augie Fackler -
r35211:15d38e8f merge default
parent child Browse files
Show More
@@ -1,298 +1,304 b''
1 # bzr.py - bzr support for the convert extension
1 # bzr.py - bzr support for the convert extension
2 #
2 #
3 # Copyright 2008, 2009 Marek Kubica <marek@xivilization.net> and others
3 # Copyright 2008, 2009 Marek Kubica <marek@xivilization.net> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # This module is for handling 'bzr', that was formerly known as Bazaar-NG;
8 # This module is for handling 'bzr', that was formerly known as Bazaar-NG;
9 # it cannot access 'bar' repositories, but they were never used very much
9 # it cannot access 'bar' repositories, but they were never used very much
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import os
12 import os
13
13
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15 from mercurial import (
15 from mercurial import (
16 demandimport,
16 demandimport,
17 error
17 error
18 )
18 )
19 from . import common
19 from . import common
20
20
21 # these do not work with demandimport, blacklist
21 # these do not work with demandimport, blacklist
22 demandimport.ignore.extend([
22 demandimport.ignore.extend([
23 'bzrlib.transactions',
23 'bzrlib.transactions',
24 'bzrlib.urlutils',
24 'bzrlib.urlutils',
25 'ElementPath',
25 'ElementPath',
26 ])
26 ])
27
27
28 try:
28 try:
29 # bazaar imports
29 # bazaar imports
30 import bzrlib.bzrdir
30 import bzrlib.bzrdir
31 import bzrlib.errors
31 import bzrlib.errors
32 import bzrlib.revision
32 import bzrlib.revision
33 import bzrlib.revisionspec
33 import bzrlib.revisionspec
34 bzrdir = bzrlib.bzrdir
34 bzrdir = bzrlib.bzrdir
35 errors = bzrlib.errors
35 errors = bzrlib.errors
36 revision = bzrlib.revision
36 revision = bzrlib.revision
37 revisionspec = bzrlib.revisionspec
37 revisionspec = bzrlib.revisionspec
38 revisionspec.RevisionSpec
38 revisionspec.RevisionSpec
39 except ImportError:
39 except ImportError:
40 pass
40 pass
41
41
42 supportedkinds = ('file', 'symlink')
42 supportedkinds = ('file', 'symlink')
43
43
44 class bzr_source(common.converter_source):
44 class bzr_source(common.converter_source):
45 """Reads Bazaar repositories by using the Bazaar Python libraries"""
45 """Reads Bazaar repositories by using the Bazaar Python libraries"""
46
46
47 def __init__(self, ui, repotype, path, revs=None):
47 def __init__(self, ui, repotype, path, revs=None):
48 super(bzr_source, self).__init__(ui, repotype, path, revs=revs)
48 super(bzr_source, self).__init__(ui, repotype, path, revs=revs)
49
49
50 if not os.path.exists(os.path.join(path, '.bzr')):
50 if not os.path.exists(os.path.join(path, '.bzr')):
51 raise common.NoRepo(_('%s does not look like a Bazaar repository')
51 raise common.NoRepo(_('%s does not look like a Bazaar repository')
52 % path)
52 % path)
53
53
54 try:
54 try:
55 # access bzrlib stuff
55 # access bzrlib stuff
56 bzrdir
56 bzrdir
57 except NameError:
57 except NameError:
58 raise common.NoRepo(_('Bazaar modules could not be loaded'))
58 raise common.NoRepo(_('Bazaar modules could not be loaded'))
59
59
60 path = os.path.abspath(path)
60 path = os.path.abspath(path)
61 self._checkrepotype(path)
61 self._checkrepotype(path)
62 try:
62 try:
63 self.sourcerepo = bzrdir.BzrDir.open(path).open_repository()
63 self.sourcerepo = bzrdir.BzrDir.open(path).open_repository()
64 except errors.NoRepositoryPresent:
64 except errors.NoRepositoryPresent:
65 raise common.NoRepo(_('%s does not look like a Bazaar repository')
65 raise common.NoRepo(_('%s does not look like a Bazaar repository')
66 % path)
66 % path)
67 self._parentids = {}
67 self._parentids = {}
68
68
69 def _checkrepotype(self, path):
69 def _checkrepotype(self, path):
70 # Lightweight checkouts detection is informational but probably
70 # Lightweight checkouts detection is informational but probably
71 # fragile at API level. It should not terminate the conversion.
71 # fragile at API level. It should not terminate the conversion.
72 try:
72 try:
73 dir = bzrdir.BzrDir.open_containing(path)[0]
73 dir = bzrdir.BzrDir.open_containing(path)[0]
74 try:
74 try:
75 tree = dir.open_workingtree(recommend_upgrade=False)
75 tree = dir.open_workingtree(recommend_upgrade=False)
76 branch = tree.branch
76 branch = tree.branch
77 except (errors.NoWorkingTree, errors.NotLocalUrl):
77 except (errors.NoWorkingTree, errors.NotLocalUrl):
78 tree = None
78 tree = None
79 branch = dir.open_branch()
79 branch = dir.open_branch()
80 if (tree is not None and tree.bzrdir.root_transport.base !=
80 if (tree is not None and tree.bzrdir.root_transport.base !=
81 branch.bzrdir.root_transport.base):
81 branch.bzrdir.root_transport.base):
82 self.ui.warn(_('warning: lightweight checkouts may cause '
82 self.ui.warn(_('warning: lightweight checkouts may cause '
83 'conversion failures, try with a regular '
83 'conversion failures, try with a regular '
84 'branch instead.\n'))
84 'branch instead.\n'))
85 except Exception:
85 except Exception:
86 self.ui.note(_('bzr source type could not be determined\n'))
86 self.ui.note(_('bzr source type could not be determined\n'))
87
87
88 def before(self):
88 def before(self):
89 """Before the conversion begins, acquire a read lock
89 """Before the conversion begins, acquire a read lock
90 for all the operations that might need it. Fortunately
90 for all the operations that might need it. Fortunately
91 read locks don't block other reads or writes to the
91 read locks don't block other reads or writes to the
92 repository, so this shouldn't have any impact on the usage of
92 repository, so this shouldn't have any impact on the usage of
93 the source repository.
93 the source repository.
94
94
95 The alternative would be locking on every operation that
95 The alternative would be locking on every operation that
96 needs locks (there are currently two: getting the file and
96 needs locks (there are currently two: getting the file and
97 getting the parent map) and releasing immediately after,
97 getting the parent map) and releasing immediately after,
98 but this approach can take even 40% longer."""
98 but this approach can take even 40% longer."""
99 self.sourcerepo.lock_read()
99 self.sourcerepo.lock_read()
100
100
101 def after(self):
101 def after(self):
102 self.sourcerepo.unlock()
102 self.sourcerepo.unlock()
103
103
104 def _bzrbranches(self):
104 def _bzrbranches(self):
105 return self.sourcerepo.find_branches(using=True)
105 return self.sourcerepo.find_branches(using=True)
106
106
107 def getheads(self):
107 def getheads(self):
108 if not self.revs:
108 if not self.revs:
109 # Set using=True to avoid nested repositories (see issue3254)
109 # Set using=True to avoid nested repositories (see issue3254)
110 heads = sorted([b.last_revision() for b in self._bzrbranches()])
110 heads = sorted([b.last_revision() for b in self._bzrbranches()])
111 else:
111 else:
112 revid = None
112 revid = None
113 for branch in self._bzrbranches():
113 for branch in self._bzrbranches():
114 try:
114 try:
115 r = revisionspec.RevisionSpec.from_string(self.revs[0])
115 r = revisionspec.RevisionSpec.from_string(self.revs[0])
116 info = r.in_history(branch)
116 info = r.in_history(branch)
117 except errors.BzrError:
117 except errors.BzrError:
118 pass
118 pass
119 revid = info.rev_id
119 revid = info.rev_id
120 if revid is None:
120 if revid is None:
121 raise error.Abort(_('%s is not a valid revision')
121 raise error.Abort(_('%s is not a valid revision')
122 % self.revs[0])
122 % self.revs[0])
123 heads = [revid]
123 heads = [revid]
124 # Empty repositories return 'null:', which cannot be retrieved
124 # Empty repositories return 'null:', which cannot be retrieved
125 heads = [h for h in heads if h != 'null:']
125 heads = [h for h in heads if h != 'null:']
126 return heads
126 return heads
127
127
128 def getfile(self, name, rev):
128 def getfile(self, name, rev):
129 revtree = self.sourcerepo.revision_tree(rev)
129 revtree = self.sourcerepo.revision_tree(rev)
130 fileid = revtree.path2id(name.decode(self.encoding or 'utf-8'))
130 fileid = revtree.path2id(name.decode(self.encoding or 'utf-8'))
131 kind = None
131 kind = None
132 if fileid is not None:
132 if fileid is not None:
133 kind = revtree.kind(fileid)
133 kind = revtree.kind(fileid)
134 if kind not in supportedkinds:
134 if kind not in supportedkinds:
135 # the file is not available anymore - was deleted
135 # the file is not available anymore - was deleted
136 return None, None
136 return None, None
137 mode = self._modecache[(name, rev)]
137 mode = self._modecache[(name, rev)]
138 if kind == 'symlink':
138 if kind == 'symlink':
139 target = revtree.get_symlink_target(fileid)
139 target = revtree.get_symlink_target(fileid)
140 if target is None:
140 if target is None:
141 raise error.Abort(_('%s.%s symlink has no target')
141 raise error.Abort(_('%s.%s symlink has no target')
142 % (name, rev))
142 % (name, rev))
143 return target, mode
143 return target, mode
144 else:
144 else:
145 sio = revtree.get_file(fileid)
145 sio = revtree.get_file(fileid)
146 return sio.read(), mode
146 return sio.read(), mode
147
147
148 def getchanges(self, version, full):
148 def getchanges(self, version, full):
149 if full:
149 if full:
150 raise error.Abort(_("convert from cvs does not support --full"))
150 raise error.Abort(_("convert from cvs does not support --full"))
151 self._modecache = {}
151 self._modecache = {}
152 self._revtree = self.sourcerepo.revision_tree(version)
152 self._revtree = self.sourcerepo.revision_tree(version)
153 # get the parentids from the cache
153 # get the parentids from the cache
154 parentids = self._parentids.pop(version)
154 parentids = self._parentids.pop(version)
155 # only diff against first parent id
155 # only diff against first parent id
156 prevtree = self.sourcerepo.revision_tree(parentids[0])
156 prevtree = self.sourcerepo.revision_tree(parentids[0])
157 files, changes = self._gettreechanges(self._revtree, prevtree)
157 files, changes = self._gettreechanges(self._revtree, prevtree)
158 return files, changes, set()
158 return files, changes, set()
159
159
160 def getcommit(self, version):
160 def getcommit(self, version):
161 rev = self.sourcerepo.get_revision(version)
161 rev = self.sourcerepo.get_revision(version)
162 # populate parent id cache
162 # populate parent id cache
163 if not rev.parent_ids:
163 if not rev.parent_ids:
164 parents = []
164 parents = []
165 self._parentids[version] = (revision.NULL_REVISION,)
165 self._parentids[version] = (revision.NULL_REVISION,)
166 else:
166 else:
167 parents = self._filterghosts(rev.parent_ids)
167 parents = self._filterghosts(rev.parent_ids)
168 self._parentids[version] = parents
168 self._parentids[version] = parents
169
169
170 branch = self.recode(rev.properties.get('branch-nick', u'default'))
170 branch = self.recode(rev.properties.get('branch-nick', u'default'))
171 if branch == 'trunk':
171 if branch == 'trunk':
172 branch = 'default'
172 branch = 'default'
173 return common.commit(parents=parents,
173 return common.commit(parents=parents,
174 date='%d %d' % (rev.timestamp, -rev.timezone),
174 date='%d %d' % (rev.timestamp, -rev.timezone),
175 author=self.recode(rev.committer),
175 author=self.recode(rev.committer),
176 desc=self.recode(rev.message),
176 desc=self.recode(rev.message),
177 branch=branch,
177 branch=branch,
178 rev=version)
178 rev=version)
179
179
180 def gettags(self):
180 def gettags(self):
181 bytetags = {}
181 bytetags = {}
182 for branch in self._bzrbranches():
182 for branch in self._bzrbranches():
183 if not branch.supports_tags():
183 if not branch.supports_tags():
184 return {}
184 return {}
185 tagdict = branch.tags.get_tag_dict()
185 tagdict = branch.tags.get_tag_dict()
186 for name, rev in tagdict.iteritems():
186 for name, rev in tagdict.iteritems():
187 bytetags[self.recode(name)] = rev
187 bytetags[self.recode(name)] = rev
188 return bytetags
188 return bytetags
189
189
190 def getchangedfiles(self, rev, i):
190 def getchangedfiles(self, rev, i):
191 self._modecache = {}
191 self._modecache = {}
192 curtree = self.sourcerepo.revision_tree(rev)
192 curtree = self.sourcerepo.revision_tree(rev)
193 if i is not None:
193 if i is not None:
194 parentid = self._parentids[rev][i]
194 parentid = self._parentids[rev][i]
195 else:
195 else:
196 # no parent id, get the empty revision
196 # no parent id, get the empty revision
197 parentid = revision.NULL_REVISION
197 parentid = revision.NULL_REVISION
198
198
199 prevtree = self.sourcerepo.revision_tree(parentid)
199 prevtree = self.sourcerepo.revision_tree(parentid)
200 changes = [e[0] for e in self._gettreechanges(curtree, prevtree)[0]]
200 changes = [e[0] for e in self._gettreechanges(curtree, prevtree)[0]]
201 return changes
201 return changes
202
202
203 def _gettreechanges(self, current, origin):
203 def _gettreechanges(self, current, origin):
204 revid = current._revision_id
204 revid = current._revision_id
205 changes = []
205 changes = []
206 renames = {}
206 renames = {}
207 seen = set()
207 seen = set()
208
209 # Fall back to the deprecated attribute for legacy installations.
210 try:
211 inventory = origin.root_inventory
212 except AttributeError:
213 inventory = origin.inventory
214
208 # Process the entries by reverse lexicographic name order to
215 # Process the entries by reverse lexicographic name order to
209 # handle nested renames correctly, most specific first.
216 # handle nested renames correctly, most specific first.
210 curchanges = sorted(current.iter_changes(origin),
217 curchanges = sorted(current.iter_changes(origin),
211 key=lambda c: c[1][0] or c[1][1],
218 key=lambda c: c[1][0] or c[1][1],
212 reverse=True)
219 reverse=True)
213 for (fileid, paths, changed_content, versioned, parent, name,
220 for (fileid, paths, changed_content, versioned, parent, name,
214 kind, executable) in curchanges:
221 kind, executable) in curchanges:
215
222
216 if paths[0] == u'' or paths[1] == u'':
223 if paths[0] == u'' or paths[1] == u'':
217 # ignore changes to tree root
224 # ignore changes to tree root
218 continue
225 continue
219
226
220 # bazaar tracks directories, mercurial does not, so
227 # bazaar tracks directories, mercurial does not, so
221 # we have to rename the directory contents
228 # we have to rename the directory contents
222 if kind[1] == 'directory':
229 if kind[1] == 'directory':
223 if kind[0] not in (None, 'directory'):
230 if kind[0] not in (None, 'directory'):
224 # Replacing 'something' with a directory, record it
231 # Replacing 'something' with a directory, record it
225 # so it can be removed.
232 # so it can be removed.
226 changes.append((self.recode(paths[0]), revid))
233 changes.append((self.recode(paths[0]), revid))
227
234
228 if kind[0] == 'directory' and None not in paths:
235 if kind[0] == 'directory' and None not in paths:
229 renaming = paths[0] != paths[1]
236 renaming = paths[0] != paths[1]
230 # neither an add nor an delete - a move
237 # neither an add nor an delete - a move
231 # rename all directory contents manually
238 # rename all directory contents manually
232 subdir = origin.root_inventory.path2id(paths[0])
239 subdir = inventory.path2id(paths[0])
233 # get all child-entries of the directory
240 # get all child-entries of the directory
234 for name, entry in origin.root_inventory.iter_entries(
241 for name, entry in inventory.iter_entries(subdir):
235 subdir):
236 # hg does not track directory renames
242 # hg does not track directory renames
237 if entry.kind == 'directory':
243 if entry.kind == 'directory':
238 continue
244 continue
239 frompath = self.recode(paths[0] + '/' + name)
245 frompath = self.recode(paths[0] + '/' + name)
240 if frompath in seen:
246 if frompath in seen:
241 # Already handled by a more specific change entry
247 # Already handled by a more specific change entry
242 # This is important when you have:
248 # This is important when you have:
243 # a => b
249 # a => b
244 # a/c => a/c
250 # a/c => a/c
245 # Here a/c must not be renamed into b/c
251 # Here a/c must not be renamed into b/c
246 continue
252 continue
247 seen.add(frompath)
253 seen.add(frompath)
248 if not renaming:
254 if not renaming:
249 continue
255 continue
250 topath = self.recode(paths[1] + '/' + name)
256 topath = self.recode(paths[1] + '/' + name)
251 # register the files as changed
257 # register the files as changed
252 changes.append((frompath, revid))
258 changes.append((frompath, revid))
253 changes.append((topath, revid))
259 changes.append((topath, revid))
254 # add to mode cache
260 # add to mode cache
255 mode = ((entry.executable and 'x')
261 mode = ((entry.executable and 'x')
256 or (entry.kind == 'symlink' and 's')
262 or (entry.kind == 'symlink' and 's')
257 or '')
263 or '')
258 self._modecache[(topath, revid)] = mode
264 self._modecache[(topath, revid)] = mode
259 # register the change as move
265 # register the change as move
260 renames[topath] = frompath
266 renames[topath] = frompath
261
267
262 # no further changes, go to the next change
268 # no further changes, go to the next change
263 continue
269 continue
264
270
265 # we got unicode paths, need to convert them
271 # we got unicode paths, need to convert them
266 path, topath = paths
272 path, topath = paths
267 if path is not None:
273 if path is not None:
268 path = self.recode(path)
274 path = self.recode(path)
269 if topath is not None:
275 if topath is not None:
270 topath = self.recode(topath)
276 topath = self.recode(topath)
271 seen.add(path or topath)
277 seen.add(path or topath)
272
278
273 if topath is None:
279 if topath is None:
274 # file deleted
280 # file deleted
275 changes.append((path, revid))
281 changes.append((path, revid))
276 continue
282 continue
277
283
278 # renamed
284 # renamed
279 if path and path != topath:
285 if path and path != topath:
280 renames[topath] = path
286 renames[topath] = path
281 changes.append((path, revid))
287 changes.append((path, revid))
282
288
283 # populate the mode cache
289 # populate the mode cache
284 kind, executable = [e[1] for e in (kind, executable)]
290 kind, executable = [e[1] for e in (kind, executable)]
285 mode = ((executable and 'x') or (kind == 'symlink' and 'l')
291 mode = ((executable and 'x') or (kind == 'symlink' and 'l')
286 or '')
292 or '')
287 self._modecache[(topath, revid)] = mode
293 self._modecache[(topath, revid)] = mode
288 changes.append((topath, revid))
294 changes.append((topath, revid))
289
295
290 return changes, renames
296 return changes, renames
291
297
292 def _filterghosts(self, ids):
298 def _filterghosts(self, ids):
293 """Filters out ghost revisions which hg does not support, see
299 """Filters out ghost revisions which hg does not support, see
294 <http://bazaar-vcs.org/GhostRevision>
300 <http://bazaar-vcs.org/GhostRevision>
295 """
301 """
296 parentmap = self.sourcerepo.get_parent_map(ids)
302 parentmap = self.sourcerepo.get_parent_map(ids)
297 parents = tuple([parent for parent in ids if parent in parentmap])
303 parents = tuple([parent for parent in ids if parent in parentmap])
298 return parents
304 return parents
General Comments 0
You need to be logged in to leave comments. Login now