Mercurial > hg
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" |