hgext/convert/subversion.py
changeset 6549 2af1b9de62b3
parent 6497 a0068c673de7
parent 6546 075b2c9aed37
child 6557 f2bd49752f0d
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"