hgext/convert/subversion.py
changeset 4795 09dae950919f
parent 4794 26857a6f9dd0
child 4796 83c1bbb934ec
equal deleted inserted replaced
4794:26857a6f9dd0 4795:09dae950919f
    24     import transport
    24     import transport
    25 except ImportError:
    25 except ImportError:
    26     pass
    26     pass
    27 
    27 
    28 class CompatibilityException(Exception): pass
    28 class CompatibilityException(Exception): pass
    29 
       
    30 LOG_BATCH_SIZE = 50
       
    31 
    29 
    32 class svn_entry(object):
    30 class svn_entry(object):
    33     """Emulate a Subversion path change."""
    31     """Emulate a Subversion path change."""
    34     __slots__ = ['path', 'copyfrom_path', 'copyfrom_rev', 'action']
    32     __slots__ = ['path', 'copyfrom_path', 'copyfrom_rev', 'action']
    35     def __init__(self, entry):
    33     def __init__(self, entry):
   104         self.url = url
   102         self.url = url
   105         self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
   103         self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
   106         try:
   104         try:
   107             self.transport = transport.SvnRaTransport(url = url)
   105             self.transport = transport.SvnRaTransport(url = url)
   108             self.ra = self.transport.ra
   106             self.ra = self.transport.ra
       
   107             self.ctx = svn.client.create_context()
   109             self.base = svn.ra.get_repos_root(self.ra)
   108             self.base = svn.ra.get_repos_root(self.ra)
   110             self.module = self.url[len(self.base):]
   109             self.module = self.url[len(self.base):]
   111             self.modulemap = {} # revision, module
   110             self.modulemap = {} # revision, module
   112             self.commits = {}
   111             self.commits = {}
   113             self.files = {}
   112             self.files = {}
   135     def revsplit(self, rev):
   134     def revsplit(self, rev):
   136         url, revnum = rev.encode(self.encoding).split('@', 1)
   135         url, revnum = rev.encode(self.encoding).split('@', 1)
   137         revnum = int(revnum)
   136         revnum = int(revnum)
   138         parts = url.split('/', 1)
   137         parts = url.split('/', 1)
   139         uuid = parts.pop(0)[4:]
   138         uuid = parts.pop(0)[4:]
   140         mod = '/'
   139         mod = ''
   141         if parts:
   140         if parts:
   142             mod += parts[0]
   141             mod = '/' + parts[0]
   143         return uuid, mod, revnum
   142         return uuid, mod, revnum
   144 
   143 
   145     def latest(self, path, stop=0):
   144     def latest(self, path, stop=0):
   146         'find the latest revision affecting path, up to stop'
   145         'find the latest revision affecting path, up to stop'
   147         if not stop:
   146         if not stop:
   180     def reparent(self, module):
   179     def reparent(self, module):
   181         svn_url = self.base + module
   180         svn_url = self.base + module
   182         self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
   181         self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
   183         svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
   182         svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
   184 
   183 
   185     def _fetch_revisions(self, from_revnum = 0, to_revnum = 347, module=None):
   184     def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
   186         def get_entry_from_path(path, module=self.module):
   185         def get_entry_from_path(path, module=self.module):
   187             # Given the repository url of this wc, say
   186             # Given the repository url of this wc, say
   188             #   "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
   187             #   "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
   189             # extract the "entry" portion (a relative path) from what
   188             # extract the "entry" portion (a relative path) from what
   190             # svn log --xml says, ie
   189             # svn log --xml says, ie
   209 
   208 
   210             if self.is_blacklisted(revnum):
   209             if self.is_blacklisted(revnum):
   211                 self.ui.note('skipping blacklisted revision %d\n' % revnum)
   210                 self.ui.note('skipping blacklisted revision %d\n' % revnum)
   212                 return
   211                 return
   213 
   212 
   214             self.ui.note("parsing revision %d\n" % revnum)
   213             self.ui.debug("parsing revision %d\n" % revnum)
   215            
   214            
   216             if orig_paths is None:
   215             if orig_paths is None:
   217                 self.ui.debug('revision %d has no entries\n' % revnum)
   216                 self.ui.debug('revision %d has no entries\n' % revnum)
   218                 return
   217                 return
   219 
   218 
   240                 if path == self.module: # Follow branching back in history
   239                 if path == self.module: # Follow branching back in history
   241                     ent = orig_paths[path]
   240                     ent = orig_paths[path]
   242                     if ent:
   241                     if ent:
   243                         if ent.copyfrom_path:
   242                         if ent.copyfrom_path:
   244                             # ent.copyfrom_rev may not be the actual last revision
   243                             # ent.copyfrom_rev may not be the actual last revision
   245                             prev = self.latest(ent.copyfrom_path, revnum)
   244                             prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
   246                             self.modulemap[prev] = ent.copyfrom_path
   245                             self.modulemap[prev] = ent.copyfrom_path
   247                             parents = [self.rev(prev, ent.copyfrom_path)]
   246                             parents = [self.rev(prev, ent.copyfrom_path)]
       
   247                             self.ui.note('found parent of branch %s at %d: %s\n' % \
       
   248                                          (self.module, prev, ent.copyfrom_path))
   248                         else:
   249                         else:
   249                             self.ui.debug("No copyfrom path, don't know what to do.\n")
   250                             self.ui.debug("No copyfrom path, don't know what to do.\n")
   250                             # Maybe it was added and there is no more history.
   251                             # Maybe it was added and there is no more history.
   251                 entrypath = get_entry_from_path(path, module=self.module)
   252                 entrypath = get_entry_from_path(path, module=self.module)
   252                 # self.ui.write("entrypath %s\n" % entrypath)
   253                 # self.ui.write("entrypath %s\n" % entrypath)
   271                                 copies[self.recode(entry)] = self.recode(copyfrom_path)
   272                                 copies[self.recode(entry)] = self.recode(copyfrom_path)
   272                     entries.append(self.recode(entry))
   273                     entries.append(self.recode(entry))
   273                 elif kind == 0: # gone, but had better be a deleted *file*
   274                 elif kind == 0: # gone, but had better be a deleted *file*
   274                     self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
   275                     self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
   275 
   276 
   276                     fromrev = revnum - 1
   277                     # if a branch is created but entries are removed in the same
   277                     # might always need to be revnum - 1 in these 3 lines?
   278                     # changeset, get the right fromrev
   278                     old_module = self.modulemap.get(fromrev, self.module)
   279                     if parents:
       
   280                         uuid, old_module, fromrev = self.revsplit(parents[0])
       
   281                     else:
       
   282                         fromrev = revnum - 1
       
   283                         # might always need to be revnum - 1 in these 3 lines?
       
   284                         old_module = self.modulemap.get(fromrev, self.module)
       
   285 
   279                     basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
   286                     basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
   280                     entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
   287                     entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
   281 
   288 
   282                     def lookup_parts(p):
   289                     def lookup_parts(p):
   283                         rc = None
   290                         rc = None
   284                         parts = p.split("/")
   291                         parts = p.split("/")
   285                         for i in range(len(parts)):
   292                         for i in range(len(parts)):
   286                             part = "/".join(parts[:i])
   293                             part = "/".join(parts[:i])
   287                             info = part, copyfrom.get(part, None)
   294                             info = part, copyfrom.get(part, None)
   288                             if info[1] is not None:
   295                             if info[1] is not None:
   289                                 self.ui.debug("Found parent directory %s\n" % info)
   296                                 self.ui.debug("Found parent directory %s\n" % info[1])
   290                                 rc = info
   297                                 rc = info
   291                         return rc
   298                         return rc
   292 
   299 
   293                     self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
   300                     self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
   294 
   301 
   425             self.commits[rev] = cset
   432             self.commits[rev] = cset
   426             if self.child_cset and not self.child_cset.parents:
   433             if self.child_cset and not self.child_cset.parents:
   427                 self.child_cset.parents = [rev]
   434                 self.child_cset.parents = [rev]
   428             self.child_cset = cset
   435             self.child_cset = cset
   429 
   436 
   430         self.ui.note('fetching revision log from %d to %d\n' % \
   437         self.ui.note('fetching revision log for "%s" from %d to %d\n' % \
   431                      (from_revnum, to_revnum))
   438                      (self.module, from_revnum, to_revnum))
   432 
   439 
   433         if module is None:
       
   434             module = self.module
       
   435         try:
   440         try:
   436             discover_changed_paths = True
   441             discover_changed_paths = True
   437             strict_node_history = False
   442             strict_node_history = False
   438             svn.ra.get_log(self.ra, [module], from_revnum, to_revnum, 0,
   443             svn.ra.get_log(self.ra, [self.module], from_revnum, to_revnum, 0,
   439                            discover_changed_paths, strict_node_history,
   444                            discover_changed_paths, strict_node_history,
   440                            parselogentry)
   445                            parselogentry)
   441             self.last_revnum = to_revnum
   446             self.last_revnum = to_revnum
   442         except SubversionException, (_, num):
   447         except SubversionException, (_, num):
   443             if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
   448             if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
   444                 raise NoSuchRevision(branch=self, 
   449                 raise NoSuchRevision(branch=self, 
   445                     revision="Revision number %d" % to_revnum)
   450                     revision="Revision number %d" % to_revnum)
   446             raise
   451             raise
   447 
   452 
   448     def getheads(self):
   453     def getheads(self):
   449         # svn-url@rev
   454         # detect standard /branches, /tags, /trunk layout
   450         # Not safe if someone committed:
   455         optrev = svn.core.svn_opt_revision_t()
   451         self.heads = [self.head]
   456         optrev.kind = svn.core.svn_opt_revision_number
   452         # print self.commits.keys()
   457         optrev.value.number = self.last_changed
       
   458         rpath = self.url.strip('/')
       
   459         paths = svn.client.ls(rpath, optrev, False, self.ctx)
       
   460         if 'branches' in paths and 'trunk' in paths:
       
   461             self.module += '/trunk'
       
   462             lt = self.latest(self.module, self.last_changed)
       
   463             self.head = self.rev(lt)
       
   464             self.heads = [self.head]
       
   465             branches = svn.client.ls(rpath + '/branches', optrev, False, self.ctx)
       
   466             for branch in branches.keys():
       
   467                 module = '/branches/' + branch
       
   468                 brevnum = self.latest(module, self.last_changed)
       
   469                 brev = self.rev(brevnum, module)
       
   470                 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
       
   471                 self.heads.append(brev)
       
   472         else:
       
   473             self.heads = [self.head]
   453         return self.heads
   474         return self.heads
   454 
   475 
   455     def _getfile(self, file, rev):
   476     def _getfile(self, file, rev):
   456         io = StringIO()
   477         io = StringIO()
   457         # TODO: ra.get_file transmits the whole file instead of diffs.
   478         # TODO: ra.get_file transmits the whole file instead of diffs.
   495         return cl
   516         return cl
   496 
   517 
   497     def getcommit(self, rev):
   518     def getcommit(self, rev):
   498         if rev not in self.commits:
   519         if rev not in self.commits:
   499             uuid, module, revnum = self.revsplit(rev)
   520             uuid, module, revnum = self.revsplit(rev)
   500             minrev = revnum - LOG_BATCH_SIZE > 0 and revnum - LOG_BATCH_SIZE or 0
   521             self.module = module
   501             self._fetch_revisions(from_revnum=revnum, to_revnum=0,
   522             self.reparent(module)
   502                                   module=module)
   523             self._fetch_revisions(from_revnum=revnum, to_revnum=0)
   503         return self.commits[rev]
   524         return self.commits[rev]
   504 
   525 
   505     def gettags(self):
   526     def gettags(self):
   506         tags = {}
   527         tags = {}
   507         def parselogentry(*arg, **args):
   528         def parselogentry(*arg, **args):
   526     def _find_children(self, path, revnum):
   547     def _find_children(self, path, revnum):
   527         path = path.strip("/")
   548         path = path.strip("/")
   528 
   549 
   529         def _find_children_fallback(path, revnum):
   550         def _find_children_fallback(path, revnum):
   530             # SWIG python bindings for getdir are broken up to at least 1.4.3
   551             # SWIG python bindings for getdir are broken up to at least 1.4.3
   531             if not hasattr(self, 'client_ctx'):
       
   532                 self.client_ctx = svn.client.create_context()
       
   533             pool = Pool()
   552             pool = Pool()
   534             optrev = svn.core.svn_opt_revision_t()
   553             optrev = svn.core.svn_opt_revision_t()
   535             optrev.kind = svn.core.svn_opt_revision_number
   554             optrev.kind = svn.core.svn_opt_revision_number
   536             optrev.value.number = revnum
   555             optrev.value.number = revnum
   537             rpath = '/'.join([self.base, path]).strip('/')
   556             rpath = '/'.join([self.base, path]).strip('/')
   538             return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev, True, self.client_ctx, pool).keys()]
   557             return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev, True, self.ctx, pool).keys()]
   539 
   558 
   540         if hasattr(self, '_find_children_fallback'):
   559         if hasattr(self, '_find_children_fallback'):
   541             return _find_children_fallback(path, revnum)
   560             return _find_children_fallback(path, revnum)
   542 
   561 
   543         self.reparent("/" + path)
   562         self.reparent("/" + path)