comparison hgext/convert/subversion.py @ 6549:2af1b9de62b3

Merge with crew-stable
author Patrick Mezard <pmezard@gmail.com>
date Mon, 14 Apr 2008 23:04:34 +0200
parents a0068c673de7 075b2c9aed37
children f2bd49752f0d
comparison
equal deleted inserted replaced
6548:962eb403165b 6549:2af1b9de62b3
183 try: 183 try:
184 self.transport = transport.SvnRaTransport(url=self.url) 184 self.transport = transport.SvnRaTransport(url=self.url)
185 self.ra = self.transport.ra 185 self.ra = self.transport.ra
186 self.ctx = self.transport.client 186 self.ctx = self.transport.client
187 self.base = svn.ra.get_repos_root(self.ra) 187 self.base = svn.ra.get_repos_root(self.ra)
188 # Module is either empty or a repository path starting with
189 # a slash and not ending with a slash.
188 self.module = self.url[len(self.base):] 190 self.module = self.url[len(self.base):]
189 self.rootmodule = self.module 191 self.rootmodule = self.module
190 self.commits = {} 192 self.commits = {}
191 self.paths = {} 193 self.paths = {}
192 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding) 194 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
533 svn_url = self.base + module 535 svn_url = self.base + module
534 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding)) 536 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
535 svn.ra.reparent(self.ra, svn_url.encode(self.encoding)) 537 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
536 538
537 def expandpaths(self, rev, paths, parents): 539 def expandpaths(self, rev, paths, parents):
538 def get_entry_from_path(path, module=self.module):
539 # Given the repository url of this wc, say
540 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
541 # extract the "entry" portion (a relative path) from what
542 # svn log --xml says, ie
543 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
544 # that is to say "tests/PloneTestCase.py"
545 if path.startswith(module):
546 relative = path[len(module):]
547 if relative.startswith('/'):
548 return relative[1:]
549 else:
550 return relative
551
552 # The path is outside our tracked tree...
553 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
554 return None
555
556 entries = [] 540 entries = []
557 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions. 541 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
558 copies = {} 542 copies = {}
559 543
560 new_module, revnum = self.revsplit(rev)[1:] 544 new_module, revnum = self.revsplit(rev)[1:]
561 if new_module != self.module: 545 if new_module != self.module:
562 self.module = new_module 546 self.module = new_module
563 self.reparent(self.module) 547 self.reparent(self.module)
564 548
565 for path, ent in paths: 549 for path, ent in paths:
566 entrypath = get_entry_from_path(path, module=self.module) 550 entrypath = self.getrelpath(path)
567 entry = entrypath.decode(self.encoding) 551 entry = entrypath.decode(self.encoding)
568 552
569 kind = svn.ra.check_path(self.ra, entrypath, revnum) 553 kind = svn.ra.check_path(self.ra, entrypath, revnum)
570 if kind == svn.core.svn_node_file: 554 if kind == svn.core.svn_node_file:
571 if ent.copyfrom_path:
572 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
573 if copyfrom_path:
574 self.ui.debug("Copied to %s from %s@%s\n" %
575 (entrypath, copyfrom_path,
576 ent.copyfrom_rev))
577 # It's probably important for hg that the source
578 # exists in the revision's parent, not just the
579 # ent.copyfrom_rev
580 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
581 if fromkind != 0:
582 copies[self.recode(entry)] = self.recode(copyfrom_path)
583 entries.append(self.recode(entry)) 555 entries.append(self.recode(entry))
556 if not ent.copyfrom_path or not parents:
557 continue
558 # Copy sources not in parent revisions cannot be represented,
559 # ignore their origin for now
560 pmodule, prevnum = self.revsplit(parents[0])[1:]
561 if ent.copyfrom_rev < prevnum:
562 continue
563 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
564 if not copyfrom_path:
565 continue
566 self.ui.debug("copied to %s from %s@%s\n" %
567 (entrypath, copyfrom_path, ent.copyfrom_rev))
568 copies[self.recode(entry)] = self.recode(copyfrom_path)
584 elif kind == 0: # gone, but had better be a deleted *file* 569 elif kind == 0: # gone, but had better be a deleted *file*
585 self.ui.debug("gone from %s\n" % ent.copyfrom_rev) 570 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
586 571
587 # if a branch is created but entries are removed in the same 572 # if a branch is created but entries are removed in the same
588 # changeset, get the right fromrev 573 # changeset, get the right fromrev
589 # parents cannot be empty here, you cannot remove things from 574 # parents cannot be empty here, you cannot remove things from
590 # a root revision. 575 # a root revision.
591 uuid, old_module, fromrev = self.revsplit(parents[0]) 576 uuid, old_module, fromrev = self.revsplit(parents[0])
592 577
593 basepath = old_module + "/" + get_entry_from_path(path, module=self.module) 578 basepath = old_module + "/" + self.getrelpath(path)
594 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module) 579 entrypath = basepath
595 580
596 def lookup_parts(p): 581 def lookup_parts(p):
597 rc = None 582 rc = None
598 parts = p.split("/") 583 parts = p.split("/")
599 for i in range(len(parts)): 584 for i in range(len(parts)):
645 for child in children: 630 for child in children:
646 # Can we move a child directory and its 631 # Can we move a child directory and its
647 # parent in the same commit? (probably can). Could 632 # parent in the same commit? (probably can). Could
648 # cause problems if instead of revnum -1, 633 # cause problems if instead of revnum -1,
649 # we have to look in (copyfrom_path, revnum - 1) 634 # we have to look in (copyfrom_path, revnum - 1)
650 entrypath = get_entry_from_path("/" + child, module=old_module) 635 entrypath = self.getrelpath("/" + child, module=old_module)
651 if entrypath: 636 if entrypath:
652 entry = self.recode(entrypath.decode(self.encoding)) 637 entry = self.recode(entrypath.decode(self.encoding))
653 if entry in copies: 638 if entry in copies:
654 # deleted file within a copy 639 # deleted file within a copy
655 del copies[entry] 640 del copies[entry]
678 for child in children: 663 for child in children:
679 # Can we move a child directory and its 664 # Can we move a child directory and its
680 # parent in the same commit? (probably can). Could 665 # parent in the same commit? (probably can). Could
681 # cause problems if instead of revnum -1, 666 # cause problems if instead of revnum -1,
682 # we have to look in (copyfrom_path, revnum - 1) 667 # we have to look in (copyfrom_path, revnum - 1)
683 entrypath = get_entry_from_path("/" + child, module=self.module) 668 entrypath = self.getrelpath("/" + child)
684 # print child, self.module, entrypath 669 # print child, self.module, entrypath
685 if entrypath: 670 if entrypath:
686 # Need to filter out directories here... 671 # Need to filter out directories here...
687 kind = svn.ra.check_path(self.ra, entrypath, revnum) 672 kind = svn.ra.check_path(self.ra, entrypath, revnum)
688 if kind != svn.core.svn_node_dir: 673 if kind != svn.core.svn_node_dir:
689 entries.append(self.recode(entrypath)) 674 entries.append(self.recode(entrypath))
690 675
691 # Copies here (must copy all from source) 676 # Copies here (must copy all from source)
692 # Probably not a real problem for us if 677 # Probably not a real problem for us if
693 # source does not exist 678 # source does not exist
694 679 if not ent.copyfrom_path or not parents:
695 # Can do this with the copy command "hg copy" 680 continue
696 # if ent.copyfrom_path: 681 # Copy sources not in parent revisions cannot be represented,
697 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding), 682 # ignore their origin for now
698 # module=self.module) 683 pmodule, prevnum = self.revsplit(parents[0])[1:]
699 # copyto_entry = entrypath 684 if ent.copyfrom_rev < prevnum:
700 # 685 continue
701 # print "copy directory", copyfrom_entry, 'to', copyto_entry 686 copyfrompath = ent.copyfrom_path.decode(self.encoding)
702 # 687 copyfrompath = self.getrelpath(copyfrompath, pmodule)
703 # copies.append((copyfrom_entry, copyto_entry)) 688 if not copyfrompath:
704 689 continue
705 if ent.copyfrom_path: 690 copyfrom[path] = ent
706 copyfrom_path = ent.copyfrom_path.decode(self.encoding) 691 self.ui.debug("mark %s came from %s:%d\n"
707 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module) 692 % (path, copyfrompath, ent.copyfrom_rev))
708 if copyfrom_entry: 693 children = self._find_children(ent.copyfrom_path, ent.copyfrom_rev)
709 copyfrom[path] = ent 694 children.sort()
710 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path])) 695 for child in children:
711 696 entrypath = self.getrelpath("/" + child, pmodule)
712 # Good, /probably/ a regular copy. Really should check 697 if not entrypath:
713 # to see whether the parent revision actually contains 698 continue
714 # the directory in question. 699 entry = entrypath.decode(self.encoding)
715 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev) 700 copytopath = path + entry[len(copyfrompath):]
716 children.sort() 701 copytopath = self.getrelpath(copytopath)
717 for child in children: 702 copies[self.recode(copytopath)] = self.recode(entry, pmodule)
718 entrypath = get_entry_from_path("/" + child, module=self.module)
719 if entrypath:
720 entry = entrypath.decode(self.encoding)
721 # print "COPY COPY From", copyfrom_entry, entry
722 copyto_path = path + entry[len(copyfrom_entry):]
723 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
724 # print "COPY", entry, "COPY To", copyto_entry
725 copies[self.recode(copyto_entry)] = self.recode(entry)
726 # copy from quux splort/quuxfile
727 703
728 return (util.unique(entries), copies) 704 return (util.unique(entries), copies)
729 705
730 def _fetch_revisions(self, from_revnum, to_revnum): 706 def _fetch_revisions(self, from_revnum, to_revnum):
731 if from_revnum < to_revnum: 707 if from_revnum < to_revnum:
732 from_revnum, to_revnum = to_revnum, from_revnum 708 from_revnum, to_revnum = to_revnum, from_revnum
733 709
734 self.child_cset = None 710 self.child_cset = None
711
712 def isdescendantof(parent, child):
713 if not child or not parent or not child.startswith(parent):
714 return False
715 subpath = child[len(parent):]
716 return len(subpath) > 1 and subpath[0] == '/'
717
735 def parselogentry(orig_paths, revnum, author, date, message): 718 def parselogentry(orig_paths, revnum, author, date, message):
736 """Return the parsed commit object or None, and True if 719 """Return the parsed commit object or None, and True if
737 the revision is a branch root. 720 the revision is a branch root.
738 """ 721 """
739 self.ui.debug("parsing revision %d (%d changes)\n" % 722 self.ui.debug("parsing revision %d (%d changes)\n" %
753 orig_paths.sort() 736 orig_paths.sort()
754 root_paths = [(p,e) for p,e in orig_paths if self.module.startswith(p)] 737 root_paths = [(p,e) for p,e in orig_paths if self.module.startswith(p)]
755 if root_paths: 738 if root_paths:
756 path, ent = root_paths[-1] 739 path, ent = root_paths[-1]
757 if ent.copyfrom_path: 740 if ent.copyfrom_path:
741 # If dir was moved while one of its file was removed
742 # the log may look like:
743 # A /dir (from /dir:x)
744 # A /dir/a (from /dir/a:y)
745 # A /dir/b (from /dir/b:z)
746 # ...
747 # for all remaining children.
748 # Let's take the highest child element from rev as source.
749 copies = [(p,e) for p,e in orig_paths[:-1]
750 if isdescendantof(ent.copyfrom_path, e.copyfrom_path)]
751 fromrev = max([e.copyfrom_rev for p,e in copies] + [ent.copyfrom_rev])
758 branched = True 752 branched = True
759 newpath = ent.copyfrom_path + self.module[len(path):] 753 newpath = ent.copyfrom_path + self.module[len(path):]
760 # ent.copyfrom_rev may not be the actual last revision 754 # ent.copyfrom_rev may not be the actual last revision
761 previd = self.latest(newpath, ent.copyfrom_rev) 755 previd = self.latest(newpath, fromrev)
762 if previd is not None: 756 if previd is not None:
763 prevmodule, prevnum = self.revsplit(previd)[1:] 757 prevmodule, prevnum = self.revsplit(previd)[1:]
764 if prevnum >= self.startrev: 758 if prevnum >= self.startrev:
765 parents = [previd] 759 parents = [previd]
766 self.ui.note('found parent of branch %s at %d: %s\n' % 760 self.ui.note('found parent of branch %s at %d: %s\n' %
769 self.ui.debug("No copyfrom path, don't know what to do.\n") 763 self.ui.debug("No copyfrom path, don't know what to do.\n")
770 764
771 paths = [] 765 paths = []
772 # filter out unrelated paths 766 # filter out unrelated paths
773 for path, ent in orig_paths: 767 for path, ent in orig_paths:
774 if not path.startswith(self.module): 768 if self.getrelpath(path) is None:
775 self.ui.debug("boring@%s: %s\n" % (revnum, path))
776 continue 769 continue
777 paths.append((path, ent)) 770 paths.append((path, ent))
778 771
779 # Example SVN datetime. Includes microseconds. 772 # Example SVN datetime. Includes microseconds.
780 # ISO-8601 conformant 773 # ISO-8601 conformant
883 path = path.strip('/') 876 path = path.strip('/')
884 pool = Pool() 877 pool = Pool()
885 rpath = '/'.join([self.base, path]).strip('/') 878 rpath = '/'.join([self.base, path]).strip('/')
886 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()] 879 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
887 880
881 def getrelpath(self, path, module=None):
882 if module is None:
883 module = self.module
884 # Given the repository url of this wc, say
885 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
886 # extract the "entry" portion (a relative path) from what
887 # svn log --xml says, ie
888 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
889 # that is to say "tests/PloneTestCase.py"
890 if path.startswith(module):
891 relative = path.rstrip('/')[len(module):]
892 if relative.startswith('/'):
893 return relative[1:]
894 elif relative == '':
895 return relative
896
897 # The path is outside our tracked tree...
898 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
899 return None
900
888 pre_revprop_change = '''#!/bin/sh 901 pre_revprop_change = '''#!/bin/sh
889 902
890 REPOS="$1" 903 REPOS="$1"
891 REV="$2" 904 REV="$2"
892 USER="$3" 905 USER="$3"