131 self.transport = transport.SvnRaTransport(url=self.url) |
131 self.transport = transport.SvnRaTransport(url=self.url) |
132 self.ra = self.transport.ra |
132 self.ra = self.transport.ra |
133 self.ctx = self.transport.client |
133 self.ctx = self.transport.client |
134 self.base = svn.ra.get_repos_root(self.ra) |
134 self.base = svn.ra.get_repos_root(self.ra) |
135 self.module = self.url[len(self.base):] |
135 self.module = self.url[len(self.base):] |
136 self.modulemap = {} # revision, module |
|
137 self.commits = {} |
136 self.commits = {} |
138 self.paths = {} |
137 self.paths = {} |
139 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding) |
138 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding) |
140 except SubversionException, e: |
139 except SubversionException, e: |
141 ui.print_exc() |
140 ui.print_exc() |
398 return None |
397 return None |
399 |
398 |
400 entries = [] |
399 entries = [] |
401 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions. |
400 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions. |
402 copies = {} |
401 copies = {} |
403 revnum = self.revnum(rev) |
402 |
404 |
403 new_module, revnum = self.revsplit(rev)[1:] |
405 if revnum in self.modulemap: |
404 if new_module != self.module: |
406 new_module = self.modulemap[revnum] |
405 self.module = new_module |
407 if new_module != self.module: |
406 self.reparent(self.module) |
408 self.module = new_module |
|
409 self.reparent(self.module) |
|
410 |
407 |
411 for path, ent in paths: |
408 for path, ent in paths: |
412 entrypath = get_entry_from_path(path, module=self.module) |
409 entrypath = get_entry_from_path(path, module=self.module) |
413 entry = entrypath.decode(self.encoding) |
410 entry = entrypath.decode(self.encoding) |
414 |
411 |
430 elif kind == 0: # gone, but had better be a deleted *file* |
427 elif kind == 0: # gone, but had better be a deleted *file* |
431 self.ui.debug("gone from %s\n" % ent.copyfrom_rev) |
428 self.ui.debug("gone from %s\n" % ent.copyfrom_rev) |
432 |
429 |
433 # if a branch is created but entries are removed in the same |
430 # if a branch is created but entries are removed in the same |
434 # changeset, get the right fromrev |
431 # changeset, get the right fromrev |
435 if parents: |
432 # parents cannot be empty here, you cannot remove things from |
436 uuid, old_module, fromrev = self.revsplit(parents[0]) |
433 # a root revision. |
437 else: |
434 uuid, old_module, fromrev = self.revsplit(parents[0]) |
438 fromrev = revnum - 1 |
|
439 # might always need to be revnum - 1 in these 3 lines? |
|
440 old_module = self.modulemap.get(fromrev, self.module) |
|
441 |
435 |
442 basepath = old_module + "/" + get_entry_from_path(path, module=self.module) |
436 basepath = old_module + "/" + get_entry_from_path(path, module=self.module) |
443 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module) |
437 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module) |
444 |
438 |
445 def lookup_parts(p): |
439 def lookup_parts(p): |
575 if from_revnum < to_revnum: |
569 if from_revnum < to_revnum: |
576 from_revnum, to_revnum = to_revnum, from_revnum |
570 from_revnum, to_revnum = to_revnum, from_revnum |
577 |
571 |
578 self.child_cset = None |
572 self.child_cset = None |
579 def parselogentry(orig_paths, revnum, author, date, message): |
573 def parselogentry(orig_paths, revnum, author, date, message): |
|
574 """Return the parsed commit object or None, and True if |
|
575 the revision is a branch root. |
|
576 """ |
580 self.ui.debug("parsing revision %d (%d changes)\n" % |
577 self.ui.debug("parsing revision %d (%d changes)\n" % |
581 (revnum, len(orig_paths))) |
578 (revnum, len(orig_paths))) |
582 |
579 |
583 if revnum in self.modulemap: |
|
584 new_module = self.modulemap[revnum] |
|
585 if new_module != self.module: |
|
586 self.module = new_module |
|
587 self.reparent(self.module) |
|
588 |
|
589 rev = self.revid(revnum) |
580 rev = self.revid(revnum) |
590 # branch log might return entries for a parent we already have |
581 # branch log might return entries for a parent we already have |
591 |
582 |
592 if (rev in self.commits or revnum < to_revnum): |
583 if (rev in self.commits or revnum < to_revnum): |
593 return None |
584 return None, False |
594 |
585 |
595 parents = [] |
586 parents = [] |
596 # check whether this revision is the start of a branch |
587 # check whether this revision is the start of a branch |
597 if self.module in orig_paths: |
588 if self.module in orig_paths: |
598 ent = orig_paths[self.module] |
589 ent = orig_paths[self.module] |
599 if ent.copyfrom_path: |
590 if ent.copyfrom_path: |
600 # ent.copyfrom_rev may not be the actual last revision |
591 # ent.copyfrom_rev may not be the actual last revision |
601 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev) |
592 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev) |
602 self.modulemap[prev] = ent.copyfrom_path |
|
603 parents = [self.revid(prev, ent.copyfrom_path)] |
593 parents = [self.revid(prev, ent.copyfrom_path)] |
604 self.ui.note('found parent of branch %s at %d: %s\n' % \ |
594 self.ui.note('found parent of branch %s at %d: %s\n' % \ |
605 (self.module, prev, ent.copyfrom_path)) |
595 (self.module, prev, ent.copyfrom_path)) |
606 else: |
596 else: |
607 self.ui.debug("No copyfrom path, don't know what to do.\n") |
597 self.ui.debug("No copyfrom path, don't know what to do.\n") |
608 |
|
609 self.modulemap[revnum] = self.module # track backwards in time |
|
610 |
598 |
611 orig_paths = orig_paths.items() |
599 orig_paths = orig_paths.items() |
612 orig_paths.sort() |
600 orig_paths.sort() |
613 paths = [] |
601 paths = [] |
614 # filter out unrelated paths |
602 # filter out unrelated paths |
616 if not path.startswith(self.module): |
604 if not path.startswith(self.module): |
617 self.ui.debug("boring@%s: %s\n" % (revnum, path)) |
605 self.ui.debug("boring@%s: %s\n" % (revnum, path)) |
618 continue |
606 continue |
619 paths.append((path, ent)) |
607 paths.append((path, ent)) |
620 |
608 |
621 self.paths[rev] = (paths, parents) |
|
622 |
|
623 # Example SVN datetime. Includes microseconds. |
609 # Example SVN datetime. Includes microseconds. |
624 # ISO-8601 conformant |
610 # ISO-8601 conformant |
625 # '2007-01-04T17:35:00.902377Z' |
611 # '2007-01-04T17:35:00.902377Z' |
626 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"]) |
612 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"]) |
627 |
613 |
640 parents=parents, |
626 parents=parents, |
641 branch=branch, |
627 branch=branch, |
642 rev=rev.encode('utf-8')) |
628 rev=rev.encode('utf-8')) |
643 |
629 |
644 self.commits[rev] = cset |
630 self.commits[rev] = cset |
|
631 # The parents list is *shared* among self.paths and the |
|
632 # commit object. Both will be updated below. |
|
633 self.paths[rev] = (paths, cset.parents) |
645 if self.child_cset and not self.child_cset.parents: |
634 if self.child_cset and not self.child_cset.parents: |
646 self.child_cset.parents = [rev] |
635 self.child_cset.parents[:] = [rev] |
647 self.child_cset = cset |
636 self.child_cset = cset |
648 return cset |
637 return cset, len(parents) > 0 |
649 |
638 |
650 self.ui.note('fetching revision log for "%s" from %d to %d\n' % |
639 self.ui.note('fetching revision log for "%s" from %d to %d\n' % |
651 (self.module, from_revnum, to_revnum)) |
640 (self.module, from_revnum, to_revnum)) |
652 |
641 |
653 try: |
642 try: |
654 firstcset = None |
643 firstcset = None |
|
644 branched = False |
655 for entry in self.get_log([self.module], from_revnum, to_revnum): |
645 for entry in self.get_log([self.module], from_revnum, to_revnum): |
|
646 if branched: |
|
647 # The iterator must be exhausted for the child process |
|
648 # to terminate cleanly. |
|
649 continue |
656 paths, revnum, author, date, message = entry |
650 paths, revnum, author, date, message = entry |
657 if self.is_blacklisted(revnum): |
651 if self.is_blacklisted(revnum): |
658 self.ui.note('skipping blacklisted revision %d\n' % revnum) |
652 self.ui.note('skipping blacklisted revision %d\n' % revnum) |
659 continue |
653 continue |
660 if paths is None: |
654 if paths is None: |
661 self.ui.debug('revision %d has no entries\n' % revnum) |
655 self.ui.debug('revision %d has no entries\n' % revnum) |
662 continue |
656 continue |
663 cset = parselogentry(paths, revnum, author, date, message) |
657 cset, branched = parselogentry(paths, revnum, author, |
|
658 date, message) |
664 if cset: |
659 if cset: |
665 firstcset = cset |
660 firstcset = cset |
666 |
661 |
667 if firstcset and not firstcset.parents: |
662 if firstcset and not firstcset.parents: |
668 # The first revision of the sequence (the last fetched one) |
663 # The first revision of the sequence (the last fetched one) |
684 def _getfile(self, file, rev): |
679 def _getfile(self, file, rev): |
685 io = StringIO() |
680 io = StringIO() |
686 # TODO: ra.get_file transmits the whole file instead of diffs. |
681 # TODO: ra.get_file transmits the whole file instead of diffs. |
687 mode = '' |
682 mode = '' |
688 try: |
683 try: |
689 revnum = self.revnum(rev) |
684 new_module, revnum = self.revsplit(rev)[1:] |
690 if self.module != self.modulemap[revnum]: |
685 if self.module != new_module: |
691 self.module = self.modulemap[revnum] |
686 self.module = new_module |
692 self.reparent(self.module) |
687 self.reparent(self.module) |
693 info = svn.ra.get_file(self.ra, file, revnum, io) |
688 info = svn.ra.get_file(self.ra, file, revnum, io) |
694 if isinstance(info, list): |
689 if isinstance(info, list): |
695 info = info[-1] |
690 info = info[-1] |
696 mode = ("svn:executable" in info) and 'x' or '' |
691 mode = ("svn:executable" in info) and 'x' or '' |