hgext/convert/subversion.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43085 eef9a2d67051
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    52     import svn.delta
    52     import svn.delta
    53     from . import transport
    53     from . import transport
    54     import warnings
    54     import warnings
    55 
    55 
    56     warnings.filterwarnings(
    56     warnings.filterwarnings(
    57         'ignore', module='svn.core', category=DeprecationWarning
    57         b'ignore', module=b'svn.core', category=DeprecationWarning
    58     )
    58     )
    59     svn.core.SubversionException  # trigger import to catch error
    59     svn.core.SubversionException  # trigger import to catch error
    60 
    60 
    61 except ImportError:
    61 except ImportError:
    62     svn = None
    62     svn = None
    78     >>> revsplit(b'7')
    78     >>> revsplit(b'7')
    79     ('', '', 0)
    79     ('', '', 0)
    80     >>> revsplit(b'bad')
    80     >>> revsplit(b'bad')
    81     ('', '', 0)
    81     ('', '', 0)
    82     """
    82     """
    83     parts = rev.rsplit('@', 1)
    83     parts = rev.rsplit(b'@', 1)
    84     revnum = 0
    84     revnum = 0
    85     if len(parts) > 1:
    85     if len(parts) > 1:
    86         revnum = int(parts[1])
    86         revnum = int(parts[1])
    87     parts = parts[0].split('/', 1)
    87     parts = parts[0].split(b'/', 1)
    88     uuid = ''
    88     uuid = b''
    89     mod = ''
    89     mod = b''
    90     if len(parts) > 1 and parts[0].startswith('svn:'):
    90     if len(parts) > 1 and parts[0].startswith(b'svn:'):
    91         uuid = parts[0][4:]
    91         uuid = parts[0][4:]
    92         mod = '/' + parts[1]
    92         mod = b'/' + parts[1]
    93     return uuid, mod, revnum
    93     return uuid, mod, revnum
    94 
    94 
    95 
    95 
    96 def quote(s):
    96 def quote(s):
    97     # As of svn 1.7, many svn calls expect "canonical" paths. In
    97     # As of svn 1.7, many svn calls expect "canonical" paths. In
    99     # before passing them to the API.  Instead, we assume the base url
    99     # before passing them to the API.  Instead, we assume the base url
   100     # is canonical and copy the behaviour of svn URL encoding function
   100     # is canonical and copy the behaviour of svn URL encoding function
   101     # so we can extend it safely with new components. The "safe"
   101     # so we can extend it safely with new components. The "safe"
   102     # characters were taken from the "svn_uri__char_validity" table in
   102     # characters were taken from the "svn_uri__char_validity" table in
   103     # libsvn_subr/path.c.
   103     # libsvn_subr/path.c.
   104     return urlreq.quote(s, "!$&'()*+,-./:=@_~")
   104     return urlreq.quote(s, b"!$&'()*+,-./:=@_~")
   105 
   105 
   106 
   106 
   107 def geturl(path):
   107 def geturl(path):
   108     try:
   108     try:
   109         return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
   109         return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
   111         # svn.client.url_from_path() fails with local repositories
   111         # svn.client.url_from_path() fails with local repositories
   112         pass
   112         pass
   113     if os.path.isdir(path):
   113     if os.path.isdir(path):
   114         path = os.path.normpath(os.path.abspath(path))
   114         path = os.path.normpath(os.path.abspath(path))
   115         if pycompat.iswindows:
   115         if pycompat.iswindows:
   116             path = '/' + util.normpath(path)
   116             path = b'/' + util.normpath(path)
   117         # Module URL is later compared with the repository URL returned
   117         # Module URL is later compared with the repository URL returned
   118         # by svn API, which is UTF-8.
   118         # by svn API, which is UTF-8.
   119         path = encoding.tolocal(path)
   119         path = encoding.tolocal(path)
   120         path = 'file://%s' % quote(path)
   120         path = b'file://%s' % quote(path)
   121     return svn.core.svn_path_canonicalize(path)
   121     return svn.core.svn_path_canonicalize(path)
   122 
   122 
   123 
   123 
   124 def optrev(number):
   124 def optrev(number):
   125     optrev = svn.core.svn_opt_revision_t()
   125     optrev = svn.core.svn_opt_revision_t()
   186     """Fetch SVN log in a subprocess and channel them back to parent to
   186     """Fetch SVN log in a subprocess and channel them back to parent to
   187     avoid memory collection issues.
   187     avoid memory collection issues.
   188     """
   188     """
   189     if svn is None:
   189     if svn is None:
   190         raise error.Abort(
   190         raise error.Abort(
   191             _('debugsvnlog could not load Subversion python ' 'bindings')
   191             _(b'debugsvnlog could not load Subversion python ' b'bindings')
   192         )
   192         )
   193 
   193 
   194     args = decodeargs(ui.fin.read())
   194     args = decodeargs(ui.fin.read())
   195     get_log_child(ui.fout, *args)
   195     get_log_child(ui.fout, *args)
   196 
   196 
   206             try:
   206             try:
   207                 entry = pickle.load(self._stdout)
   207                 entry = pickle.load(self._stdout)
   208             except EOFError:
   208             except EOFError:
   209                 raise error.Abort(
   209                 raise error.Abort(
   210                     _(
   210                     _(
   211                         'Mercurial failed to run itself, check'
   211                         b'Mercurial failed to run itself, check'
   212                         ' hg executable is in PATH'
   212                         b' hg executable is in PATH'
   213                     )
   213                     )
   214                 )
   214                 )
   215             try:
   215             try:
   216                 orig_paths, revnum, author, date, message = entry
   216                 orig_paths, revnum, author, date, message = entry
   217             except (TypeError, ValueError):
   217             except (TypeError, ValueError):
   218                 if entry is None:
   218                 if entry is None:
   219                     break
   219                     break
   220                 raise error.Abort(_("log stream exception '%s'") % entry)
   220                 raise error.Abort(_(b"log stream exception '%s'") % entry)
   221             yield entry
   221             yield entry
   222 
   222 
   223     def close(self):
   223     def close(self):
   224         if self._stdout:
   224         if self._stdout:
   225             self._stdout.close()
   225             self._stdout.close()
   268 
   268 
   269 # Check to see if the given path is a local Subversion repo. Verify this by
   269 # Check to see if the given path is a local Subversion repo. Verify this by
   270 # looking for several svn-specific files and directories in the given
   270 # looking for several svn-specific files and directories in the given
   271 # directory.
   271 # directory.
   272 def filecheck(ui, path, proto):
   272 def filecheck(ui, path, proto):
   273     for x in ('locks', 'hooks', 'format', 'db'):
   273     for x in (b'locks', b'hooks', b'format', b'db'):
   274         if not os.path.exists(os.path.join(path, x)):
   274         if not os.path.exists(os.path.join(path, x)):
   275             return False
   275             return False
   276     return True
   276     return True
   277 
   277 
   278 
   278 
   280 # this by requesting a version-controlled URL we know can't exist and looking
   280 # this by requesting a version-controlled URL we know can't exist and looking
   281 # for the svn-specific "not found" XML.
   281 # for the svn-specific "not found" XML.
   282 def httpcheck(ui, path, proto):
   282 def httpcheck(ui, path, proto):
   283     try:
   283     try:
   284         opener = urlreq.buildopener()
   284         opener = urlreq.buildopener()
   285         rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path), 'rb')
   285         rsp = opener.open(b'%s://%s/!svn/ver/0/.svn' % (proto, path), b'rb')
   286         data = rsp.read()
   286         data = rsp.read()
   287     except urlerr.httperror as inst:
   287     except urlerr.httperror as inst:
   288         if inst.code != 404:
   288         if inst.code != 404:
   289             # Except for 404 we cannot know for sure this is not an svn repo
   289             # Except for 404 we cannot know for sure this is not an svn repo
   290             ui.warn(
   290             ui.warn(
   291                 _(
   291                 _(
   292                     'svn: cannot probe remote repository, assume it could '
   292                     b'svn: cannot probe remote repository, assume it could '
   293                     'be a subversion repository. Use --source-type if you '
   293                     b'be a subversion repository. Use --source-type if you '
   294                     'know better.\n'
   294                     b'know better.\n'
   295                 )
   295                 )
   296             )
   296             )
   297             return True
   297             return True
   298         data = inst.fp.read()
   298         data = inst.fp.read()
   299     except Exception:
   299     except Exception:
   300         # Could be urlerr.urlerror if the URL is invalid or anything else.
   300         # Could be urlerr.urlerror if the URL is invalid or anything else.
   301         return False
   301         return False
   302     return '<m:human-readable errcode="160013">' in data
   302     return b'<m:human-readable errcode="160013">' in data
   303 
   303 
   304 
   304 
   305 protomap = {
   305 protomap = {
   306     'http': httpcheck,
   306     b'http': httpcheck,
   307     'https': httpcheck,
   307     b'https': httpcheck,
   308     'file': filecheck,
   308     b'file': filecheck,
   309 }
   309 }
   310 
   310 
   311 
   311 
   312 def issvnurl(ui, url):
   312 def issvnurl(ui, url):
   313     try:
   313     try:
   314         proto, path = url.split('://', 1)
   314         proto, path = url.split(b'://', 1)
   315         if proto == 'file':
   315         if proto == b'file':
   316             if (
   316             if (
   317                 pycompat.iswindows
   317                 pycompat.iswindows
   318                 and path[:1] == '/'
   318                 and path[:1] == b'/'
   319                 and path[1:2].isalpha()
   319                 and path[1:2].isalpha()
   320                 and path[2:6].lower() == '%3a/'
   320                 and path[2:6].lower() == b'%3a/'
   321             ):
   321             ):
   322                 path = path[:2] + ':/' + path[6:]
   322                 path = path[:2] + b':/' + path[6:]
   323             path = urlreq.url2pathname(path)
   323             path = urlreq.url2pathname(path)
   324     except ValueError:
   324     except ValueError:
   325         proto = 'file'
   325         proto = b'file'
   326         path = os.path.abspath(url)
   326         path = os.path.abspath(url)
   327     if proto == 'file':
   327     if proto == b'file':
   328         path = util.pconvert(path)
   328         path = util.pconvert(path)
   329     check = protomap.get(proto, lambda *args: False)
   329     check = protomap.get(proto, lambda *args: False)
   330     while '/' in path:
   330     while b'/' in path:
   331         if check(ui, path, proto):
   331         if check(ui, path, proto):
   332             return True
   332             return True
   333         path = path.rsplit('/', 1)[0]
   333         path = path.rsplit(b'/', 1)[0]
   334     return False
   334     return False
   335 
   335 
   336 
   336 
   337 # SVN conversion code stolen from bzr-svn and tailor
   337 # SVN conversion code stolen from bzr-svn and tailor
   338 #
   338 #
   351 class svn_source(converter_source):
   351 class svn_source(converter_source):
   352     def __init__(self, ui, repotype, url, revs=None):
   352     def __init__(self, ui, repotype, url, revs=None):
   353         super(svn_source, self).__init__(ui, repotype, url, revs=revs)
   353         super(svn_source, self).__init__(ui, repotype, url, revs=revs)
   354 
   354 
   355         if not (
   355         if not (
   356             url.startswith('svn://')
   356             url.startswith(b'svn://')
   357             or url.startswith('svn+ssh://')
   357             or url.startswith(b'svn+ssh://')
   358             or (
   358             or (
   359                 os.path.exists(url)
   359                 os.path.exists(url)
   360                 and os.path.exists(os.path.join(url, '.svn'))
   360                 and os.path.exists(os.path.join(url, b'.svn'))
   361             )
   361             )
   362             or issvnurl(ui, url)
   362             or issvnurl(ui, url)
   363         ):
   363         ):
   364             raise NoRepo(
   364             raise NoRepo(
   365                 _("%s does not look like a Subversion repository") % url
   365                 _(b"%s does not look like a Subversion repository") % url
   366             )
   366             )
   367         if svn is None:
   367         if svn is None:
   368             raise MissingTool(_('could not load Subversion python bindings'))
   368             raise MissingTool(_(b'could not load Subversion python bindings'))
   369 
   369 
   370         try:
   370         try:
   371             version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
   371             version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
   372             if version < (1, 4):
   372             if version < (1, 4):
   373                 raise MissingTool(
   373                 raise MissingTool(
   374                     _(
   374                     _(
   375                         'Subversion python bindings %d.%d found, '
   375                         b'Subversion python bindings %d.%d found, '
   376                         '1.4 or later required'
   376                         b'1.4 or later required'
   377                     )
   377                     )
   378                     % version
   378                     % version
   379                 )
   379                 )
   380         except AttributeError:
   380         except AttributeError:
   381             raise MissingTool(
   381             raise MissingTool(
   382                 _(
   382                 _(
   383                     'Subversion python bindings are too old, 1.4 '
   383                     b'Subversion python bindings are too old, 1.4 '
   384                     'or later required'
   384                     b'or later required'
   385                 )
   385                 )
   386             )
   386             )
   387 
   387 
   388         self.lastrevs = {}
   388         self.lastrevs = {}
   389 
   389 
   390         latest = None
   390         latest = None
   391         try:
   391         try:
   392             # Support file://path@rev syntax. Useful e.g. to convert
   392             # Support file://path@rev syntax. Useful e.g. to convert
   393             # deleted branches.
   393             # deleted branches.
   394             at = url.rfind('@')
   394             at = url.rfind(b'@')
   395             if at >= 0:
   395             if at >= 0:
   396                 latest = int(url[at + 1 :])
   396                 latest = int(url[at + 1 :])
   397                 url = url[:at]
   397                 url = url[:at]
   398         except ValueError:
   398         except ValueError:
   399             pass
   399             pass
   400         self.url = geturl(url)
   400         self.url = geturl(url)
   401         self.encoding = 'UTF-8'  # Subversion is always nominal UTF-8
   401         self.encoding = b'UTF-8'  # Subversion is always nominal UTF-8
   402         try:
   402         try:
   403             self.transport = transport.SvnRaTransport(url=self.url)
   403             self.transport = transport.SvnRaTransport(url=self.url)
   404             self.ra = self.transport.ra
   404             self.ra = self.transport.ra
   405             self.ctx = self.transport.client
   405             self.ctx = self.transport.client
   406             self.baseurl = svn.ra.get_repos_root(self.ra)
   406             self.baseurl = svn.ra.get_repos_root(self.ra)
   412             self.commits = {}
   412             self.commits = {}
   413             self.paths = {}
   413             self.paths = {}
   414             self.uuid = svn.ra.get_uuid(self.ra)
   414             self.uuid = svn.ra.get_uuid(self.ra)
   415         except svn.core.SubversionException:
   415         except svn.core.SubversionException:
   416             ui.traceback()
   416             ui.traceback()
   417             svnversion = '%d.%d.%d' % (
   417             svnversion = b'%d.%d.%d' % (
   418                 svn.core.SVN_VER_MAJOR,
   418                 svn.core.SVN_VER_MAJOR,
   419                 svn.core.SVN_VER_MINOR,
   419                 svn.core.SVN_VER_MINOR,
   420                 svn.core.SVN_VER_MICRO,
   420                 svn.core.SVN_VER_MICRO,
   421             )
   421             )
   422             raise NoRepo(
   422             raise NoRepo(
   423                 _(
   423                 _(
   424                     "%s does not look like a Subversion repository "
   424                     b"%s does not look like a Subversion repository "
   425                     "to libsvn version %s"
   425                     b"to libsvn version %s"
   426                 )
   426                 )
   427                 % (self.url, svnversion)
   427                 % (self.url, svnversion)
   428             )
   428             )
   429 
   429 
   430         if revs:
   430         if revs:
   431             if len(revs) > 1:
   431             if len(revs) > 1:
   432                 raise error.Abort(
   432                 raise error.Abort(
   433                     _(
   433                     _(
   434                         'subversion source does not support '
   434                         b'subversion source does not support '
   435                         'specifying multiple revisions'
   435                         b'specifying multiple revisions'
   436                     )
   436                     )
   437                 )
   437                 )
   438             try:
   438             try:
   439                 latest = int(revs[0])
   439                 latest = int(revs[0])
   440             except ValueError:
   440             except ValueError:
   441                 raise error.Abort(
   441                 raise error.Abort(
   442                     _('svn: revision %s is not an integer') % revs[0]
   442                     _(b'svn: revision %s is not an integer') % revs[0]
   443                 )
   443                 )
   444 
   444 
   445         trunkcfg = self.ui.config('convert', 'svn.trunk')
   445         trunkcfg = self.ui.config(b'convert', b'svn.trunk')
   446         if trunkcfg is None:
   446         if trunkcfg is None:
   447             trunkcfg = 'trunk'
   447             trunkcfg = b'trunk'
   448         self.trunkname = trunkcfg.strip('/')
   448         self.trunkname = trunkcfg.strip(b'/')
   449         self.startrev = self.ui.config('convert', 'svn.startrev')
   449         self.startrev = self.ui.config(b'convert', b'svn.startrev')
   450         try:
   450         try:
   451             self.startrev = int(self.startrev)
   451             self.startrev = int(self.startrev)
   452             if self.startrev < 0:
   452             if self.startrev < 0:
   453                 self.startrev = 0
   453                 self.startrev = 0
   454         except ValueError:
   454         except ValueError:
   455             raise error.Abort(
   455             raise error.Abort(
   456                 _('svn: start revision %s is not an integer') % self.startrev
   456                 _(b'svn: start revision %s is not an integer') % self.startrev
   457             )
   457             )
   458 
   458 
   459         try:
   459         try:
   460             self.head = self.latest(self.module, latest)
   460             self.head = self.latest(self.module, latest)
   461         except SvnPathNotFound:
   461         except SvnPathNotFound:
   462             self.head = None
   462             self.head = None
   463         if not self.head:
   463         if not self.head:
   464             raise error.Abort(_('no revision found in module %s') % self.module)
   464             raise error.Abort(
       
   465                 _(b'no revision found in module %s') % self.module
       
   466             )
   465         self.last_changed = self.revnum(self.head)
   467         self.last_changed = self.revnum(self.head)
   466 
   468 
   467         self._changescache = (None, None)
   469         self._changescache = (None, None)
   468 
   470 
   469         if os.path.exists(os.path.join(url, '.svn/entries')):
   471         if os.path.exists(os.path.join(url, b'.svn/entries')):
   470             self.wc = url
   472             self.wc = url
   471         else:
   473         else:
   472             self.wc = None
   474             self.wc = None
   473         self.convertfp = None
   475         self.convertfp = None
   474 
   476 
   482         self.lastrevs = lastrevs
   484         self.lastrevs = lastrevs
   483 
   485 
   484     def exists(self, path, optrev):
   486     def exists(self, path, optrev):
   485         try:
   487         try:
   486             svn.client.ls(
   488             svn.client.ls(
   487                 self.url.rstrip('/') + '/' + quote(path),
   489                 self.url.rstrip(b'/') + b'/' + quote(path),
   488                 optrev,
   490                 optrev,
   489                 False,
   491                 False,
   490                 self.ctx,
   492                 self.ctx,
   491             )
   493             )
   492             return True
   494             return True
   497         def isdir(path, revnum):
   499         def isdir(path, revnum):
   498             kind = self._checkpath(path, revnum)
   500             kind = self._checkpath(path, revnum)
   499             return kind == svn.core.svn_node_dir
   501             return kind == svn.core.svn_node_dir
   500 
   502 
   501         def getcfgpath(name, rev):
   503         def getcfgpath(name, rev):
   502             cfgpath = self.ui.config('convert', 'svn.' + name)
   504             cfgpath = self.ui.config(b'convert', b'svn.' + name)
   503             if cfgpath is not None and cfgpath.strip() == '':
   505             if cfgpath is not None and cfgpath.strip() == b'':
   504                 return None
   506                 return None
   505             path = (cfgpath or name).strip('/')
   507             path = (cfgpath or name).strip(b'/')
   506             if not self.exists(path, rev):
   508             if not self.exists(path, rev):
   507                 if self.module.endswith(path) and name == 'trunk':
   509                 if self.module.endswith(path) and name == b'trunk':
   508                     # we are converting from inside this directory
   510                     # we are converting from inside this directory
   509                     return None
   511                     return None
   510                 if cfgpath:
   512                 if cfgpath:
   511                     raise error.Abort(
   513                     raise error.Abort(
   512                         _('expected %s to be at %r, but not found')
   514                         _(b'expected %s to be at %r, but not found')
   513                         % (name, path)
   515                         % (name, path)
   514                     )
   516                     )
   515                 return None
   517                 return None
   516             self.ui.note(_('found %s at %r\n') % (name, path))
   518             self.ui.note(_(b'found %s at %r\n') % (name, path))
   517             return path
   519             return path
   518 
   520 
   519         rev = optrev(self.last_changed)
   521         rev = optrev(self.last_changed)
   520         oldmodule = ''
   522         oldmodule = b''
   521         trunk = getcfgpath('trunk', rev)
   523         trunk = getcfgpath(b'trunk', rev)
   522         self.tags = getcfgpath('tags', rev)
   524         self.tags = getcfgpath(b'tags', rev)
   523         branches = getcfgpath('branches', rev)
   525         branches = getcfgpath(b'branches', rev)
   524 
   526 
   525         # If the project has a trunk or branches, we will extract heads
   527         # If the project has a trunk or branches, we will extract heads
   526         # from them. We keep the project root otherwise.
   528         # from them. We keep the project root otherwise.
   527         if trunk:
   529         if trunk:
   528             oldmodule = self.module or ''
   530             oldmodule = self.module or b''
   529             self.module += '/' + trunk
   531             self.module += b'/' + trunk
   530             self.head = self.latest(self.module, self.last_changed)
   532             self.head = self.latest(self.module, self.last_changed)
   531             if not self.head:
   533             if not self.head:
   532                 raise error.Abort(
   534                 raise error.Abort(
   533                     _('no revision found in module %s') % self.module
   535                     _(b'no revision found in module %s') % self.module
   534                 )
   536                 )
   535 
   537 
   536         # First head in the list is the module's head
   538         # First head in the list is the module's head
   537         self.heads = [self.head]
   539         self.heads = [self.head]
   538         if self.tags is not None:
   540         if self.tags is not None:
   539             self.tags = '%s/%s' % (oldmodule, (self.tags or 'tags'))
   541             self.tags = b'%s/%s' % (oldmodule, (self.tags or b'tags'))
   540 
   542 
   541         # Check if branches bring a few more heads to the list
   543         # Check if branches bring a few more heads to the list
   542         if branches:
   544         if branches:
   543             rpath = self.url.strip('/')
   545             rpath = self.url.strip(b'/')
   544             branchnames = svn.client.ls(
   546             branchnames = svn.client.ls(
   545                 rpath + '/' + quote(branches), rev, False, self.ctx
   547                 rpath + b'/' + quote(branches), rev, False, self.ctx
   546             )
   548             )
   547             for branch in sorted(branchnames):
   549             for branch in sorted(branchnames):
   548                 module = '%s/%s/%s' % (oldmodule, branches, branch)
   550                 module = b'%s/%s/%s' % (oldmodule, branches, branch)
   549                 if not isdir(module, self.last_changed):
   551                 if not isdir(module, self.last_changed):
   550                     continue
   552                     continue
   551                 brevid = self.latest(module, self.last_changed)
   553                 brevid = self.latest(module, self.last_changed)
   552                 if not brevid:
   554                 if not brevid:
   553                     self.ui.note(_('ignoring empty branch %s\n') % branch)
   555                     self.ui.note(_(b'ignoring empty branch %s\n') % branch)
   554                     continue
   556                     continue
   555                 self.ui.note(
   557                 self.ui.note(
   556                     _('found branch %s at %d\n') % (branch, self.revnum(brevid))
   558                     _(b'found branch %s at %d\n')
       
   559                     % (branch, self.revnum(brevid))
   557                 )
   560                 )
   558                 self.heads.append(brevid)
   561                 self.heads.append(brevid)
   559 
   562 
   560         if self.startrev and self.heads:
   563         if self.startrev and self.heads:
   561             if len(self.heads) > 1:
   564             if len(self.heads) > 1:
   562                 raise error.Abort(
   565                 raise error.Abort(
   563                     _(
   566                     _(
   564                         'svn: start revision is not supported '
   567                         b'svn: start revision is not supported '
   565                         'with more than one branch'
   568                         b'with more than one branch'
   566                     )
   569                     )
   567                 )
   570                 )
   568             revnum = self.revnum(self.heads[0])
   571             revnum = self.revnum(self.heads[0])
   569             if revnum < self.startrev:
   572             if revnum < self.startrev:
   570                 raise error.Abort(
   573                 raise error.Abort(
   571                     _('svn: no revision found after start revision %d')
   574                     _(b'svn: no revision found after start revision %d')
   572                     % self.startrev
   575                     % self.startrev
   573                 )
   576                 )
   574 
   577 
   575         return self.heads
   578         return self.heads
   576 
   579 
   626             stop = self.lastrevs.get(module, 0)
   629             stop = self.lastrevs.get(module, 0)
   627             if revnum < stop:
   630             if revnum < stop:
   628                 stop = revnum + 1
   631                 stop = revnum + 1
   629             self._fetch_revisions(revnum, stop)
   632             self._fetch_revisions(revnum, stop)
   630             if rev not in self.commits:
   633             if rev not in self.commits:
   631                 raise error.Abort(_('svn: revision %s not found') % revnum)
   634                 raise error.Abort(_(b'svn: revision %s not found') % revnum)
   632         revcommit = self.commits[rev]
   635         revcommit = self.commits[rev]
   633         # caller caches the result, so free it here to release memory
   636         # caller caches the result, so free it here to release memory
   634         del self.commits[rev]
   637         del self.commits[rev]
   635         return revcommit
   638         return revcommit
   636 
   639 
   637     def checkrevformat(self, revstr, mapname='splicemap'):
   640     def checkrevformat(self, revstr, mapname=b'splicemap'):
   638         """ fails if revision format does not match the correct format"""
   641         """ fails if revision format does not match the correct format"""
   639         if not re.match(
   642         if not re.match(
   640             r'svn:[0-9a-f]{8,8}-[0-9a-f]{4,4}-'
   643             r'svn:[0-9a-f]{8,8}-[0-9a-f]{4,4}-'
   641             r'[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]'
   644             r'[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]'
   642             r'{12,12}(.*)\@[0-9]+$',
   645             r'{12,12}(.*)\@[0-9]+$',
   643             revstr,
   646             revstr,
   644         ):
   647         ):
   645             raise error.Abort(
   648             raise error.Abort(
   646                 _('%s entry %s is not a valid revision' ' identifier')
   649                 _(b'%s entry %s is not a valid revision' b' identifier')
   647                 % (mapname, revstr)
   650                 % (mapname, revstr)
   648             )
   651             )
   649 
   652 
   650     def numcommits(self):
   653     def numcommits(self):
   651         return int(self.head.rsplit('@', 1)[1]) - self.startrev
   654         return int(self.head.rsplit(b'@', 1)[1]) - self.startrev
   652 
   655 
   653     def gettags(self):
   656     def gettags(self):
   654         tags = {}
   657         tags = {}
   655         if self.tags is None:
   658         if self.tags is None:
   656             return tags
   659             return tags
   687                 if copies and copies[-1][2] == tagspath:
   690                 if copies and copies[-1][2] == tagspath:
   688                     # Track tags directory moves
   691                     # Track tags directory moves
   689                     srctagspath = copies.pop()[0]
   692                     srctagspath = copies.pop()[0]
   690 
   693 
   691                 for source, sourcerev, dest in copies:
   694                 for source, sourcerev, dest in copies:
   692                     if not dest.startswith(tagspath + '/'):
   695                     if not dest.startswith(tagspath + b'/'):
   693                         continue
   696                         continue
   694                     for tag in pendings:
   697                     for tag in pendings:
   695                         if tag[0].startswith(dest):
   698                         if tag[0].startswith(dest):
   696                             tagpath = source + tag[0][len(dest) :]
   699                             tagpath = source + tag[0][len(dest) :]
   697                             tag[:2] = [tagpath, sourcerev]
   700                             tag[:2] = [tagpath, sourcerev]
   707                 # It happens with tools like cvs2svn. Such tags cannot
   710                 # It happens with tools like cvs2svn. Such tags cannot
   708                 # be represented in mercurial.
   711                 # be represented in mercurial.
   709                 addeds = dict(
   712                 addeds = dict(
   710                     (p, e.copyfrom_path)
   713                     (p, e.copyfrom_path)
   711                     for p, e in origpaths.iteritems()
   714                     for p, e in origpaths.iteritems()
   712                     if e.action == 'A' and e.copyfrom_path
   715                     if e.action == b'A' and e.copyfrom_path
   713                 )
   716                 )
   714                 badroots = set()
   717                 badroots = set()
   715                 for destroot in addeds:
   718                 for destroot in addeds:
   716                     for source, sourcerev, dest in pendings:
   719                     for source, sourcerev, dest in pendings:
   717                         if not dest.startswith(
   720                         if not dest.startswith(
   718                             destroot + '/'
   721                             destroot + b'/'
   719                         ) or source.startswith(addeds[destroot] + '/'):
   722                         ) or source.startswith(addeds[destroot] + b'/'):
   720                             continue
   723                             continue
   721                         badroots.add(destroot)
   724                         badroots.add(destroot)
   722                         break
   725                         break
   723 
   726 
   724                 for badroot in badroots:
   727                 for badroot in badroots:
   725                     pendings = [
   728                     pendings = [
   726                         p
   729                         p
   727                         for p in pendings
   730                         for p in pendings
   728                         if p[2] != badroot
   731                         if p[2] != badroot
   729                         and not p[2].startswith(badroot + '/')
   732                         and not p[2].startswith(badroot + b'/')
   730                     ]
   733                     ]
   731 
   734 
   732                 # Tell tag renamings from tag creations
   735                 # Tell tag renamings from tag creations
   733                 renamings = []
   736                 renamings = []
   734                 for source, sourcerev, dest in pendings:
   737                 for source, sourcerev, dest in pendings:
   735                     tagname = dest.split('/')[-1]
   738                     tagname = dest.split(b'/')[-1]
   736                     if source.startswith(srctagspath):
   739                     if source.startswith(srctagspath):
   737                         renamings.append([source, sourcerev, tagname])
   740                         renamings.append([source, sourcerev, tagname])
   738                         continue
   741                         continue
   739                     if tagname in tags:
   742                     if tagname in tags:
   740                         # Keep the latest tag value
   743                         # Keep the latest tag value
   759     def converted(self, rev, destrev):
   762     def converted(self, rev, destrev):
   760         if not self.wc:
   763         if not self.wc:
   761             return
   764             return
   762         if self.convertfp is None:
   765         if self.convertfp is None:
   763             self.convertfp = open(
   766             self.convertfp = open(
   764                 os.path.join(self.wc, '.svn', 'hg-shamap'), 'ab'
   767                 os.path.join(self.wc, b'.svn', b'hg-shamap'), b'ab'
   765             )
   768             )
   766         self.convertfp.write(
   769         self.convertfp.write(
   767             util.tonativeeol('%s %d\n' % (destrev, self.revnum(rev)))
   770             util.tonativeeol(b'%s %d\n' % (destrev, self.revnum(rev)))
   768         )
   771         )
   769         self.convertfp.flush()
   772         self.convertfp.flush()
   770 
   773 
   771     def revid(self, revnum, module=None):
   774     def revid(self, revnum, module=None):
   772         return 'svn:%s%s@%s' % (self.uuid, module or self.module, revnum)
   775         return b'svn:%s%s@%s' % (self.uuid, module or self.module, revnum)
   773 
   776 
   774     def revnum(self, rev):
   777     def revnum(self, rev):
   775         return int(rev.split('@')[-1])
   778         return int(rev.split(b'@')[-1])
   776 
   779 
   777     def latest(self, path, stop=None):
   780     def latest(self, path, stop=None):
   778         """Find the latest revid affecting path, up to stop revision
   781         """Find the latest revid affecting path, up to stop revision
   779         number. If stop is None, default to repository latest
   782         number. If stop is None, default to repository latest
   780         revision. It may return a revision in a different module,
   783         revision. It may return a revision in a different module,
   798                     for p in paths:
   801                     for p in paths:
   799                         if not path.startswith(p) or not paths[p].copyfrom_path:
   802                         if not path.startswith(p) or not paths[p].copyfrom_path:
   800                             continue
   803                             continue
   801                         newpath = paths[p].copyfrom_path + path[len(p) :]
   804                         newpath = paths[p].copyfrom_path + path[len(p) :]
   802                         self.ui.debug(
   805                         self.ui.debug(
   803                             "branch renamed from %s to %s at %d\n"
   806                             b"branch renamed from %s to %s at %d\n"
   804                             % (path, newpath, revnum)
   807                             % (path, newpath, revnum)
   805                         )
   808                         )
   806                         path = newpath
   809                         path = newpath
   807                         break
   810                         break
   808                 if not paths:
   811                 if not paths:
   811             finally:
   814             finally:
   812                 stream.close()
   815                 stream.close()
   813 
   816 
   814         if not path.startswith(self.rootmodule):
   817         if not path.startswith(self.rootmodule):
   815             # Requests on foreign branches may be forbidden at server level
   818             # Requests on foreign branches may be forbidden at server level
   816             self.ui.debug('ignoring foreign branch %r\n' % path)
   819             self.ui.debug(b'ignoring foreign branch %r\n' % path)
   817             return None
   820             return None
   818 
   821 
   819         if stop is None:
   822         if stop is None:
   820             stop = svn.ra.get_latest_revnum(self.ra)
   823             stop = svn.ra.get_latest_revnum(self.ra)
   821         try:
   824         try:
   822             prevmodule = self.reparent('')
   825             prevmodule = self.reparent(b'')
   823             dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
   826             dirent = svn.ra.stat(self.ra, path.strip(b'/'), stop)
   824             self.reparent(prevmodule)
   827             self.reparent(prevmodule)
   825         except svn.core.SubversionException:
   828         except svn.core.SubversionException:
   826             dirent = None
   829             dirent = None
   827         if not dirent:
   830         if not dirent:
   828             raise SvnPathNotFound(
   831             raise SvnPathNotFound(
   829                 _('%s not found up to revision %d') % (path, stop)
   832                 _(b'%s not found up to revision %d') % (path, stop)
   830             )
   833             )
   831 
   834 
   832         # stat() gives us the previous revision on this line of
   835         # stat() gives us the previous revision on this line of
   833         # development, but it might be in *another module*. Fetch the
   836         # development, but it might be in *another module*. Fetch the
   834         # log and detect renames down to the latest revision.
   837         # log and detect renames down to the latest revision.
   841             # returned by ra.stat(), at least when stating the root
   844             # returned by ra.stat(), at least when stating the root
   842             # module. In that case, do not trust created_rev and scan
   845             # module. In that case, do not trust created_rev and scan
   843             # the whole history.
   846             # the whole history.
   844             revnum, realpath = findchanges(path, stop)
   847             revnum, realpath = findchanges(path, stop)
   845             if revnum is None:
   848             if revnum is None:
   846                 self.ui.debug('ignoring empty branch %r\n' % realpath)
   849                 self.ui.debug(b'ignoring empty branch %r\n' % realpath)
   847                 return None
   850                 return None
   848 
   851 
   849         if not realpath.startswith(self.rootmodule):
   852         if not realpath.startswith(self.rootmodule):
   850             self.ui.debug('ignoring foreign branch %r\n' % realpath)
   853             self.ui.debug(b'ignoring foreign branch %r\n' % realpath)
   851             return None
   854             return None
   852         return self.revid(revnum, realpath)
   855         return self.revid(revnum, realpath)
   853 
   856 
   854     def reparent(self, module):
   857     def reparent(self, module):
   855         """Reparent the svn transport and return the previous parent."""
   858         """Reparent the svn transport and return the previous parent."""
   856         if self.prevmodule == module:
   859         if self.prevmodule == module:
   857             return module
   860             return module
   858         svnurl = self.baseurl + quote(module)
   861         svnurl = self.baseurl + quote(module)
   859         prevmodule = self.prevmodule
   862         prevmodule = self.prevmodule
   860         if prevmodule is None:
   863         if prevmodule is None:
   861             prevmodule = ''
   864             prevmodule = b''
   862         self.ui.debug("reparent to %s\n" % svnurl)
   865         self.ui.debug(b"reparent to %s\n" % svnurl)
   863         svn.ra.reparent(self.ra, svnurl)
   866         svn.ra.reparent(self.ra, svnurl)
   864         self.prevmodule = module
   867         self.prevmodule = module
   865         return prevmodule
   868         return prevmodule
   866 
   869 
   867     def expandpaths(self, rev, paths, parents):
   870     def expandpaths(self, rev, paths, parents):
   872         if new_module != self.module:
   875         if new_module != self.module:
   873             self.module = new_module
   876             self.module = new_module
   874             self.reparent(self.module)
   877             self.reparent(self.module)
   875 
   878 
   876         progress = self.ui.makeprogress(
   879         progress = self.ui.makeprogress(
   877             _('scanning paths'), unit=_('paths'), total=len(paths)
   880             _(b'scanning paths'), unit=_(b'paths'), total=len(paths)
   878         )
   881         )
   879         for i, (path, ent) in enumerate(paths):
   882         for i, (path, ent) in enumerate(paths):
   880             progress.update(i, item=path)
   883             progress.update(i, item=path)
   881             entrypath = self.getrelpath(path)
   884             entrypath = self.getrelpath(path)
   882 
   885 
   892                     continue
   895                     continue
   893                 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
   896                 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
   894                 if not copyfrom_path:
   897                 if not copyfrom_path:
   895                     continue
   898                     continue
   896                 self.ui.debug(
   899                 self.ui.debug(
   897                     "copied to %s from %s@%s\n"
   900                     b"copied to %s from %s@%s\n"
   898                     % (entrypath, copyfrom_path, ent.copyfrom_rev)
   901                     % (entrypath, copyfrom_path, ent.copyfrom_rev)
   899                 )
   902                 )
   900                 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
   903                 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
   901             elif kind == 0:  # gone, but had better be a deleted *file*
   904             elif kind == 0:  # gone, but had better be a deleted *file*
   902                 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
   905                 self.ui.debug(b"gone from %s\n" % ent.copyfrom_rev)
   903                 pmodule, prevnum = revsplit(parents[0])[1:]
   906                 pmodule, prevnum = revsplit(parents[0])[1:]
   904                 parentpath = pmodule + "/" + entrypath
   907                 parentpath = pmodule + b"/" + entrypath
   905                 fromkind = self._checkpath(entrypath, prevnum, pmodule)
   908                 fromkind = self._checkpath(entrypath, prevnum, pmodule)
   906 
   909 
   907                 if fromkind == svn.core.svn_node_file:
   910                 if fromkind == svn.core.svn_node_file:
   908                     removed.add(self.recode(entrypath))
   911                     removed.add(self.recode(entrypath))
   909                 elif fromkind == svn.core.svn_node_dir:
   912                 elif fromkind == svn.core.svn_node_dir:
   910                     oroot = parentpath.strip('/')
   913                     oroot = parentpath.strip(b'/')
   911                     nroot = path.strip('/')
   914                     nroot = path.strip(b'/')
   912                     children = self._iterfiles(oroot, prevnum)
   915                     children = self._iterfiles(oroot, prevnum)
   913                     for childpath in children:
   916                     for childpath in children:
   914                         childpath = childpath.replace(oroot, nroot)
   917                         childpath = childpath.replace(oroot, nroot)
   915                         childpath = self.getrelpath("/" + childpath, pmodule)
   918                         childpath = self.getrelpath(b"/" + childpath, pmodule)
   916                         if childpath:
   919                         if childpath:
   917                             removed.add(self.recode(childpath))
   920                             removed.add(self.recode(childpath))
   918                 else:
   921                 else:
   919                     self.ui.debug(
   922                     self.ui.debug(
   920                         'unknown path in revision %d: %s\n' % (revnum, path)
   923                         b'unknown path in revision %d: %s\n' % (revnum, path)
   921                     )
   924                     )
   922             elif kind == svn.core.svn_node_dir:
   925             elif kind == svn.core.svn_node_dir:
   923                 if ent.action == 'M':
   926                 if ent.action == b'M':
   924                     # If the directory just had a prop change,
   927                     # If the directory just had a prop change,
   925                     # then we shouldn't need to look for its children.
   928                     # then we shouldn't need to look for its children.
   926                     continue
   929                     continue
   927                 if ent.action == 'R' and parents:
   930                 if ent.action == b'R' and parents:
   928                     # If a directory is replacing a file, mark the previous
   931                     # If a directory is replacing a file, mark the previous
   929                     # file as deleted
   932                     # file as deleted
   930                     pmodule, prevnum = revsplit(parents[0])[1:]
   933                     pmodule, prevnum = revsplit(parents[0])[1:]
   931                     pkind = self._checkpath(entrypath, prevnum, pmodule)
   934                     pkind = self._checkpath(entrypath, prevnum, pmodule)
   932                     if pkind == svn.core.svn_node_file:
   935                     if pkind == svn.core.svn_node_file:
   933                         removed.add(self.recode(entrypath))
   936                         removed.add(self.recode(entrypath))
   934                     elif pkind == svn.core.svn_node_dir:
   937                     elif pkind == svn.core.svn_node_dir:
   935                         # We do not know what files were kept or removed,
   938                         # We do not know what files were kept or removed,
   936                         # mark them all as changed.
   939                         # mark them all as changed.
   937                         for childpath in self._iterfiles(pmodule, prevnum):
   940                         for childpath in self._iterfiles(pmodule, prevnum):
   938                             childpath = self.getrelpath("/" + childpath)
   941                             childpath = self.getrelpath(b"/" + childpath)
   939                             if childpath:
   942                             if childpath:
   940                                 changed.add(self.recode(childpath))
   943                                 changed.add(self.recode(childpath))
   941 
   944 
   942                 for childpath in self._iterfiles(path, revnum):
   945                 for childpath in self._iterfiles(path, revnum):
   943                     childpath = self.getrelpath("/" + childpath)
   946                     childpath = self.getrelpath(b"/" + childpath)
   944                     if childpath:
   947                     if childpath:
   945                         changed.add(self.recode(childpath))
   948                         changed.add(self.recode(childpath))
   946 
   949 
   947                 # Handle directory copies
   950                 # Handle directory copies
   948                 if not ent.copyfrom_path or not parents:
   951                 if not ent.copyfrom_path or not parents:
   954                     continue
   957                     continue
   955                 copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule)
   958                 copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule)
   956                 if not copyfrompath:
   959                 if not copyfrompath:
   957                     continue
   960                     continue
   958                 self.ui.debug(
   961                 self.ui.debug(
   959                     "mark %s came from %s:%d\n"
   962                     b"mark %s came from %s:%d\n"
   960                     % (path, copyfrompath, ent.copyfrom_rev)
   963                     % (path, copyfrompath, ent.copyfrom_rev)
   961                 )
   964                 )
   962                 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev)
   965                 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev)
   963                 for childpath in children:
   966                 for childpath in children:
   964                     childpath = self.getrelpath("/" + childpath, pmodule)
   967                     childpath = self.getrelpath(b"/" + childpath, pmodule)
   965                     if not childpath:
   968                     if not childpath:
   966                         continue
   969                         continue
   967                     copytopath = path + childpath[len(copyfrompath) :]
   970                     copytopath = path + childpath[len(copyfrompath) :]
   968                     copytopath = self.getrelpath(copytopath)
   971                     copytopath = self.getrelpath(copytopath)
   969                     copies[self.recode(copytopath)] = self.recode(childpath)
   972                     copies[self.recode(copytopath)] = self.recode(childpath)
   981         def parselogentry(orig_paths, revnum, author, date, message):
   984         def parselogentry(orig_paths, revnum, author, date, message):
   982             """Return the parsed commit object or None, and True if
   985             """Return the parsed commit object or None, and True if
   983             the revision is a branch root.
   986             the revision is a branch root.
   984             """
   987             """
   985             self.ui.debug(
   988             self.ui.debug(
   986                 "parsing revision %d (%d changes)\n" % (revnum, len(orig_paths))
   989                 b"parsing revision %d (%d changes)\n"
       
   990                 % (revnum, len(orig_paths))
   987             )
   991             )
   988 
   992 
   989             branched = False
   993             branched = False
   990             rev = self.revid(revnum)
   994             rev = self.revid(revnum)
   991             # branch log might return entries for a parent we already have
   995             # branch log might return entries for a parent we already have
  1010                     if previd is not None:
  1014                     if previd is not None:
  1011                         prevmodule, prevnum = revsplit(previd)[1:]
  1015                         prevmodule, prevnum = revsplit(previd)[1:]
  1012                         if prevnum >= self.startrev:
  1016                         if prevnum >= self.startrev:
  1013                             parents = [previd]
  1017                             parents = [previd]
  1014                             self.ui.note(
  1018                             self.ui.note(
  1015                                 _('found parent of branch %s at %d: %s\n')
  1019                                 _(b'found parent of branch %s at %d: %s\n')
  1016                                 % (self.module, prevnum, prevmodule)
  1020                                 % (self.module, prevnum, prevmodule)
  1017                             )
  1021                             )
  1018                 else:
  1022                 else:
  1019                     self.ui.debug("no copyfrom path, don't know what to do.\n")
  1023                     self.ui.debug(b"no copyfrom path, don't know what to do.\n")
  1020 
  1024 
  1021             paths = []
  1025             paths = []
  1022             # filter out unrelated paths
  1026             # filter out unrelated paths
  1023             for path, ent in orig_paths:
  1027             for path, ent in orig_paths:
  1024                 if self.getrelpath(path) is None:
  1028                 if self.getrelpath(path) is None:
  1026                 paths.append((path, ent))
  1030                 paths.append((path, ent))
  1027 
  1031 
  1028             # Example SVN datetime. Includes microseconds.
  1032             # Example SVN datetime. Includes microseconds.
  1029             # ISO-8601 conformant
  1033             # ISO-8601 conformant
  1030             # '2007-01-04T17:35:00.902377Z'
  1034             # '2007-01-04T17:35:00.902377Z'
  1031             date = dateutil.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
  1035             date = dateutil.parsedate(
  1032             if self.ui.configbool('convert', 'localtimezone'):
  1036                 date[:19] + b" UTC", [b"%Y-%m-%dT%H:%M:%S"]
       
  1037             )
       
  1038             if self.ui.configbool(b'convert', b'localtimezone'):
  1033                 date = makedatetimestamp(date[0])
  1039                 date = makedatetimestamp(date[0])
  1034 
  1040 
  1035             if message:
  1041             if message:
  1036                 log = self.recode(message)
  1042                 log = self.recode(message)
  1037             else:
  1043             else:
  1038                 log = ''
  1044                 log = b''
  1039 
  1045 
  1040             if author:
  1046             if author:
  1041                 author = self.recode(author)
  1047                 author = self.recode(author)
  1042             else:
  1048             else:
  1043                 author = ''
  1049                 author = b''
  1044 
  1050 
  1045             try:
  1051             try:
  1046                 branch = self.module.split("/")[-1]
  1052                 branch = self.module.split(b"/")[-1]
  1047                 if branch == self.trunkname:
  1053                 if branch == self.trunkname:
  1048                     branch = None
  1054                     branch = None
  1049             except IndexError:
  1055             except IndexError:
  1050                 branch = None
  1056                 branch = None
  1051 
  1057 
  1052             cset = commit(
  1058             cset = commit(
  1053                 author=author,
  1059                 author=author,
  1054                 date=dateutil.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
  1060                 date=dateutil.datestr(date, b'%Y-%m-%d %H:%M:%S %1%2'),
  1055                 desc=log,
  1061                 desc=log,
  1056                 parents=parents,
  1062                 parents=parents,
  1057                 branch=branch,
  1063                 branch=branch,
  1058                 rev=rev,
  1064                 rev=rev,
  1059             )
  1065             )
  1066                 self.child_cset.parents[:] = [rev]
  1072                 self.child_cset.parents[:] = [rev]
  1067             self.child_cset = cset
  1073             self.child_cset = cset
  1068             return cset, branched
  1074             return cset, branched
  1069 
  1075 
  1070         self.ui.note(
  1076         self.ui.note(
  1071             _('fetching revision log for "%s" from %d to %d\n')
  1077             _(b'fetching revision log for "%s" from %d to %d\n')
  1072             % (self.module, from_revnum, to_revnum)
  1078             % (self.module, from_revnum, to_revnum)
  1073         )
  1079         )
  1074 
  1080 
  1075         try:
  1081         try:
  1076             firstcset = None
  1082             firstcset = None
  1081                     paths, revnum, author, date, message = entry
  1087                     paths, revnum, author, date, message = entry
  1082                     if revnum < self.startrev:
  1088                     if revnum < self.startrev:
  1083                         lastonbranch = True
  1089                         lastonbranch = True
  1084                         break
  1090                         break
  1085                     if not paths:
  1091                     if not paths:
  1086                         self.ui.debug('revision %d has no entries\n' % revnum)
  1092                         self.ui.debug(b'revision %d has no entries\n' % revnum)
  1087                         # If we ever leave the loop on an empty
  1093                         # If we ever leave the loop on an empty
  1088                         # revision, do not try to get a parent branch
  1094                         # revision, do not try to get a parent branch
  1089                         lastonbranch = lastonbranch or revnum == 0
  1095                         lastonbranch = lastonbranch or revnum == 0
  1090                         continue
  1096                         continue
  1091                     cset, lastonbranch = parselogentry(
  1097                     cset, lastonbranch = parselogentry(
  1112                     pass
  1118                     pass
  1113         except svn.core.SubversionException as xxx_todo_changeme:
  1119         except svn.core.SubversionException as xxx_todo_changeme:
  1114             (inst, num) = xxx_todo_changeme.args
  1120             (inst, num) = xxx_todo_changeme.args
  1115             if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
  1121             if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
  1116                 raise error.Abort(
  1122                 raise error.Abort(
  1117                     _('svn: branch has no revision %s') % to_revnum
  1123                     _(b'svn: branch has no revision %s') % to_revnum
  1118                 )
  1124                 )
  1119             raise
  1125             raise
  1120 
  1126 
  1121     def getfile(self, file, rev):
  1127     def getfile(self, file, rev):
  1122         # TODO: ra.get_file transmits the whole file instead of diffs.
  1128         # TODO: ra.get_file transmits the whole file instead of diffs.
  1133             # ra.get_file() seems to keep a reference on the input buffer
  1139             # ra.get_file() seems to keep a reference on the input buffer
  1134             # preventing collection. Release it explicitly.
  1140             # preventing collection. Release it explicitly.
  1135             io.close()
  1141             io.close()
  1136             if isinstance(info, list):
  1142             if isinstance(info, list):
  1137                 info = info[-1]
  1143                 info = info[-1]
  1138             mode = ("svn:executable" in info) and 'x' or ''
  1144             mode = (b"svn:executable" in info) and b'x' or b''
  1139             mode = ("svn:special" in info) and 'l' or mode
  1145             mode = (b"svn:special" in info) and b'l' or mode
  1140         except svn.core.SubversionException as e:
  1146         except svn.core.SubversionException as e:
  1141             notfound = (
  1147             notfound = (
  1142                 svn.core.SVN_ERR_FS_NOT_FOUND,
  1148                 svn.core.SVN_ERR_FS_NOT_FOUND,
  1143                 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND,
  1149                 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND,
  1144             )
  1150             )
  1145             if e.apr_err in notfound:  # File not found
  1151             if e.apr_err in notfound:  # File not found
  1146                 return None, None
  1152                 return None, None
  1147             raise
  1153             raise
  1148         if mode == 'l':
  1154         if mode == b'l':
  1149             link_prefix = "link "
  1155             link_prefix = b"link "
  1150             if data.startswith(link_prefix):
  1156             if data.startswith(link_prefix):
  1151                 data = data[len(link_prefix) :]
  1157                 data = data[len(link_prefix) :]
  1152         return data, mode
  1158         return data, mode
  1153 
  1159 
  1154     def _iterfiles(self, path, revnum):
  1160     def _iterfiles(self, path, revnum):
  1155         """Enumerate all files in path at revnum, recursively."""
  1161         """Enumerate all files in path at revnum, recursively."""
  1156         path = path.strip('/')
  1162         path = path.strip(b'/')
  1157         pool = svn.core.Pool()
  1163         pool = svn.core.Pool()
  1158         rpath = '/'.join([self.baseurl, quote(path)]).strip('/')
  1164         rpath = b'/'.join([self.baseurl, quote(path)]).strip(b'/')
  1159         entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
  1165         entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
  1160         if path:
  1166         if path:
  1161             path += '/'
  1167             path += b'/'
  1162         return (
  1168         return (
  1163             (path + p)
  1169             (path + p)
  1164             for p, e in entries.iteritems()
  1170             for p, e in entries.iteritems()
  1165             if e.kind == svn.core.svn_node_file
  1171             if e.kind == svn.core.svn_node_file
  1166         )
  1172         )
  1173         # extract the "entry" portion (a relative path) from what
  1179         # extract the "entry" portion (a relative path) from what
  1174         # svn log --xml says, i.e.
  1180         # svn log --xml says, i.e.
  1175         #   "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
  1181         #   "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
  1176         # that is to say "tests/PloneTestCase.py"
  1182         # that is to say "tests/PloneTestCase.py"
  1177         if path.startswith(module):
  1183         if path.startswith(module):
  1178             relative = path.rstrip('/')[len(module) :]
  1184             relative = path.rstrip(b'/')[len(module) :]
  1179             if relative.startswith('/'):
  1185             if relative.startswith(b'/'):
  1180                 return relative[1:]
  1186                 return relative[1:]
  1181             elif relative == '':
  1187             elif relative == b'':
  1182                 return relative
  1188                 return relative
  1183 
  1189 
  1184         # The path is outside our tracked tree...
  1190         # The path is outside our tracked tree...
  1185         self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
  1191         self.ui.debug(b'%r is not under %r, ignoring\n' % (path, module))
  1186         return None
  1192         return None
  1187 
  1193 
  1188     def _checkpath(self, path, revnum, module=None):
  1194     def _checkpath(self, path, revnum, module=None):
  1189         if module is not None:
  1195         if module is not None:
  1190             prevmodule = self.reparent('')
  1196             prevmodule = self.reparent(b'')
  1191             path = module + '/' + path
  1197             path = module + b'/' + path
  1192         try:
  1198         try:
  1193             # ra.check_path does not like leading slashes very much, it leads
  1199             # ra.check_path does not like leading slashes very much, it leads
  1194             # to PROPFIND subversion errors
  1200             # to PROPFIND subversion errors
  1195             return svn.ra.check_path(self.ra, path.strip('/'), revnum)
  1201             return svn.ra.check_path(self.ra, path.strip(b'/'), revnum)
  1196         finally:
  1202         finally:
  1197             if module is not None:
  1203             if module is not None:
  1198                 self.reparent(prevmodule)
  1204                 self.reparent(prevmodule)
  1199 
  1205 
  1200     def _getlog(
  1206     def _getlog(
  1208     ):
  1214     ):
  1209         # Normalize path names, svn >= 1.5 only wants paths relative to
  1215         # Normalize path names, svn >= 1.5 only wants paths relative to
  1210         # supplied URL
  1216         # supplied URL
  1211         relpaths = []
  1217         relpaths = []
  1212         for p in paths:
  1218         for p in paths:
  1213             if not p.startswith('/'):
  1219             if not p.startswith(b'/'):
  1214                 p = self.module + '/' + p
  1220                 p = self.module + b'/' + p
  1215             relpaths.append(p.strip('/'))
  1221             relpaths.append(p.strip(b'/'))
  1216         args = [
  1222         args = [
  1217             self.baseurl,
  1223             self.baseurl,
  1218             relpaths,
  1224             relpaths,
  1219             start,
  1225             start,
  1220             end,
  1226             end,
  1221             limit,
  1227             limit,
  1222             discover_changed_paths,
  1228             discover_changed_paths,
  1223             strict_node_history,
  1229             strict_node_history,
  1224         ]
  1230         ]
  1225         # developer config: convert.svn.debugsvnlog
  1231         # developer config: convert.svn.debugsvnlog
  1226         if not self.ui.configbool('convert', 'svn.debugsvnlog'):
  1232         if not self.ui.configbool(b'convert', b'svn.debugsvnlog'):
  1227             return directlogstream(*args)
  1233             return directlogstream(*args)
  1228         arg = encodeargs(args)
  1234         arg = encodeargs(args)
  1229         hgexe = procutil.hgexecutable()
  1235         hgexe = procutil.hgexecutable()
  1230         cmd = '%s debugsvnlog' % procutil.shellquote(hgexe)
  1236         cmd = b'%s debugsvnlog' % procutil.shellquote(hgexe)
  1231         stdin, stdout = procutil.popen2(procutil.quotecommand(cmd))
  1237         stdin, stdout = procutil.popen2(procutil.quotecommand(cmd))
  1232         stdin.write(arg)
  1238         stdin.write(arg)
  1233         try:
  1239         try:
  1234             stdin.close()
  1240             stdin.close()
  1235         except IOError:
  1241         except IOError:
  1236             raise error.Abort(
  1242             raise error.Abort(
  1237                 _(
  1243                 _(
  1238                     'Mercurial failed to run itself, check'
  1244                     b'Mercurial failed to run itself, check'
  1239                     ' hg executable is in PATH'
  1245                     b' hg executable is in PATH'
  1240                 )
  1246                 )
  1241             )
  1247             )
  1242         return logstream(stdout)
  1248         return logstream(stdout)
  1243 
  1249 
  1244 
  1250 
  1270     def postrun(self):
  1276     def postrun(self):
  1271         if self.wc:
  1277         if self.wc:
  1272             os.chdir(self.cwd)
  1278             os.chdir(self.cwd)
  1273 
  1279 
  1274     def join(self, name):
  1280     def join(self, name):
  1275         return os.path.join(self.wc, '.svn', name)
  1281         return os.path.join(self.wc, b'.svn', name)
  1276 
  1282 
  1277     def revmapfile(self):
  1283     def revmapfile(self):
  1278         return self.join('hg-shamap')
  1284         return self.join(b'hg-shamap')
  1279 
  1285 
  1280     def authorfile(self):
  1286     def authorfile(self):
  1281         return self.join('hg-authormap')
  1287         return self.join(b'hg-authormap')
  1282 
  1288 
  1283     def __init__(self, ui, repotype, path):
  1289     def __init__(self, ui, repotype, path):
  1284 
  1290 
  1285         converter_sink.__init__(self, ui, repotype, path)
  1291         converter_sink.__init__(self, ui, repotype, path)
  1286         commandline.__init__(self, ui, 'svn')
  1292         commandline.__init__(self, ui, b'svn')
  1287         self.delete = []
  1293         self.delete = []
  1288         self.setexec = []
  1294         self.setexec = []
  1289         self.delexec = []
  1295         self.delexec = []
  1290         self.copies = []
  1296         self.copies = []
  1291         self.wc = None
  1297         self.wc = None
  1292         self.cwd = encoding.getcwd()
  1298         self.cwd = encoding.getcwd()
  1293 
  1299 
  1294         created = False
  1300         created = False
  1295         if os.path.isfile(os.path.join(path, '.svn', 'entries')):
  1301         if os.path.isfile(os.path.join(path, b'.svn', b'entries')):
  1296             self.wc = os.path.realpath(path)
  1302             self.wc = os.path.realpath(path)
  1297             self.run0('update')
  1303             self.run0(b'update')
  1298         else:
  1304         else:
  1299             if not re.search(br'^(file|http|https|svn|svn\+ssh)\://', path):
  1305             if not re.search(br'^(file|http|https|svn|svn\+ssh)\://', path):
  1300                 path = os.path.realpath(path)
  1306                 path = os.path.realpath(path)
  1301                 if os.path.isdir(os.path.dirname(path)):
  1307                 if os.path.isdir(os.path.dirname(path)):
  1302                     if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
  1308                     if not os.path.exists(
       
  1309                         os.path.join(path, b'db', b'fs-type')
       
  1310                     ):
  1303                         ui.status(
  1311                         ui.status(
  1304                             _("initializing svn repository '%s'\n")
  1312                             _(b"initializing svn repository '%s'\n")
  1305                             % os.path.basename(path)
  1313                             % os.path.basename(path)
  1306                         )
  1314                         )
  1307                         commandline(ui, 'svnadmin').run0('create', path)
  1315                         commandline(ui, b'svnadmin').run0(b'create', path)
  1308                         created = path
  1316                         created = path
  1309                     path = util.normpath(path)
  1317                     path = util.normpath(path)
  1310                     if not path.startswith('/'):
  1318                     if not path.startswith(b'/'):
  1311                         path = '/' + path
  1319                         path = b'/' + path
  1312                     path = 'file://' + path
  1320                     path = b'file://' + path
  1313 
  1321 
  1314             wcpath = os.path.join(
  1322             wcpath = os.path.join(
  1315                 encoding.getcwd(), os.path.basename(path) + '-wc'
  1323                 encoding.getcwd(), os.path.basename(path) + b'-wc'
  1316             )
  1324             )
  1317             ui.status(
  1325             ui.status(
  1318                 _("initializing svn working copy '%s'\n")
  1326                 _(b"initializing svn working copy '%s'\n")
  1319                 % os.path.basename(wcpath)
  1327                 % os.path.basename(wcpath)
  1320             )
  1328             )
  1321             self.run0('checkout', path, wcpath)
  1329             self.run0(b'checkout', path, wcpath)
  1322 
  1330 
  1323             self.wc = wcpath
  1331             self.wc = wcpath
  1324         self.opener = vfsmod.vfs(self.wc)
  1332         self.opener = vfsmod.vfs(self.wc)
  1325         self.wopener = vfsmod.vfs(self.wc)
  1333         self.wopener = vfsmod.vfs(self.wc)
  1326         self.childmap = mapfile(ui, self.join('hg-childmap'))
  1334         self.childmap = mapfile(ui, self.join(b'hg-childmap'))
  1327         if util.checkexec(self.wc):
  1335         if util.checkexec(self.wc):
  1328             self.is_exec = util.isexec
  1336             self.is_exec = util.isexec
  1329         else:
  1337         else:
  1330             self.is_exec = None
  1338             self.is_exec = None
  1331 
  1339 
  1332         if created:
  1340         if created:
  1333             hook = os.path.join(created, 'hooks', 'pre-revprop-change')
  1341             hook = os.path.join(created, b'hooks', b'pre-revprop-change')
  1334             fp = open(hook, 'wb')
  1342             fp = open(hook, b'wb')
  1335             fp.write(pre_revprop_change)
  1343             fp.write(pre_revprop_change)
  1336             fp.close()
  1344             fp.close()
  1337             util.setflags(hook, False, True)
  1345             util.setflags(hook, False, True)
  1338 
  1346 
  1339         output = self.run0('info')
  1347         output = self.run0(b'info')
  1340         self.uuid = self.uuid_re.search(output).group(1).strip()
  1348         self.uuid = self.uuid_re.search(output).group(1).strip()
  1341 
  1349 
  1342     def wjoin(self, *names):
  1350     def wjoin(self, *names):
  1343         return os.path.join(self.wc, *names)
  1351         return os.path.join(self.wc, *names)
  1344 
  1352 
  1346     def manifest(self):
  1354     def manifest(self):
  1347         # As of svn 1.7, the "add" command fails when receiving
  1355         # As of svn 1.7, the "add" command fails when receiving
  1348         # already tracked entries, so we have to track and filter them
  1356         # already tracked entries, so we have to track and filter them
  1349         # ourselves.
  1357         # ourselves.
  1350         m = set()
  1358         m = set()
  1351         output = self.run0('ls', recursive=True, xml=True)
  1359         output = self.run0(b'ls', recursive=True, xml=True)
  1352         doc = xml.dom.minidom.parseString(output)
  1360         doc = xml.dom.minidom.parseString(output)
  1353         for e in doc.getElementsByTagName(r'entry'):
  1361         for e in doc.getElementsByTagName(r'entry'):
  1354             for n in e.childNodes:
  1362             for n in e.childNodes:
  1355                 if n.nodeType != n.ELEMENT_NODE or n.tagName != r'name':
  1363                 if n.nodeType != n.ELEMENT_NODE or n.tagName != r'name':
  1356                     continue
  1364                     continue
  1365                 m.add(encoding.unitolocal(name))
  1373                 m.add(encoding.unitolocal(name))
  1366                 break
  1374                 break
  1367         return m
  1375         return m
  1368 
  1376 
  1369     def putfile(self, filename, flags, data):
  1377     def putfile(self, filename, flags, data):
  1370         if 'l' in flags:
  1378         if b'l' in flags:
  1371             self.wopener.symlink(data, filename)
  1379             self.wopener.symlink(data, filename)
  1372         else:
  1380         else:
  1373             try:
  1381             try:
  1374                 if os.path.islink(self.wjoin(filename)):
  1382                 if os.path.islink(self.wjoin(filename)):
  1375                     os.unlink(filename)
  1383                     os.unlink(filename)
  1385 
  1393 
  1386             self.wopener.write(filename, data)
  1394             self.wopener.write(filename, data)
  1387 
  1395 
  1388             if self.is_exec:
  1396             if self.is_exec:
  1389                 if wasexec:
  1397                 if wasexec:
  1390                     if 'x' not in flags:
  1398                     if b'x' not in flags:
  1391                         self.delexec.append(filename)
  1399                         self.delexec.append(filename)
  1392                 else:
  1400                 else:
  1393                     if 'x' in flags:
  1401                     if b'x' in flags:
  1394                         self.setexec.append(filename)
  1402                         self.setexec.append(filename)
  1395                 util.setflags(self.wjoin(filename), False, 'x' in flags)
  1403                 util.setflags(self.wjoin(filename), False, b'x' in flags)
  1396 
  1404 
  1397     def _copyfile(self, source, dest):
  1405     def _copyfile(self, source, dest):
  1398         # SVN's copy command pukes if the destination file exists, but
  1406         # SVN's copy command pukes if the destination file exists, but
  1399         # our copyfile method expects to record a copy that has
  1407         # our copyfile method expects to record a copy that has
  1400         # already occurred.  Cross the semantic gap.
  1408         # already occurred.  Cross the semantic gap.
  1401         wdest = self.wjoin(dest)
  1409         wdest = self.wjoin(dest)
  1402         exists = os.path.lexists(wdest)
  1410         exists = os.path.lexists(wdest)
  1403         if exists:
  1411         if exists:
  1404             fd, tempname = pycompat.mkstemp(
  1412             fd, tempname = pycompat.mkstemp(
  1405                 prefix='hg-copy-', dir=os.path.dirname(wdest)
  1413                 prefix=b'hg-copy-', dir=os.path.dirname(wdest)
  1406             )
  1414             )
  1407             os.close(fd)
  1415             os.close(fd)
  1408             os.unlink(tempname)
  1416             os.unlink(tempname)
  1409             os.rename(wdest, tempname)
  1417             os.rename(wdest, tempname)
  1410         try:
  1418         try:
  1411             self.run0('copy', source, dest)
  1419             self.run0(b'copy', source, dest)
  1412         finally:
  1420         finally:
  1413             self.manifest.add(dest)
  1421             self.manifest.add(dest)
  1414             if exists:
  1422             if exists:
  1415                 try:
  1423                 try:
  1416                     os.unlink(wdest)
  1424                     os.unlink(wdest)
  1422         dirs = set()
  1430         dirs = set()
  1423         for f in files:
  1431         for f in files:
  1424             if os.path.isdir(self.wjoin(f)):
  1432             if os.path.isdir(self.wjoin(f)):
  1425                 dirs.add(f)
  1433                 dirs.add(f)
  1426             i = len(f)
  1434             i = len(f)
  1427             for i in iter(lambda: f.rfind('/', 0, i), -1):
  1435             for i in iter(lambda: f.rfind(b'/', 0, i), -1):
  1428                 dirs.add(f[:i])
  1436                 dirs.add(f[:i])
  1429         return dirs
  1437         return dirs
  1430 
  1438 
  1431     def add_dirs(self, files):
  1439     def add_dirs(self, files):
  1432         add_dirs = [
  1440         add_dirs = [
  1433             d for d in sorted(self.dirs_of(files)) if d not in self.manifest
  1441             d for d in sorted(self.dirs_of(files)) if d not in self.manifest
  1434         ]
  1442         ]
  1435         if add_dirs:
  1443         if add_dirs:
  1436             self.manifest.update(add_dirs)
  1444             self.manifest.update(add_dirs)
  1437             self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
  1445             self.xargs(add_dirs, b'add', non_recursive=True, quiet=True)
  1438         return add_dirs
  1446         return add_dirs
  1439 
  1447 
  1440     def add_files(self, files):
  1448     def add_files(self, files):
  1441         files = [f for f in files if f not in self.manifest]
  1449         files = [f for f in files if f not in self.manifest]
  1442         if files:
  1450         if files:
  1443             self.manifest.update(files)
  1451             self.manifest.update(files)
  1444             self.xargs(files, 'add', quiet=True)
  1452             self.xargs(files, b'add', quiet=True)
  1445         return files
  1453         return files
  1446 
  1454 
  1447     def addchild(self, parent, child):
  1455     def addchild(self, parent, child):
  1448         self.childmap[parent] = child
  1456         self.childmap[parent] = child
  1449 
  1457 
  1450     def revid(self, rev):
  1458     def revid(self, rev):
  1451         return "svn:%s@%s" % (self.uuid, rev)
  1459         return b"svn:%s@%s" % (self.uuid, rev)
  1452 
  1460 
  1453     def putcommit(
  1461     def putcommit(
  1454         self, files, copies, parents, commit, source, revmap, full, cleanp2
  1462         self, files, copies, parents, commit, source, revmap, full, cleanp2
  1455     ):
  1463     ):
  1456         for parent in parents:
  1464         for parent in parents:
  1478         if self.copies:
  1486         if self.copies:
  1479             for s, d in self.copies:
  1487             for s, d in self.copies:
  1480                 self._copyfile(s, d)
  1488                 self._copyfile(s, d)
  1481             self.copies = []
  1489             self.copies = []
  1482         if self.delete:
  1490         if self.delete:
  1483             self.xargs(self.delete, 'delete')
  1491             self.xargs(self.delete, b'delete')
  1484             for f in self.delete:
  1492             for f in self.delete:
  1485                 self.manifest.remove(f)
  1493                 self.manifest.remove(f)
  1486             self.delete = []
  1494             self.delete = []
  1487         entries.update(self.add_files(files.difference(entries)))
  1495         entries.update(self.add_files(files.difference(entries)))
  1488         if self.delexec:
  1496         if self.delexec:
  1489             self.xargs(self.delexec, 'propdel', 'svn:executable')
  1497             self.xargs(self.delexec, b'propdel', b'svn:executable')
  1490             self.delexec = []
  1498             self.delexec = []
  1491         if self.setexec:
  1499         if self.setexec:
  1492             self.xargs(self.setexec, 'propset', 'svn:executable', '*')
  1500             self.xargs(self.setexec, b'propset', b'svn:executable', b'*')
  1493             self.setexec = []
  1501             self.setexec = []
  1494 
  1502 
  1495         fd, messagefile = pycompat.mkstemp(prefix='hg-convert-')
  1503         fd, messagefile = pycompat.mkstemp(prefix=b'hg-convert-')
  1496         fp = os.fdopen(fd, r'wb')
  1504         fp = os.fdopen(fd, r'wb')
  1497         fp.write(util.tonativeeol(commit.desc))
  1505         fp.write(util.tonativeeol(commit.desc))
  1498         fp.close()
  1506         fp.close()
  1499         try:
  1507         try:
  1500             output = self.run0(
  1508             output = self.run0(
  1501                 'commit',
  1509                 b'commit',
  1502                 username=stringutil.shortuser(commit.author),
  1510                 username=stringutil.shortuser(commit.author),
  1503                 file=messagefile,
  1511                 file=messagefile,
  1504                 encoding='utf-8',
  1512                 encoding=b'utf-8',
  1505             )
  1513             )
  1506             try:
  1514             try:
  1507                 rev = self.commit_re.search(output).group(1)
  1515                 rev = self.commit_re.search(output).group(1)
  1508             except AttributeError:
  1516             except AttributeError:
  1509                 if not files:
  1517                 if not files:
  1510                     return parents[0] if parents else 'None'
  1518                     return parents[0] if parents else b'None'
  1511                 self.ui.warn(_('unexpected svn output:\n'))
  1519                 self.ui.warn(_(b'unexpected svn output:\n'))
  1512                 self.ui.warn(output)
  1520                 self.ui.warn(output)
  1513                 raise error.Abort(_('unable to cope with svn output'))
  1521                 raise error.Abort(_(b'unable to cope with svn output'))
  1514             if commit.rev:
  1522             if commit.rev:
  1515                 self.run(
  1523                 self.run(
  1516                     'propset',
  1524                     b'propset',
  1517                     'hg:convert-rev',
  1525                     b'hg:convert-rev',
  1518                     commit.rev,
  1526                     commit.rev,
  1519                     revprop=True,
  1527                     revprop=True,
  1520                     revision=rev,
  1528                     revision=rev,
  1521                 )
  1529                 )
  1522             if commit.branch and commit.branch != 'default':
  1530             if commit.branch and commit.branch != b'default':
  1523                 self.run(
  1531                 self.run(
  1524                     'propset',
  1532                     b'propset',
  1525                     'hg:convert-branch',
  1533                     b'hg:convert-branch',
  1526                     commit.branch,
  1534                     commit.branch,
  1527                     revprop=True,
  1535                     revprop=True,
  1528                     revision=rev,
  1536                     revision=rev,
  1529                 )
  1537                 )
  1530             for parent in parents:
  1538             for parent in parents:
  1532             return self.revid(rev)
  1540             return self.revid(rev)
  1533         finally:
  1541         finally:
  1534             os.unlink(messagefile)
  1542             os.unlink(messagefile)
  1535 
  1543 
  1536     def puttags(self, tags):
  1544     def puttags(self, tags):
  1537         self.ui.warn(_('writing Subversion tags is not yet implemented\n'))
  1545         self.ui.warn(_(b'writing Subversion tags is not yet implemented\n'))
  1538         return None, None
  1546         return None, None
  1539 
  1547 
  1540     def hascommitfrommap(self, rev):
  1548     def hascommitfrommap(self, rev):
  1541         # We trust that revisions referenced in a map still is present
  1549         # We trust that revisions referenced in a map still is present
  1542         # TODO: implement something better if necessary and feasible
  1550         # TODO: implement something better if necessary and feasible
  1547         # repository and childmap would not list all revisions. Too bad.
  1555         # repository and childmap would not list all revisions. Too bad.
  1548         if rev in self.childmap:
  1556         if rev in self.childmap:
  1549             return True
  1557             return True
  1550         raise error.Abort(
  1558         raise error.Abort(
  1551             _(
  1559             _(
  1552                 'splice map revision %s not found in subversion '
  1560                 b'splice map revision %s not found in subversion '
  1553                 'child map (revision lookups are not implemented)'
  1561                 b'child map (revision lookups are not implemented)'
  1554             )
  1562             )
  1555             % rev
  1563             % rev
  1556         )
  1564         )