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