hgext/convert/subversion.py
changeset 4928 e8f4e40f285a
parent 4926 bbf1c1e89892
child 4931 2f0f9528e77b
equal deleted inserted replaced
4927:382520bacc17 4928:e8f4e40f285a
    13 #
    13 #
    14 #   hg convert --config convert.svn.trunk=wackoname [...]
    14 #   hg convert --config convert.svn.trunk=wackoname [...]
    15 
    15 
    16 import pprint
    16 import pprint
    17 import locale
    17 import locale
    18 
    18 import os
       
    19 import cPickle as pickle
    19 from mercurial import util
    20 from mercurial import util
    20 
    21 
    21 # Subversion stuff. Works best with very recent Python SVN bindings
    22 # Subversion stuff. Works best with very recent Python SVN bindings
    22 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
    23 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
    23 # these bindings.
    24 # these bindings.
    35     import transport
    36     import transport
    36 except ImportError:
    37 except ImportError:
    37     pass
    38     pass
    38 
    39 
    39 class CompatibilityException(Exception): pass
    40 class CompatibilityException(Exception): pass
       
    41 
       
    42 class changedpath(object):
       
    43     def __init__(self, p):
       
    44         self.copyfrom_path = p.copyfrom_path
       
    45         self.copyfrom_rev = p.copyfrom_rev
       
    46         self.action = p.action
    40 
    47 
    41 # SVN conversion code stolen from bzr-svn and tailor
    48 # SVN conversion code stolen from bzr-svn and tailor
    42 class convert_svn(converter_source):
    49 class convert_svn(converter_source):
    43     def __init__(self, ui, url, rev=None):
    50     def __init__(self, ui, url, rev=None):
    44         super(convert_svn, self).__init__(ui, url, rev=rev)
    51         super(convert_svn, self).__init__(ui, url, rev=rev)
    69         except ValueError, e:
    76         except ValueError, e:
    70             pass
    77             pass
    71         self.url = url
    78         self.url = url
    72         self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
    79         self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
    73         try:
    80         try:
    74             self.transport = transport.SvnRaTransport(url = url)
    81             self.transport = transport.SvnRaTransport(url=url)
    75             self.ra = self.transport.ra
    82             self.ra = self.transport.ra
    76             self.ctx = svn.client.create_context()
    83             self.ctx = self.transport.client
    77             self.base = svn.ra.get_repos_root(self.ra)
    84             self.base = svn.ra.get_repos_root(self.ra)
    78             self.module = self.url[len(self.base):]
    85             self.module = self.url[len(self.base):]
    79             self.modulemap = {} # revision, module
    86             self.modulemap = {} # revision, module
    80             self.commits = {}
    87             self.commits = {}
    81             self.files = {}
    88             self.files = {}
   172         commit = self.commits[rev]
   179         commit = self.commits[rev]
   173         # caller caches the result, so free it here to release memory
   180         # caller caches the result, so free it here to release memory
   174         del self.commits[rev]
   181         del self.commits[rev]
   175         return commit
   182         return commit
   176 
   183 
       
   184     def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
       
   185                 strict_node_history=False):
       
   186         '''wrapper for svn.ra.get_log.
       
   187         on a large repository, svn.ra.get_log pins huge amounts of
       
   188         memory that cannot be recovered.  work around it by forking
       
   189         and writing results over a pipe.'''
       
   190 
       
   191         def child(fp):
       
   192             protocol = -1
       
   193             def receiver(orig_paths, revnum, author, date, message, pool):
       
   194                 if orig_paths is not None:
       
   195                     for k, v in orig_paths.iteritems():
       
   196                         orig_paths[k] = changedpath(v)
       
   197                 pickle.dump((orig_paths, revnum, author, date, message),
       
   198                             fp, protocol)
       
   199 
       
   200             try:
       
   201                 # Use an ra of our own so that our parent can consume
       
   202                 # our results without confusing the server.
       
   203                 t = transport.SvnRaTransport(url=self.url)
       
   204                 svn.ra.get_log(t.ra, paths, start, end, limit,
       
   205                                discover_changed_paths,
       
   206                                strict_node_history,
       
   207                                receiver)
       
   208             except SubversionException, (_, num):
       
   209                 pickle.dump(num, fp, protocol)
       
   210             else:
       
   211                 pickle.dump(None, fp, protocol)
       
   212             fp.close()
       
   213 
       
   214         def parent(fp):
       
   215             while True:
       
   216                 entry = pickle.load(fp)
       
   217                 try:
       
   218                     orig_paths, revnum, author, date, message = entry
       
   219                 except:
       
   220                     if entry is None:
       
   221                         break
       
   222                     raise SubversionException("child raised exception", entry)
       
   223                 yield entry
       
   224 
       
   225         rfd, wfd = os.pipe()
       
   226         pid = os.fork()
       
   227         if pid:
       
   228             os.close(wfd)
       
   229             for p in parent(os.fdopen(rfd, 'rb')):
       
   230                 yield p
       
   231             ret = os.waitpid(pid, 0)[1]
       
   232             if ret:
       
   233                 raise util.Abort(_('get_log %s') % util.explain_exit(ret))
       
   234         else:
       
   235             os.close(rfd)
       
   236             child(os.fdopen(wfd, 'wb'))
       
   237             os._exit(0)
       
   238 
   177     def gettags(self):
   239     def gettags(self):
   178         tags = {}
   240         tags = {}
   179         def parselogentry(*arg, **args):
   241         for entry in self.get_log(['/tags'], 0, self.revnum(self.head)):
   180             orig_paths, revnum, author, date, message, pool = arg
   242             orig_paths, revnum, author, date, message = entry
   181             for path in orig_paths:
   243             for path in orig_paths:
   182                 if not path.startswith('/tags/'):
   244                 if not path.startswith('/tags/'):
   183                     continue
   245                     continue
   184                 ent = orig_paths[path]
   246                 ent = orig_paths[path]
   185                 source = ent.copyfrom_path
   247                 source = ent.copyfrom_path
   186                 rev = ent.copyfrom_rev
   248                 rev = ent.copyfrom_rev
   187                 tag = path.split('/', 2)[2]
   249                 tag = path.split('/', 2)[2]
   188                 tags[tag] = self.revid(rev, module=source)
   250                 tags[tag] = self.revid(rev, module=source)
   189 
   251         return tags
   190         start = self.revnum(self.head)
       
   191         try:
       
   192             svn.ra.get_log(self.ra, ['/tags'], 0, start, 0, True, False,
       
   193                            parselogentry)
       
   194             return tags
       
   195         except SubversionException:
       
   196             self.ui.note('no tags found at revision %d\n' % start)
       
   197             return {}
       
   198 
   252 
   199     # -- helper functions --
   253     # -- helper functions --
   200 
   254 
   201     def revid(self, revnum, module=None):
   255     def revid(self, revnum, module=None):
   202         if not module:
   256         if not module:
   274 
   328 
   275             # The path is outside our tracked tree...
   329             # The path is outside our tracked tree...
   276             self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module))
   330             self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module))
   277             return None
   331             return None
   278 
   332 
   279         received = []
       
   280         # svn.ra.get_log requires no other calls to the ra until it completes,
       
   281         # so we just collect the log entries and parse them afterwards
       
   282         def receivelog(orig_paths, revnum, author, date, message, pool):
       
   283             if self.is_blacklisted(revnum):
       
   284                 self.ui.note('skipping blacklisted revision %d\n' % revnum)
       
   285                 return
       
   286            
       
   287             if orig_paths is None:
       
   288                 self.ui.debug('revision %d has no entries\n' % revnum)
       
   289                 return
       
   290 
       
   291             received.append((revnum, orig_paths.items(), author, date, message))
       
   292 
       
   293         self.child_cset = None
   333         self.child_cset = None
   294         def parselogentry((revnum, orig_paths, author, date, message)):
   334         def parselogentry(orig_paths, revnum, author, date, message):
   295             self.ui.debug("parsing revision %d\n" % revnum)
   335             self.ui.debug("parsing revision %d (%d changes)\n" %
       
   336                           (revnum, len(orig_paths)))
   296 
   337 
   297             if revnum in self.modulemap:
   338             if revnum in self.modulemap:
   298                 new_module = self.modulemap[revnum]
   339                 new_module = self.modulemap[revnum]
   299                 if new_module != self.module:
   340                 if new_module != self.module:
   300                     self.module = new_module
   341                     self.module = new_module
   316                 if branch == 'trunk':
   357                 if branch == 'trunk':
   317                     branch = ''
   358                     branch = ''
   318             except IndexError:
   359             except IndexError:
   319                 branch = None
   360                 branch = None
   320 
   361 
       
   362             orig_paths = orig_paths.items()
   321             orig_paths.sort()
   363             orig_paths.sort()
   322             for path, ent in orig_paths:
   364             for path, ent in orig_paths:
   323                 # self.ui.write("path %s\n" % path)
   365                 # self.ui.write("path %s\n" % path)
   324                 if path == self.module: # Follow branching back in history
   366                 if path == self.module: # Follow branching back in history
   325                     if ent:
   367                     if ent:
   525                      (self.module, from_revnum, to_revnum))
   567                      (self.module, from_revnum, to_revnum))
   526 
   568 
   527         try:
   569         try:
   528             discover_changed_paths = True
   570             discover_changed_paths = True
   529             strict_node_history = False
   571             strict_node_history = False
   530             svn.ra.get_log(self.ra, [self.module], from_revnum, to_revnum, 0,
   572             for entry in self.get_log([self.module], from_revnum, to_revnum):
   531                            discover_changed_paths, strict_node_history,
   573                 orig_paths, revnum, author, date, message = entry
   532                            receivelog)
   574                 if self.is_blacklisted(revnum):
   533             self.ui.note('parsing %d log entries for "%s"\n' %
   575                     self.ui.note('skipping blacklisted revision %d\n' % revnum)
   534                          (len(received), self.module))
   576                     continue
   535             for entry in received:
   577                 if orig_paths is None:
   536                 parselogentry(entry)
   578                     self.ui.debug('revision %d has no entries\n' % revnum)
       
   579                     continue
       
   580                 parselogentry(orig_paths, revnum, author, date, message)
   537         except SubversionException, (_, num):
   581         except SubversionException, (_, num):
   538             if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
   582             if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
   539                 raise NoSuchRevision(branch=self, 
   583                 raise NoSuchRevision(branch=self, 
   540                     revision="Revision number %d" % to_revnum)
   584                     revision="Revision number %d" % to_revnum)
   541             raise
   585             raise