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