##// END OF EJS Templates
convert: fix invalid svn.ra.check_path() call (issue 771)...
Patrick Mezard -
r5880:b32a0596 default
parent child Browse files
Show More
@@ -1,666 +1,671 b''
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 # Configuration options:
6 6 #
7 7 # convert.svn.trunk
8 8 # Relative path to the trunk (default: "trunk")
9 9 # convert.svn.branches
10 10 # Relative path to tree of branches (default: "branches")
11 11 # convert.svn.tags
12 12 # Relative path to tree of tags (default: "tags")
13 13 #
14 14 # Set these in a hgrc, or on the command line as follows:
15 15 #
16 16 # hg convert --config convert.svn.trunk=wackoname [...]
17 17
18 18 import locale
19 19 import os
20 20 import sys
21 21 import cPickle as pickle
22 22 from mercurial import util
23 23
24 24 # Subversion stuff. Works best with very recent Python SVN bindings
25 25 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
26 26 # these bindings.
27 27
28 28 from cStringIO import StringIO
29 29
30 30 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
31 31
32 32 try:
33 33 from svn.core import SubversionException, Pool
34 34 import svn
35 35 import svn.client
36 36 import svn.core
37 37 import svn.ra
38 38 import svn.delta
39 39 import transport
40 40 except ImportError:
41 41 pass
42 42
43 43 def geturl(path):
44 44 try:
45 45 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
46 46 except SubversionException:
47 47 pass
48 48 if os.path.isdir(path):
49 49 return 'file://%s' % os.path.normpath(os.path.abspath(path))
50 50 return path
51 51
52 52 def optrev(number):
53 53 optrev = svn.core.svn_opt_revision_t()
54 54 optrev.kind = svn.core.svn_opt_revision_number
55 55 optrev.value.number = number
56 56 return optrev
57 57
58 58 class changedpath(object):
59 59 def __init__(self, p):
60 60 self.copyfrom_path = p.copyfrom_path
61 61 self.copyfrom_rev = p.copyfrom_rev
62 62 self.action = p.action
63 63
64 64 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
65 65 strict_node_history=False):
66 66 protocol = -1
67 67 def receiver(orig_paths, revnum, author, date, message, pool):
68 68 if orig_paths is not None:
69 69 for k, v in orig_paths.iteritems():
70 70 orig_paths[k] = changedpath(v)
71 71 pickle.dump((orig_paths, revnum, author, date, message),
72 72 fp, protocol)
73 73
74 74 try:
75 75 # Use an ra of our own so that our parent can consume
76 76 # our results without confusing the server.
77 77 t = transport.SvnRaTransport(url=url)
78 78 svn.ra.get_log(t.ra, paths, start, end, limit,
79 79 discover_changed_paths,
80 80 strict_node_history,
81 81 receiver)
82 82 except SubversionException, (inst, num):
83 83 pickle.dump(num, fp, protocol)
84 84 else:
85 85 pickle.dump(None, fp, protocol)
86 86 fp.close()
87 87
88 88 def debugsvnlog(ui, **opts):
89 89 """Fetch SVN log in a subprocess and channel them back to parent to
90 90 avoid memory collection issues.
91 91 """
92 92 util.set_binary(sys.stdin)
93 93 util.set_binary(sys.stdout)
94 94 args = decodeargs(sys.stdin.read())
95 95 get_log_child(sys.stdout, *args)
96 96
97 97 # SVN conversion code stolen from bzr-svn and tailor
98 98 class svn_source(converter_source):
99 99 def __init__(self, ui, url, rev=None):
100 100 super(svn_source, self).__init__(ui, url, rev=rev)
101 101
102 102 try:
103 103 SubversionException
104 104 except NameError:
105 105 raise NoRepo('Subversion python bindings could not be loaded')
106 106
107 107 self.encoding = locale.getpreferredencoding()
108 108 self.lastrevs = {}
109 109
110 110 latest = None
111 111 try:
112 112 # Support file://path@rev syntax. Useful e.g. to convert
113 113 # deleted branches.
114 114 at = url.rfind('@')
115 115 if at >= 0:
116 116 latest = int(url[at+1:])
117 117 url = url[:at]
118 118 except ValueError, e:
119 119 pass
120 120 self.url = geturl(url)
121 121 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
122 122 try:
123 123 self.transport = transport.SvnRaTransport(url=self.url)
124 124 self.ra = self.transport.ra
125 125 self.ctx = self.transport.client
126 126 self.base = svn.ra.get_repos_root(self.ra)
127 127 self.module = self.url[len(self.base):]
128 128 self.modulemap = {} # revision, module
129 129 self.commits = {}
130 130 self.paths = {}
131 131 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
132 132 except SubversionException, e:
133 133 ui.print_exc()
134 134 raise NoRepo("%s does not look like a Subversion repo" % self.url)
135 135
136 136 if rev:
137 137 try:
138 138 latest = int(rev)
139 139 except ValueError:
140 140 raise util.Abort('svn: revision %s is not an integer' % rev)
141 141
142 142 try:
143 143 self.get_blacklist()
144 144 except IOError, e:
145 145 pass
146 146
147 147 self.last_changed = self.latest(self.module, latest)
148 148
149 149 self.head = self.revid(self.last_changed)
150 150 self._changescache = None
151 151
152 152 def setrevmap(self, revmap, order):
153 153 lastrevs = {}
154 154 for revid in revmap.keys():
155 155 uuid, module, revnum = self.revsplit(revid)
156 156 lastrevnum = lastrevs.setdefault(module, revnum)
157 157 if revnum > lastrevnum:
158 158 lastrevs[module] = revnum
159 159 self.lastrevs = lastrevs
160 160
161 161 def exists(self, path, optrev):
162 162 try:
163 163 svn.client.ls(self.url.rstrip('/') + '/' + path,
164 164 optrev, False, self.ctx)
165 165 return True
166 166 except SubversionException, err:
167 167 return False
168 168
169 169 def getheads(self):
170 170 # detect standard /branches, /tags, /trunk layout
171 171 rev = optrev(self.last_changed)
172 172 rpath = self.url.strip('/')
173 173 cfgtrunk = self.ui.config('convert', 'svn.trunk')
174 174 cfgbranches = self.ui.config('convert', 'svn.branches')
175 175 cfgtags = self.ui.config('convert', 'svn.tags')
176 176 trunk = (cfgtrunk or 'trunk').strip('/')
177 177 branches = (cfgbranches or 'branches').strip('/')
178 178 tags = (cfgtags or 'tags').strip('/')
179 179 if self.exists(trunk, rev) and self.exists(branches, rev) and self.exists(tags, rev):
180 180 self.ui.note('found trunk at %r, branches at %r and tags at %r\n' %
181 181 (trunk, branches, tags))
182 182 oldmodule = self.module
183 183 self.module += '/' + trunk
184 184 lt = self.latest(self.module, self.last_changed)
185 185 self.head = self.revid(lt)
186 186 self.heads = [self.head]
187 187 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
188 188 self.ctx)
189 189 for branch in branchnames.keys():
190 190 if oldmodule:
191 191 module = oldmodule + '/' + branches + '/' + branch
192 192 else:
193 193 module = '/' + branches + '/' + branch
194 194 brevnum = self.latest(module, self.last_changed)
195 195 brev = self.revid(brevnum, module)
196 196 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
197 197 self.heads.append(brev)
198 198
199 199 if oldmodule:
200 200 self.tags = '%s/%s' % (oldmodule, tags)
201 201 else:
202 202 self.tags = '/%s' % tags
203 203
204 204 elif cfgtrunk or cfgbranches or cfgtags:
205 205 raise util.Abort('trunk/branch/tags layout expected, but not found')
206 206 else:
207 207 self.ui.note('working with one branch\n')
208 208 self.heads = [self.head]
209 209 self.tags = tags
210 210 return self.heads
211 211
212 212 def getfile(self, file, rev):
213 213 data, mode = self._getfile(file, rev)
214 214 self.modecache[(file, rev)] = mode
215 215 return data
216 216
217 217 def getmode(self, file, rev):
218 218 return self.modecache[(file, rev)]
219 219
220 220 def getchanges(self, rev):
221 221 if self._changescache and self._changescache[0] == rev:
222 222 return self._changescache[1]
223 223 self._changescache = None
224 224 self.modecache = {}
225 225 (paths, parents) = self.paths[rev]
226 226 files, copies = self.expandpaths(rev, paths, parents)
227 227 files.sort()
228 228 files = zip(files, [rev] * len(files))
229 229
230 230 # caller caches the result, so free it here to release memory
231 231 del self.paths[rev]
232 232 return (files, copies)
233 233
234 234 def getchangedfiles(self, rev, i):
235 235 changes = self.getchanges(rev)
236 236 self._changescache = (rev, changes)
237 237 return [f[0] for f in changes[0]]
238 238
239 239 def getcommit(self, rev):
240 240 if rev not in self.commits:
241 241 uuid, module, revnum = self.revsplit(rev)
242 242 self.module = module
243 243 self.reparent(module)
244 244 stop = self.lastrevs.get(module, 0)
245 245 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
246 246 commit = self.commits[rev]
247 247 # caller caches the result, so free it here to release memory
248 248 del self.commits[rev]
249 249 return commit
250 250
251 251 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
252 252 strict_node_history=False):
253 253
254 254 def parent(fp):
255 255 while True:
256 256 entry = pickle.load(fp)
257 257 try:
258 258 orig_paths, revnum, author, date, message = entry
259 259 except:
260 260 if entry is None:
261 261 break
262 262 raise SubversionException("child raised exception", entry)
263 263 yield entry
264 264
265 265 args = [self.url, paths, start, end, limit, discover_changed_paths,
266 266 strict_node_history]
267 267 arg = encodeargs(args)
268 268 hgexe = util.hgexecutable()
269 269 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
270 270 stdin, stdout = os.popen2(cmd, 'b')
271 271
272 272 stdin.write(arg)
273 273 stdin.close()
274 274
275 275 for p in parent(stdout):
276 276 yield p
277 277
278 278 def gettags(self):
279 279 tags = {}
280 280 start = self.revnum(self.head)
281 281 try:
282 282 for entry in self.get_log([self.tags], 0, start):
283 283 orig_paths, revnum, author, date, message = entry
284 284 for path in orig_paths:
285 285 if not path.startswith(self.tags+'/'):
286 286 continue
287 287 ent = orig_paths[path]
288 288 source = ent.copyfrom_path
289 289 rev = ent.copyfrom_rev
290 290 tag = path.split('/')[-1]
291 291 tags[tag] = self.revid(rev, module=source)
292 292 except SubversionException, (inst, num):
293 293 self.ui.note('no tags found at revision %d\n' % start)
294 294 return tags
295 295
296 296 # -- helper functions --
297 297
298 298 def revid(self, revnum, module=None):
299 299 if not module:
300 300 module = self.module
301 301 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
302 302 revnum)
303 303
304 304 def revnum(self, rev):
305 305 return int(rev.split('@')[-1])
306 306
307 307 def revsplit(self, rev):
308 308 url, revnum = rev.encode(self.encoding).split('@', 1)
309 309 revnum = int(revnum)
310 310 parts = url.split('/', 1)
311 311 uuid = parts.pop(0)[4:]
312 312 mod = ''
313 313 if parts:
314 314 mod = '/' + parts[0]
315 315 return uuid, mod, revnum
316 316
317 317 def latest(self, path, stop=0):
318 318 'find the latest revision affecting path, up to stop'
319 319 if not stop:
320 320 stop = svn.ra.get_latest_revnum(self.ra)
321 321 try:
322 322 self.reparent('')
323 323 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
324 324 self.reparent(self.module)
325 325 except SubversionException:
326 326 dirent = None
327 327 if not dirent:
328 328 raise util.Abort('%s not found up to revision %d' % (path, stop))
329 329
330 330 return dirent.created_rev
331 331
332 332 def get_blacklist(self):
333 333 """Avoid certain revision numbers.
334 334 It is not uncommon for two nearby revisions to cancel each other
335 335 out, e.g. 'I copied trunk into a subdirectory of itself instead
336 336 of making a branch'. The converted repository is significantly
337 337 smaller if we ignore such revisions."""
338 338 self.blacklist = util.set()
339 339 blacklist = self.blacklist
340 340 for line in file("blacklist.txt", "r"):
341 341 if not line.startswith("#"):
342 342 try:
343 343 svn_rev = int(line.strip())
344 344 blacklist.add(svn_rev)
345 345 except ValueError, e:
346 346 pass # not an integer or a comment
347 347
348 348 def is_blacklisted(self, svn_rev):
349 349 return svn_rev in self.blacklist
350 350
351 351 def reparent(self, module):
352 352 svn_url = self.base + module
353 353 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
354 354 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
355 355
356 356 def expandpaths(self, rev, paths, parents):
357 357 def get_entry_from_path(path, module=self.module):
358 358 # Given the repository url of this wc, say
359 359 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
360 360 # extract the "entry" portion (a relative path) from what
361 361 # svn log --xml says, ie
362 362 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
363 363 # that is to say "tests/PloneTestCase.py"
364 364 if path.startswith(module):
365 365 relative = path[len(module):]
366 366 if relative.startswith('/'):
367 367 return relative[1:]
368 368 else:
369 369 return relative
370 370
371 371 # The path is outside our tracked tree...
372 372 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
373 373 return None
374 374
375 375 entries = []
376 376 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
377 377 copies = {}
378 378 revnum = self.revnum(rev)
379 379
380 380 if revnum in self.modulemap:
381 381 new_module = self.modulemap[revnum]
382 382 if new_module != self.module:
383 383 self.module = new_module
384 384 self.reparent(self.module)
385 385
386 386 for path, ent in paths:
387 387 entrypath = get_entry_from_path(path, module=self.module)
388 388 entry = entrypath.decode(self.encoding)
389 389
390 390 kind = svn.ra.check_path(self.ra, entrypath, revnum)
391 391 if kind == svn.core.svn_node_file:
392 392 if ent.copyfrom_path:
393 393 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
394 394 if copyfrom_path:
395 395 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
396 396 # It's probably important for hg that the source
397 397 # exists in the revision's parent, not just the
398 398 # ent.copyfrom_rev
399 399 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
400 400 if fromkind != 0:
401 401 copies[self.recode(entry)] = self.recode(copyfrom_path)
402 402 entries.append(self.recode(entry))
403 403 elif kind == 0: # gone, but had better be a deleted *file*
404 404 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
405 405
406 406 # if a branch is created but entries are removed in the same
407 407 # changeset, get the right fromrev
408 408 if parents:
409 409 uuid, old_module, fromrev = self.revsplit(parents[0])
410 410 else:
411 411 fromrev = revnum - 1
412 412 # might always need to be revnum - 1 in these 3 lines?
413 413 old_module = self.modulemap.get(fromrev, self.module)
414 414
415 415 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
416 416 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
417 417
418 418 def lookup_parts(p):
419 419 rc = None
420 420 parts = p.split("/")
421 421 for i in range(len(parts)):
422 422 part = "/".join(parts[:i])
423 423 info = part, copyfrom.get(part, None)
424 424 if info[1] is not None:
425 425 self.ui.debug("Found parent directory %s\n" % info[1])
426 426 rc = info
427 427 return rc
428 428
429 429 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
430 430
431 431 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
432 432
433 433 # need to remove fragment from lookup_parts and replace with copyfrom_path
434 434 if frompath is not None:
435 435 self.ui.debug("munge-o-matic\n")
436 436 self.ui.debug(entrypath + '\n')
437 437 self.ui.debug(entrypath[len(frompath):] + '\n')
438 438 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
439 439 fromrev = froment.copyfrom_rev
440 440 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
441 441
442 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
442 # We can avoid the reparent calls if the module has not changed
443 # but it probably does not worth the pain.
444 self.reparent('')
445 fromkind = svn.ra.check_path(self.ra, entrypath.strip('/'), fromrev)
446 self.reparent(self.module)
447
443 448 if fromkind == svn.core.svn_node_file: # a deleted file
444 449 entries.append(self.recode(entry))
445 450 elif fromkind == svn.core.svn_node_dir:
446 451 # print "Deleted/moved non-file:", revnum, path, ent
447 452 # children = self._find_children(path, revnum - 1)
448 453 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
449 454 # Sometimes this is tricky. For example: in
450 455 # The Subversion Repository revision 6940 a dir
451 456 # was copied and one of its files was deleted
452 457 # from the new location in the same commit. This
453 458 # code can't deal with that yet.
454 459 if ent.action == 'C':
455 460 children = self._find_children(path, fromrev)
456 461 else:
457 462 oroot = entrypath.strip('/')
458 463 nroot = path.strip('/')
459 464 children = self._find_children(oroot, fromrev)
460 465 children = [s.replace(oroot,nroot) for s in children]
461 466 # Mark all [files, not directories] as deleted.
462 467 for child in children:
463 468 # Can we move a child directory and its
464 469 # parent in the same commit? (probably can). Could
465 470 # cause problems if instead of revnum -1,
466 471 # we have to look in (copyfrom_path, revnum - 1)
467 472 entrypath = get_entry_from_path("/" + child, module=old_module)
468 473 if entrypath:
469 474 entry = self.recode(entrypath.decode(self.encoding))
470 475 if entry in copies:
471 476 # deleted file within a copy
472 477 del copies[entry]
473 478 else:
474 479 entries.append(entry)
475 480 else:
476 481 self.ui.debug('unknown path in revision %d: %s\n' % \
477 482 (revnum, path))
478 483 elif kind == svn.core.svn_node_dir:
479 484 # Should probably synthesize normal file entries
480 485 # and handle as above to clean up copy/rename handling.
481 486
482 487 # If the directory just had a prop change,
483 488 # then we shouldn't need to look for its children.
484 489 # Also this could create duplicate entries. Not sure
485 490 # whether this will matter. Maybe should make entries a set.
486 491 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
487 492 # This will fail if a directory was copied
488 493 # from another branch and then some of its files
489 494 # were deleted in the same transaction.
490 495 children = self._find_children(path, revnum)
491 496 children.sort()
492 497 for child in children:
493 498 # Can we move a child directory and its
494 499 # parent in the same commit? (probably can). Could
495 500 # cause problems if instead of revnum -1,
496 501 # we have to look in (copyfrom_path, revnum - 1)
497 502 entrypath = get_entry_from_path("/" + child, module=self.module)
498 503 # print child, self.module, entrypath
499 504 if entrypath:
500 505 # Need to filter out directories here...
501 506 kind = svn.ra.check_path(self.ra, entrypath, revnum)
502 507 if kind != svn.core.svn_node_dir:
503 508 entries.append(self.recode(entrypath))
504 509
505 510 # Copies here (must copy all from source)
506 511 # Probably not a real problem for us if
507 512 # source does not exist
508 513
509 514 # Can do this with the copy command "hg copy"
510 515 # if ent.copyfrom_path:
511 516 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
512 517 # module=self.module)
513 518 # copyto_entry = entrypath
514 519 #
515 520 # print "copy directory", copyfrom_entry, 'to', copyto_entry
516 521 #
517 522 # copies.append((copyfrom_entry, copyto_entry))
518 523
519 524 if ent.copyfrom_path:
520 525 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
521 526 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
522 527 if copyfrom_entry:
523 528 copyfrom[path] = ent
524 529 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
525 530
526 531 # Good, /probably/ a regular copy. Really should check
527 532 # to see whether the parent revision actually contains
528 533 # the directory in question.
529 534 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
530 535 children.sort()
531 536 for child in children:
532 537 entrypath = get_entry_from_path("/" + child, module=self.module)
533 538 if entrypath:
534 539 entry = entrypath.decode(self.encoding)
535 540 # print "COPY COPY From", copyfrom_entry, entry
536 541 copyto_path = path + entry[len(copyfrom_entry):]
537 542 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
538 543 # print "COPY", entry, "COPY To", copyto_entry
539 544 copies[self.recode(copyto_entry)] = self.recode(entry)
540 545 # copy from quux splort/quuxfile
541 546
542 547 return (entries, copies)
543 548
544 549 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
545 550 self.child_cset = None
546 551 def parselogentry(orig_paths, revnum, author, date, message):
547 552 self.ui.debug("parsing revision %d (%d changes)\n" %
548 553 (revnum, len(orig_paths)))
549 554
550 555 if revnum in self.modulemap:
551 556 new_module = self.modulemap[revnum]
552 557 if new_module != self.module:
553 558 self.module = new_module
554 559 self.reparent(self.module)
555 560
556 561 rev = self.revid(revnum)
557 562 # branch log might return entries for a parent we already have
558 563 if (rev in self.commits or
559 564 (revnum < self.lastrevs.get(self.module, 0))):
560 565 return
561 566
562 567 parents = []
563 568 # check whether this revision is the start of a branch
564 569 if self.module in orig_paths:
565 570 ent = orig_paths[self.module]
566 571 if ent.copyfrom_path:
567 572 # ent.copyfrom_rev may not be the actual last revision
568 573 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
569 574 self.modulemap[prev] = ent.copyfrom_path
570 575 parents = [self.revid(prev, ent.copyfrom_path)]
571 576 self.ui.note('found parent of branch %s at %d: %s\n' % \
572 577 (self.module, prev, ent.copyfrom_path))
573 578 else:
574 579 self.ui.debug("No copyfrom path, don't know what to do.\n")
575 580
576 581 self.modulemap[revnum] = self.module # track backwards in time
577 582
578 583 orig_paths = orig_paths.items()
579 584 orig_paths.sort()
580 585 paths = []
581 586 # filter out unrelated paths
582 587 for path, ent in orig_paths:
583 588 if not path.startswith(self.module):
584 589 self.ui.debug("boring@%s: %s\n" % (revnum, path))
585 590 continue
586 591 paths.append((path, ent))
587 592
588 593 self.paths[rev] = (paths, parents)
589 594
590 595 # Example SVN datetime. Includes microseconds.
591 596 # ISO-8601 conformant
592 597 # '2007-01-04T17:35:00.902377Z'
593 598 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
594 599
595 600 log = message and self.recode(message)
596 601 author = author and self.recode(author) or ''
597 602 try:
598 603 branch = self.module.split("/")[-1]
599 604 if branch == 'trunk':
600 605 branch = ''
601 606 except IndexError:
602 607 branch = None
603 608
604 609 cset = commit(author=author,
605 610 date=util.datestr(date),
606 611 desc=log,
607 612 parents=parents,
608 613 branch=branch,
609 614 rev=rev.encode('utf-8'))
610 615
611 616 self.commits[rev] = cset
612 617 if self.child_cset and not self.child_cset.parents:
613 618 self.child_cset.parents = [rev]
614 619 self.child_cset = cset
615 620
616 621 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
617 622 (self.module, from_revnum, to_revnum))
618 623
619 624 try:
620 625 for entry in self.get_log([self.module], from_revnum, to_revnum):
621 626 orig_paths, revnum, author, date, message = entry
622 627 if self.is_blacklisted(revnum):
623 628 self.ui.note('skipping blacklisted revision %d\n' % revnum)
624 629 continue
625 630 if orig_paths is None:
626 631 self.ui.debug('revision %d has no entries\n' % revnum)
627 632 continue
628 633 parselogentry(orig_paths, revnum, author, date, message)
629 634 except SubversionException, (inst, num):
630 635 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
631 636 raise NoSuchRevision(branch=self,
632 637 revision="Revision number %d" % to_revnum)
633 638 raise
634 639
635 640 def _getfile(self, file, rev):
636 641 io = StringIO()
637 642 # TODO: ra.get_file transmits the whole file instead of diffs.
638 643 mode = ''
639 644 try:
640 645 revnum = self.revnum(rev)
641 646 if self.module != self.modulemap[revnum]:
642 647 self.module = self.modulemap[revnum]
643 648 self.reparent(self.module)
644 649 info = svn.ra.get_file(self.ra, file, revnum, io)
645 650 if isinstance(info, list):
646 651 info = info[-1]
647 652 mode = ("svn:executable" in info) and 'x' or ''
648 653 mode = ("svn:special" in info) and 'l' or mode
649 654 except SubversionException, e:
650 655 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
651 656 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
652 657 if e.apr_err in notfound: # File not found
653 658 raise IOError()
654 659 raise
655 660 data = io.getvalue()
656 661 if mode == 'l':
657 662 link_prefix = "link "
658 663 if data.startswith(link_prefix):
659 664 data = data[len(link_prefix):]
660 665 return data, mode
661 666
662 667 def _find_children(self, path, revnum):
663 668 path = path.strip('/')
664 669 pool = Pool()
665 670 rpath = '/'.join([self.base, path]).strip('/')
666 671 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
General Comments 0
You need to be logged in to leave comments. Login now