##// END OF EJS Templates
convert: svn: use revmap to parse only new revisions in incremental conversions
Brendan Cully -
r4813:1fcdf2fe default
parent child Browse files
Show More
@@ -1,119 +1,118
1 1 # common code for the convert extension
2 2
3 3 class NoRepo(Exception): pass
4 4
5 5 class commit(object):
6 6 def __init__(self, **parts):
7 7 for x in "author date desc parents".split():
8 8 if not x in parts:
9 9 raise util.Abort("commit missing field %s" % x)
10 10 self.__dict__.update(parts)
11 11 if not self.desc or self.desc.isspace():
12 12 self.desc = '*** empty log message ***'
13 13
14 14 class converter_source(object):
15 15 """Conversion source interface"""
16 16
17 17 def __init__(self, ui, path, rev=None):
18 18 """Initialize conversion source (or raise NoRepo("message")
19 19 exception if path is not a valid repository)"""
20 20 self.ui = ui
21 21 self.path = path
22 22 self.rev = rev
23 23
24 24 self.encoding = 'utf-8'
25 self.revmap = {}
26 25
27 26 def setrevmap(self, revmap):
28 27 """set the map of already-converted revisions"""
29 self.revmap = revmap
28 pass
30 29
31 30 def getheads(self):
32 31 """Return a list of this repository's heads"""
33 32 raise NotImplementedError()
34 33
35 34 def getfile(self, name, rev):
36 35 """Return file contents as a string"""
37 36 raise NotImplementedError()
38 37
39 38 def getmode(self, name, rev):
40 39 """Return file mode, eg. '', 'x', or 'l'"""
41 40 raise NotImplementedError()
42 41
43 42 def getchanges(self, version):
44 43 """Return sorted list of (filename, id) tuples for all files changed in rev.
45 44
46 45 id just tells us which revision to return in getfile(), e.g. in
47 46 git it's an object hash."""
48 47 raise NotImplementedError()
49 48
50 49 def getcommit(self, version):
51 50 """Return the commit object for version"""
52 51 raise NotImplementedError()
53 52
54 53 def gettags(self):
55 54 """Return the tags as a dictionary of name: revision"""
56 55 raise NotImplementedError()
57 56
58 57 def recode(self, s, encoding=None):
59 58 if not encoding:
60 59 encoding = self.encoding or 'utf-8'
61 60
62 61 try:
63 62 return s.decode(encoding).encode("utf-8")
64 63 except:
65 64 try:
66 65 return s.decode("latin-1").encode("utf-8")
67 66 except:
68 67 return s.decode(encoding, "replace").encode("utf-8")
69 68
70 69 class converter_sink(object):
71 70 """Conversion sink (target) interface"""
72 71
73 72 def __init__(self, ui, path):
74 73 """Initialize conversion sink (or raise NoRepo("message")
75 74 exception if path is not a valid repository)"""
76 75 raise NotImplementedError()
77 76
78 77 def getheads(self):
79 78 """Return a list of this repository's heads"""
80 79 raise NotImplementedError()
81 80
82 81 def mapfile(self):
83 82 """Path to a file that will contain lines
84 83 source_rev_id sink_rev_id
85 84 mapping equivalent revision identifiers for each system."""
86 85 raise NotImplementedError()
87 86
88 87 def authorfile(self):
89 88 """Path to a file that will contain lines
90 89 srcauthor=dstauthor
91 90 mapping equivalent authors identifiers for each system."""
92 91 return None
93 92
94 93 def putfile(self, f, e, data):
95 94 """Put file for next putcommit().
96 95 f: path to file
97 96 e: '', 'x', or 'l' (regular file, executable, or symlink)
98 97 data: file contents"""
99 98 raise NotImplementedError()
100 99
101 100 def delfile(self, f):
102 101 """Delete file for next putcommit().
103 102 f: path to file"""
104 103 raise NotImplementedError()
105 104
106 105 def putcommit(self, files, parents, commit):
107 106 """Create a revision with all changed files listed in 'files'
108 107 and having listed parents. 'commit' is a commit object containing
109 108 at a minimum the author, date, and message for this changeset.
110 109 Called after putfile() and delfile() calls. Note that the sink
111 110 repository is not told to update itself to a particular revision
112 111 (or even what that revision would be) before it receives the
113 112 file data."""
114 113 raise NotImplementedError()
115 114
116 115 def puttags(self, tags):
117 116 """Put tags into sink.
118 117 tags: {tagname: sink_rev_id, ...}"""
119 118 raise NotImplementedError()
@@ -1,611 +1,622
1 1 # Subversion 1.4/1.5 Python API backend
2 2 #
3 3 # Copyright(C) 2007 Daniel Holth et al
4 4
5 5 import pprint
6 6 import locale
7 7
8 8 from mercurial import util
9 9
10 10 # Subversion stuff. Works best with very recent Python SVN bindings
11 11 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
12 12 # these bindings.
13 13
14 14 from cStringIO import StringIO
15 15
16 16 from common import NoRepo, commit, converter_source
17 17
18 18 try:
19 19 from svn.core import SubversionException, Pool
20 20 import svn.core
21 21 import svn.ra
22 22 import svn.delta
23 23 import svn
24 24 import transport
25 25 except ImportError:
26 26 pass
27 27
28 28 class CompatibilityException(Exception): pass
29 29
30 30 class svn_entry(object):
31 31 """Emulate a Subversion path change."""
32 32 __slots__ = ['path', 'copyfrom_path', 'copyfrom_rev', 'action']
33 33 def __init__(self, entry):
34 34 self.copyfrom_path = entry.copyfrom_path
35 35 self.copyfrom_rev = entry.copyfrom_rev
36 36 self.action = entry.action
37 37
38 38 def __str__(self):
39 39 return "%s %s %s" % (self.action, self.copyfrom_path, self.copyfrom_rev)
40 40
41 41 def __repr__(self):
42 42 return self.__str__()
43 43
44 44 class svn_paths(object):
45 45 """Emulate a Subversion ordered dictionary of changed paths."""
46 46 __slots__ = ['values', 'order']
47 47 def __init__(self, orig_paths):
48 48 self.order = []
49 49 self.values = {}
50 50 if hasattr(orig_paths, 'keys'):
51 51 self.order = sorted(orig_paths.keys())
52 52 self.values.update(orig_paths)
53 53 return
54 54 if not orig_paths:
55 55 return
56 56 for path in orig_paths:
57 57 self.order.append(path)
58 58 self.values[path] = svn_entry(orig_paths[path])
59 59 self.order.sort() # maybe the order it came in isn't so great...
60 60
61 61 def __iter__(self):
62 62 return iter(self.order)
63 63
64 64 def __getitem__(self, key):
65 65 return self.values[key]
66 66
67 67 def __str__(self):
68 68 s = "{\n"
69 69 for path in self.order:
70 70 s += "'%s': %s,\n" % (path, self.values[path])
71 71 s += "}"
72 72 return s
73 73
74 74 def __repr__(self):
75 75 return self.__str__()
76 76
77 77 # SVN conversion code stolen from bzr-svn and tailor
78 78 class convert_svn(converter_source):
79 79 def __init__(self, ui, url, rev=None):
80 80 super(convert_svn, self).__init__(ui, url, rev=rev)
81 81
82 82 try:
83 83 SubversionException
84 84 except NameError:
85 85 msg = 'subversion python bindings could not be loaded\n'
86 86 ui.warn(msg)
87 87 raise NoRepo(msg)
88 88
89 89 self.encoding = locale.getpreferredencoding()
90 self.lastrevs = {}
91
90 92 latest = None
91 93 if rev:
92 94 try:
93 95 latest = int(rev)
94 96 except ValueError:
95 97 raise util.Abort('svn: revision %s is not an integer' % rev)
96 98 try:
97 99 # Support file://path@rev syntax. Useful e.g. to convert
98 100 # deleted branches.
99 101 url, latest = url.rsplit("@", 1)
100 102 latest = int(latest)
101 103 except ValueError, e:
102 104 pass
103 105 self.url = url
104 106 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
105 107 try:
106 108 self.transport = transport.SvnRaTransport(url = url)
107 109 self.ra = self.transport.ra
108 110 self.ctx = svn.client.create_context()
109 111 self.base = svn.ra.get_repos_root(self.ra)
110 112 self.module = self.url[len(self.base):]
111 113 self.modulemap = {} # revision, module
112 114 self.commits = {}
113 115 self.files = {}
114 116 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
115 117 except SubversionException, e:
116 118 raise NoRepo("couldn't open SVN repo %s" % url)
117 119
118 120 try:
119 121 self.get_blacklist()
120 122 except IOError, e:
121 123 pass
122 124
123 125 self.last_changed = self.latest(self.module, latest)
124 126
125 127 self.head = self.revid(self.last_changed)
126 128
127 129 def revid(self, revnum, module=None):
128 130 if not module:
129 131 module = self.module
130 132 return (u"svn:%s%s@%s" % (self.uuid, module, revnum)).decode(self.encoding)
131 133
132 134 def revnum(self, rev):
133 135 return int(rev.split('@')[-1])
134 136
135 137 def revsplit(self, rev):
136 138 url, revnum = rev.encode(self.encoding).split('@', 1)
137 139 revnum = int(revnum)
138 140 parts = url.split('/', 1)
139 141 uuid = parts.pop(0)[4:]
140 142 mod = ''
141 143 if parts:
142 144 mod = '/' + parts[0]
143 145 return uuid, mod, revnum
144 146
145 147 def latest(self, path, stop=0):
146 148 'find the latest revision affecting path, up to stop'
147 149 if not stop:
148 150 stop = svn.ra.get_latest_revnum(self.ra)
149 151 try:
150 152 self.reparent('')
151 153 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
152 154 self.reparent(self.module)
153 155 except SubversionException:
154 156 dirent = None
155 157 if not dirent:
156 158 raise util.Abort('%s not found up to revision %d' \
157 159 % (path, stop))
158 160
159 161 return dirent.created_rev
160 162
161 163 def get_blacklist(self):
162 164 """Avoid certain revision numbers.
163 165 It is not uncommon for two nearby revisions to cancel each other
164 166 out, e.g. 'I copied trunk into a subdirectory of itself instead
165 167 of making a branch'. The converted repository is significantly
166 168 smaller if we ignore such revisions."""
167 169 self.blacklist = set()
168 170 blacklist = self.blacklist
169 171 for line in file("blacklist.txt", "r"):
170 172 if not line.startswith("#"):
171 173 try:
172 174 svn_rev = int(line.strip())
173 175 blacklist.add(svn_rev)
174 176 except ValueError, e:
175 177 pass # not an integer or a comment
176 178
177 179 def is_blacklisted(self, svn_rev):
178 180 return svn_rev in self.blacklist
179 181
180 182 def reparent(self, module):
181 183 svn_url = self.base + module
182 184 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
183 185 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
184 186
185 187 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
186 188 def get_entry_from_path(path, module=self.module):
187 189 # Given the repository url of this wc, say
188 190 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
189 191 # extract the "entry" portion (a relative path) from what
190 192 # svn log --xml says, ie
191 193 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
192 194 # that is to say "tests/PloneTestCase.py"
193 195
194 196 if path.startswith(module):
195 197 relative = path[len(module):]
196 198 if relative.startswith('/'):
197 199 return relative[1:]
198 200 else:
199 201 return relative
200 202
201 203 # The path is outside our tracked tree...
202 204 self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module))
203 205 return None
204 206
205 207 received = []
206 208 # svn.ra.get_log requires no other calls to the ra until it completes,
207 209 # so we just collect the log entries and parse them afterwards
208 210 def receivelog(*arg, **args):
209 211 received.append(arg)
210 212
211 213 self.child_cset = None
212 214 def parselogentry(*arg, **args):
213 215 orig_paths, revnum, author, date, message, pool = arg
214 216 orig_paths = svn_paths(orig_paths)
215 217
216 218 if self.is_blacklisted(revnum):
217 219 self.ui.note('skipping blacklisted revision %d\n' % revnum)
218 220 return
219 221
220 222 self.ui.debug("parsing revision %d\n" % revnum)
221 223
222 224 if orig_paths is None:
223 225 self.ui.debug('revision %d has no entries\n' % revnum)
224 226 return
225 227
226 228 if revnum in self.modulemap:
227 229 new_module = self.modulemap[revnum]
228 230 if new_module != self.module:
229 231 self.module = new_module
230 232 self.reparent(self.module)
231 233
232 234 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
233 235 copies = {}
234 236 entries = []
235 237 rev = self.revid(revnum)
236 238 parents = []
237 239 try:
238 240 branch = self.module.split("/")[-1]
239 241 if branch == 'trunk':
240 242 branch = ''
241 243 except IndexError:
242 244 branch = None
243 245
244 246 for path in orig_paths:
245 247 # self.ui.write("path %s\n" % path)
246 248 if path == self.module: # Follow branching back in history
247 249 ent = orig_paths[path]
248 250 if ent:
249 251 if ent.copyfrom_path:
250 252 # ent.copyfrom_rev may not be the actual last revision
251 253 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
252 254 self.modulemap[prev] = ent.copyfrom_path
253 255 parents = [self.revid(prev, ent.copyfrom_path)]
254 256 self.ui.note('found parent of branch %s at %d: %s\n' % \
255 257 (self.module, prev, ent.copyfrom_path))
256 258 else:
257 259 self.ui.debug("No copyfrom path, don't know what to do.\n")
258 260 # Maybe it was added and there is no more history.
259 261 entrypath = get_entry_from_path(path, module=self.module)
260 262 # self.ui.write("entrypath %s\n" % entrypath)
261 263 if entrypath is None:
262 264 # Outside our area of interest
263 265 self.ui.debug("boring@%s: %s\n" % (revnum, path))
264 266 continue
265 267 entry = entrypath.decode(self.encoding)
266 268 ent = orig_paths[path]
267 269
268 270 kind = svn.ra.check_path(self.ra, entrypath, revnum)
269 271 if kind == svn.core.svn_node_file:
270 272 if ent.copyfrom_path:
271 273 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
272 274 if copyfrom_path:
273 275 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
274 276 # It's probably important for hg that the source
275 277 # exists in the revision's parent, not just the
276 278 # ent.copyfrom_rev
277 279 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
278 280 if fromkind != 0:
279 281 copies[self.recode(entry)] = self.recode(copyfrom_path)
280 282 entries.append(self.recode(entry))
281 283 elif kind == 0: # gone, but had better be a deleted *file*
282 284 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
283 285
284 286 # if a branch is created but entries are removed in the same
285 287 # changeset, get the right fromrev
286 288 if parents:
287 289 uuid, old_module, fromrev = self.revsplit(parents[0])
288 290 else:
289 291 fromrev = revnum - 1
290 292 # might always need to be revnum - 1 in these 3 lines?
291 293 old_module = self.modulemap.get(fromrev, self.module)
292 294
293 295 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
294 296 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
295 297
296 298 def lookup_parts(p):
297 299 rc = None
298 300 parts = p.split("/")
299 301 for i in range(len(parts)):
300 302 part = "/".join(parts[:i])
301 303 info = part, copyfrom.get(part, None)
302 304 if info[1] is not None:
303 305 self.ui.debug("Found parent directory %s\n" % info[1])
304 306 rc = info
305 307 return rc
306 308
307 309 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
308 310
309 311 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
310 312
311 313 # need to remove fragment from lookup_parts and replace with copyfrom_path
312 314 if frompath is not None:
313 315 self.ui.debug("munge-o-matic\n")
314 316 self.ui.debug(entrypath + '\n')
315 317 self.ui.debug(entrypath[len(frompath):] + '\n')
316 318 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
317 319 fromrev = froment.copyfrom_rev
318 320 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
319 321
320 322 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
321 323 if fromkind == svn.core.svn_node_file: # a deleted file
322 324 entries.append(self.recode(entry))
323 325 elif fromkind == svn.core.svn_node_dir:
324 326 # print "Deleted/moved non-file:", revnum, path, ent
325 327 # children = self._find_children(path, revnum - 1)
326 328 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
327 329 # Sometimes this is tricky. For example: in
328 330 # The Subversion Repository revision 6940 a dir
329 331 # was copied and one of its files was deleted
330 332 # from the new location in the same commit. This
331 333 # code can't deal with that yet.
332 334 if ent.action == 'C':
333 335 children = self._find_children(path, fromrev)
334 336 else:
335 337 oroot = entrypath.strip('/')
336 338 nroot = path.strip('/')
337 339 children = self._find_children(oroot, fromrev)
338 340 children = [s.replace(oroot,nroot) for s in children]
339 341 # Mark all [files, not directories] as deleted.
340 342 for child in children:
341 343 # Can we move a child directory and its
342 344 # parent in the same commit? (probably can). Could
343 345 # cause problems if instead of revnum -1,
344 346 # we have to look in (copyfrom_path, revnum - 1)
345 347 entrypath = get_entry_from_path("/" + child, module=old_module)
346 348 if entrypath:
347 349 entry = self.recode(entrypath.decode(self.encoding))
348 350 if entry in copies:
349 351 # deleted file within a copy
350 352 del copies[entry]
351 353 else:
352 354 entries.append(entry)
353 355 else:
354 356 self.ui.debug('unknown path in revision %d: %s\n' % \
355 357 (revnum, path))
356 358 elif kind == svn.core.svn_node_dir:
357 359 # Should probably synthesize normal file entries
358 360 # and handle as above to clean up copy/rename handling.
359 361
360 362 # If the directory just had a prop change,
361 363 # then we shouldn't need to look for its children.
362 364 # Also this could create duplicate entries. Not sure
363 365 # whether this will matter. Maybe should make entries a set.
364 366 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
365 367 # This will fail if a directory was copied
366 368 # from another branch and then some of its files
367 369 # were deleted in the same transaction.
368 370 children = self._find_children(path, revnum)
369 371 children.sort()
370 372 for child in children:
371 373 # Can we move a child directory and its
372 374 # parent in the same commit? (probably can). Could
373 375 # cause problems if instead of revnum -1,
374 376 # we have to look in (copyfrom_path, revnum - 1)
375 377 entrypath = get_entry_from_path("/" + child, module=self.module)
376 378 # print child, self.module, entrypath
377 379 if entrypath:
378 380 # Need to filter out directories here...
379 381 kind = svn.ra.check_path(self.ra, entrypath, revnum)
380 382 if kind != svn.core.svn_node_dir:
381 383 entries.append(self.recode(entrypath))
382 384
383 385 # Copies here (must copy all from source)
384 386 # Probably not a real problem for us if
385 387 # source does not exist
386 388
387 389 # Can do this with the copy command "hg copy"
388 390 # if ent.copyfrom_path:
389 391 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
390 392 # module=self.module)
391 393 # copyto_entry = entrypath
392 394 #
393 395 # print "copy directory", copyfrom_entry, 'to', copyto_entry
394 396 #
395 397 # copies.append((copyfrom_entry, copyto_entry))
396 398
397 399 if ent.copyfrom_path:
398 400 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
399 401 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
400 402 if copyfrom_entry:
401 403 copyfrom[path] = ent
402 404 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
403 405
404 406 # Good, /probably/ a regular copy. Really should check
405 407 # to see whether the parent revision actually contains
406 408 # the directory in question.
407 409 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
408 410 children.sort()
409 411 for child in children:
410 412 entrypath = get_entry_from_path("/" + child, module=self.module)
411 413 if entrypath:
412 414 entry = entrypath.decode(self.encoding)
413 415 # print "COPY COPY From", copyfrom_entry, entry
414 416 copyto_path = path + entry[len(copyfrom_entry):]
415 417 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
416 418 # print "COPY", entry, "COPY To", copyto_entry
417 419 copies[self.recode(copyto_entry)] = self.recode(entry)
418 420 # copy from quux splort/quuxfile
419 421
420 422 self.modulemap[revnum] = self.module # track backwards in time
421 423 # a list of (filename, id) where id lets us retrieve the file.
422 424 # eg in git, id is the object hash. for svn it'll be the
423 425 self.files[rev] = zip(entries, [rev] * len(entries))
424 426 if not entries:
425 427 return
426 428
427 429 # Example SVN datetime. Includes microseconds.
428 430 # ISO-8601 conformant
429 431 # '2007-01-04T17:35:00.902377Z'
430 432 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
431 433
432 434 log = message and self.recode(message)
433 435 author = author and self.recode(author) or ''
434 436
435 437 cset = commit(author=author,
436 438 date=util.datestr(date),
437 439 desc=log,
438 440 parents=parents,
439 441 copies=copies,
440 442 branch=branch)
441 443
442 444 self.commits[rev] = cset
443 445 if self.child_cset and not self.child_cset.parents:
444 446 self.child_cset.parents = [rev]
445 447 self.child_cset = cset
446 448
447 449 self.ui.note('fetching revision log for "%s" from %d to %d\n' % \
448 450 (self.module, from_revnum, to_revnum))
449 451
450 452 try:
451 453 discover_changed_paths = True
452 454 strict_node_history = False
453 455 svn.ra.get_log(self.ra, [self.module], from_revnum, to_revnum, 0,
454 456 discover_changed_paths, strict_node_history,
455 457 receivelog)
456 458 for entry in received:
457 459 parselogentry(*entry)
458 self.last_revnum = to_revnum
459 460 except SubversionException, (_, num):
460 461 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
461 462 raise NoSuchRevision(branch=self,
462 463 revision="Revision number %d" % to_revnum)
463 464 raise
464 465
466 def setrevmap(self, revmap):
467 lastrevs = {}
468 for revid in revmap.keys():
469 uuid, module, revnum = self.revsplit(revid)
470 lastrevnum = lastrevs.setdefault(module, revnum)
471 if revnum > lastrevnum:
472 lastrevs[module] = revnum
473 self.lastrevs = lastrevs
474
465 475 def getheads(self):
466 476 # detect standard /branches, /tags, /trunk layout
467 477 optrev = svn.core.svn_opt_revision_t()
468 478 optrev.kind = svn.core.svn_opt_revision_number
469 479 optrev.value.number = self.last_changed
470 480 rpath = self.url.strip('/')
471 481 paths = svn.client.ls(rpath, optrev, False, self.ctx)
472 482 if 'branches' in paths and 'trunk' in paths:
473 483 self.module += '/trunk'
474 484 lt = self.latest(self.module, self.last_changed)
475 485 self.head = self.revid(lt)
476 486 self.heads = [self.head]
477 487 branches = svn.client.ls(rpath + '/branches', optrev, False, self.ctx)
478 488 for branch in branches.keys():
479 489 module = '/branches/' + branch
480 490 brevnum = self.latest(module, self.last_changed)
481 491 brev = self.revid(brevnum, module)
482 492 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
483 493 self.heads.append(brev)
484 494 else:
485 495 self.heads = [self.head]
486 496 return self.heads
487 497
488 498 def _getfile(self, file, rev):
489 499 io = StringIO()
490 500 # TODO: ra.get_file transmits the whole file instead of diffs.
491 501 mode = ''
492 502 try:
493 503 revnum = self.revnum(rev)
494 504 if self.module != self.modulemap[revnum]:
495 505 self.module = self.modulemap[revnum]
496 506 self.reparent(self.module)
497 507 info = svn.ra.get_file(self.ra, file, revnum, io)
498 508 if isinstance(info, list):
499 509 info = info[-1]
500 510 mode = ("svn:executable" in info) and 'x' or ''
501 511 mode = ("svn:special" in info) and 'l' or mode
502 512 except SubversionException, e:
503 513 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
504 514 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
505 515 if e.apr_err in notfound: # File not found
506 516 raise IOError()
507 517 raise
508 518 data = io.getvalue()
509 519 if mode == 'l':
510 520 link_prefix = "link "
511 521 if data.startswith(link_prefix):
512 522 data = data[len(link_prefix):]
513 523 return data, mode
514 524
515 525 def getfile(self, file, rev):
516 526 data, mode = self._getfile(file, rev)
517 527 self.modecache[(file, rev)] = mode
518 528 return data
519 529
520 530 def getmode(self, file, rev):
521 531 return self.modecache[(file, rev)]
522 532
523 533 def getchanges(self, rev):
524 534 self.modecache = {}
525 535 files = self.files[rev]
526 536 cl = files
527 537 cl.sort()
528 538 return cl
529 539
530 540 def getcommit(self, rev):
531 541 if rev not in self.commits:
532 542 uuid, module, revnum = self.revsplit(rev)
533 543 self.module = module
534 544 self.reparent(module)
535 self._fetch_revisions(from_revnum=revnum, to_revnum=0)
545 stop = self.lastrevs.get(module, 0)
546 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
536 547 return self.commits[rev]
537 548
538 549 def gettags(self):
539 550 tags = {}
540 551 def parselogentry(*arg, **args):
541 552 orig_paths, revnum, author, date, message, pool = arg
542 553 orig_paths = svn_paths(orig_paths)
543 554 for path in orig_paths:
544 555 if not path.startswith('/tags/'):
545 556 continue
546 557 ent = orig_paths[path]
547 558 source = ent.copyfrom_path
548 559 rev = ent.copyfrom_rev
549 560 tag = path.split('/', 2)[2]
550 561 tags[tag] = self.revid(rev, module=source)
551 562
552 563 start = self.revnum(self.head)
553 564 try:
554 565 svn.ra.get_log(self.ra, ['/tags'], 0, start, 0, True, False,
555 566 parselogentry)
556 567 return tags
557 568 except SubversionException:
558 569 self.ui.note('no tags found at revision %d\n' % start)
559 570 return {}
560 571
561 572 def _find_children(self, path, revnum):
562 573 path = path.strip("/")
563 574
564 575 def _find_children_fallback(path, revnum):
565 576 # SWIG python bindings for getdir are broken up to at least 1.4.3
566 577 pool = Pool()
567 578 optrev = svn.core.svn_opt_revision_t()
568 579 optrev.kind = svn.core.svn_opt_revision_number
569 580 optrev.value.number = revnum
570 581 rpath = '/'.join([self.base, path]).strip('/')
571 582 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev, True, self.ctx, pool).keys()]
572 583
573 584 if hasattr(self, '_find_children_fallback'):
574 585 return _find_children_fallback(path, revnum)
575 586
576 587 self.reparent("/" + path)
577 588 pool = Pool()
578 589
579 590 children = []
580 591 def find_children_inner(children, path, revnum = revnum):
581 592 if hasattr(svn.ra, 'get_dir2'): # Since SVN 1.4
582 593 fields = 0xffffffff # Binding does not provide SVN_DIRENT_ALL
583 594 getdir = svn.ra.get_dir2(self.ra, path, revnum, fields, pool)
584 595 else:
585 596 getdir = svn.ra.get_dir(self.ra, path, revnum, pool)
586 597 if type(getdir) == dict:
587 598 # python binding for getdir is broken up to at least 1.4.3
588 599 raise CompatibilityException()
589 600 dirents = getdir[0]
590 601 if type(dirents) == int:
591 602 # got here once due to infinite recursion bug
592 603 # pprint.pprint(getdir)
593 604 return
594 605 c = dirents.keys()
595 606 c.sort()
596 607 for child in c:
597 608 dirent = dirents[child]
598 609 if dirent.kind == svn.core.svn_node_dir:
599 610 find_children_inner(children, (path + "/" + child).strip("/"))
600 611 else:
601 612 children.append((path + "/" + child).strip("/"))
602 613
603 614 try:
604 615 find_children_inner(children, "")
605 616 except CompatibilityException:
606 617 self._find_children_fallback = True
607 618 self.reparent(self.module)
608 619 return _find_children_fallback(path, revnum)
609 620
610 621 self.reparent(self.module)
611 622 return [path + "/" + c for c in children]
General Comments 0
You need to be logged in to leave comments. Login now