##// END OF EJS Templates
convert: fix SVN date parser dropping the final whole second digit
David J. Mellor -
r5617:924fd86f default
parent child Browse files
Show More
@@ -1,666 +1,666
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 442 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
443 443 if fromkind == svn.core.svn_node_file: # a deleted file
444 444 entries.append(self.recode(entry))
445 445 elif fromkind == svn.core.svn_node_dir:
446 446 # print "Deleted/moved non-file:", revnum, path, ent
447 447 # children = self._find_children(path, revnum - 1)
448 448 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
449 449 # Sometimes this is tricky. For example: in
450 450 # The Subversion Repository revision 6940 a dir
451 451 # was copied and one of its files was deleted
452 452 # from the new location in the same commit. This
453 453 # code can't deal with that yet.
454 454 if ent.action == 'C':
455 455 children = self._find_children(path, fromrev)
456 456 else:
457 457 oroot = entrypath.strip('/')
458 458 nroot = path.strip('/')
459 459 children = self._find_children(oroot, fromrev)
460 460 children = [s.replace(oroot,nroot) for s in children]
461 461 # Mark all [files, not directories] as deleted.
462 462 for child in children:
463 463 # Can we move a child directory and its
464 464 # parent in the same commit? (probably can). Could
465 465 # cause problems if instead of revnum -1,
466 466 # we have to look in (copyfrom_path, revnum - 1)
467 467 entrypath = get_entry_from_path("/" + child, module=old_module)
468 468 if entrypath:
469 469 entry = self.recode(entrypath.decode(self.encoding))
470 470 if entry in copies:
471 471 # deleted file within a copy
472 472 del copies[entry]
473 473 else:
474 474 entries.append(entry)
475 475 else:
476 476 self.ui.debug('unknown path in revision %d: %s\n' % \
477 477 (revnum, path))
478 478 elif kind == svn.core.svn_node_dir:
479 479 # Should probably synthesize normal file entries
480 480 # and handle as above to clean up copy/rename handling.
481 481
482 482 # If the directory just had a prop change,
483 483 # then we shouldn't need to look for its children.
484 484 # Also this could create duplicate entries. Not sure
485 485 # whether this will matter. Maybe should make entries a set.
486 486 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
487 487 # This will fail if a directory was copied
488 488 # from another branch and then some of its files
489 489 # were deleted in the same transaction.
490 490 children = self._find_children(path, revnum)
491 491 children.sort()
492 492 for child in children:
493 493 # Can we move a child directory and its
494 494 # parent in the same commit? (probably can). Could
495 495 # cause problems if instead of revnum -1,
496 496 # we have to look in (copyfrom_path, revnum - 1)
497 497 entrypath = get_entry_from_path("/" + child, module=self.module)
498 498 # print child, self.module, entrypath
499 499 if entrypath:
500 500 # Need to filter out directories here...
501 501 kind = svn.ra.check_path(self.ra, entrypath, revnum)
502 502 if kind != svn.core.svn_node_dir:
503 503 entries.append(self.recode(entrypath))
504 504
505 505 # Copies here (must copy all from source)
506 506 # Probably not a real problem for us if
507 507 # source does not exist
508 508
509 509 # Can do this with the copy command "hg copy"
510 510 # if ent.copyfrom_path:
511 511 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
512 512 # module=self.module)
513 513 # copyto_entry = entrypath
514 514 #
515 515 # print "copy directory", copyfrom_entry, 'to', copyto_entry
516 516 #
517 517 # copies.append((copyfrom_entry, copyto_entry))
518 518
519 519 if ent.copyfrom_path:
520 520 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
521 521 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
522 522 if copyfrom_entry:
523 523 copyfrom[path] = ent
524 524 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
525 525
526 526 # Good, /probably/ a regular copy. Really should check
527 527 # to see whether the parent revision actually contains
528 528 # the directory in question.
529 529 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
530 530 children.sort()
531 531 for child in children:
532 532 entrypath = get_entry_from_path("/" + child, module=self.module)
533 533 if entrypath:
534 534 entry = entrypath.decode(self.encoding)
535 535 # print "COPY COPY From", copyfrom_entry, entry
536 536 copyto_path = path + entry[len(copyfrom_entry):]
537 537 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
538 538 # print "COPY", entry, "COPY To", copyto_entry
539 539 copies[self.recode(copyto_entry)] = self.recode(entry)
540 540 # copy from quux splort/quuxfile
541 541
542 542 return (entries, copies)
543 543
544 544 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
545 545 self.child_cset = None
546 546 def parselogentry(orig_paths, revnum, author, date, message):
547 547 self.ui.debug("parsing revision %d (%d changes)\n" %
548 548 (revnum, len(orig_paths)))
549 549
550 550 if revnum in self.modulemap:
551 551 new_module = self.modulemap[revnum]
552 552 if new_module != self.module:
553 553 self.module = new_module
554 554 self.reparent(self.module)
555 555
556 556 rev = self.revid(revnum)
557 557 # branch log might return entries for a parent we already have
558 558 if (rev in self.commits or
559 559 (revnum < self.lastrevs.get(self.module, 0))):
560 560 return
561 561
562 562 parents = []
563 563 # check whether this revision is the start of a branch
564 564 if self.module in orig_paths:
565 565 ent = orig_paths[self.module]
566 566 if ent.copyfrom_path:
567 567 # ent.copyfrom_rev may not be the actual last revision
568 568 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
569 569 self.modulemap[prev] = ent.copyfrom_path
570 570 parents = [self.revid(prev, ent.copyfrom_path)]
571 571 self.ui.note('found parent of branch %s at %d: %s\n' % \
572 572 (self.module, prev, ent.copyfrom_path))
573 573 else:
574 574 self.ui.debug("No copyfrom path, don't know what to do.\n")
575 575
576 576 self.modulemap[revnum] = self.module # track backwards in time
577 577
578 578 orig_paths = orig_paths.items()
579 579 orig_paths.sort()
580 580 paths = []
581 581 # filter out unrelated paths
582 582 for path, ent in orig_paths:
583 583 if not path.startswith(self.module):
584 584 self.ui.debug("boring@%s: %s\n" % (revnum, path))
585 585 continue
586 586 paths.append((path, ent))
587 587
588 588 self.paths[rev] = (paths, parents)
589 589
590 590 # Example SVN datetime. Includes microseconds.
591 591 # ISO-8601 conformant
592 592 # '2007-01-04T17:35:00.902377Z'
593 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
593 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
594 594
595 595 log = message and self.recode(message)
596 596 author = author and self.recode(author) or ''
597 597 try:
598 598 branch = self.module.split("/")[-1]
599 599 if branch == 'trunk':
600 600 branch = ''
601 601 except IndexError:
602 602 branch = None
603 603
604 604 cset = commit(author=author,
605 605 date=util.datestr(date),
606 606 desc=log,
607 607 parents=parents,
608 608 branch=branch,
609 609 rev=rev.encode('utf-8'))
610 610
611 611 self.commits[rev] = cset
612 612 if self.child_cset and not self.child_cset.parents:
613 613 self.child_cset.parents = [rev]
614 614 self.child_cset = cset
615 615
616 616 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
617 617 (self.module, from_revnum, to_revnum))
618 618
619 619 try:
620 620 for entry in self.get_log([self.module], from_revnum, to_revnum):
621 621 orig_paths, revnum, author, date, message = entry
622 622 if self.is_blacklisted(revnum):
623 623 self.ui.note('skipping blacklisted revision %d\n' % revnum)
624 624 continue
625 625 if orig_paths is None:
626 626 self.ui.debug('revision %d has no entries\n' % revnum)
627 627 continue
628 628 parselogentry(orig_paths, revnum, author, date, message)
629 629 except SubversionException, (inst, num):
630 630 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
631 631 raise NoSuchRevision(branch=self,
632 632 revision="Revision number %d" % to_revnum)
633 633 raise
634 634
635 635 def _getfile(self, file, rev):
636 636 io = StringIO()
637 637 # TODO: ra.get_file transmits the whole file instead of diffs.
638 638 mode = ''
639 639 try:
640 640 revnum = self.revnum(rev)
641 641 if self.module != self.modulemap[revnum]:
642 642 self.module = self.modulemap[revnum]
643 643 self.reparent(self.module)
644 644 info = svn.ra.get_file(self.ra, file, revnum, io)
645 645 if isinstance(info, list):
646 646 info = info[-1]
647 647 mode = ("svn:executable" in info) and 'x' or ''
648 648 mode = ("svn:special" in info) and 'l' or mode
649 649 except SubversionException, e:
650 650 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
651 651 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
652 652 if e.apr_err in notfound: # File not found
653 653 raise IOError()
654 654 raise
655 655 data = io.getvalue()
656 656 if mode == 'l':
657 657 link_prefix = "link "
658 658 if data.startswith(link_prefix):
659 659 data = data[len(link_prefix):]
660 660 return data, mode
661 661
662 662 def _find_children(self, path, revnum):
663 663 path = path.strip('/')
664 664 pool = Pool()
665 665 rpath = '/'.join([self.base, path]).strip('/')
666 666 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