eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/convert/common.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # common.py - common code for the convert extension
       
     2 #
       
     3 #  Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 import base64, errno
       
     9 import os
       
    10 import cPickle as pickle
       
    11 from mercurial import util
       
    12 from mercurial.i18n import _
       
    13 
       
    14 def encodeargs(args):
       
    15     def encodearg(s):
       
    16         lines = base64.encodestring(s)
       
    17         lines = [l.splitlines()[0] for l in lines]
       
    18         return ''.join(lines)
       
    19 
       
    20     s = pickle.dumps(args)
       
    21     return encodearg(s)
       
    22 
       
    23 def decodeargs(s):
       
    24     s = base64.decodestring(s)
       
    25     return pickle.loads(s)
       
    26 
       
    27 class MissingTool(Exception):
       
    28     pass
       
    29 
       
    30 def checktool(exe, name=None, abort=True):
       
    31     name = name or exe
       
    32     if not util.find_exe(exe):
       
    33         exc = abort and util.Abort or MissingTool
       
    34         raise exc(_('cannot find required "%s" tool') % name)
       
    35 
       
    36 class NoRepo(Exception):
       
    37     pass
       
    38 
       
    39 SKIPREV = 'SKIP'
       
    40 
       
    41 class commit(object):
       
    42     def __init__(self, author, date, desc, parents, branch=None, rev=None,
       
    43                  extra={}, sortkey=None):
       
    44         self.author = author or 'unknown'
       
    45         self.date = date or '0 0'
       
    46         self.desc = desc
       
    47         self.parents = parents
       
    48         self.branch = branch
       
    49         self.rev = rev
       
    50         self.extra = extra
       
    51         self.sortkey = sortkey
       
    52 
       
    53 class converter_source(object):
       
    54     """Conversion source interface"""
       
    55 
       
    56     def __init__(self, ui, path=None, rev=None):
       
    57         """Initialize conversion source (or raise NoRepo("message")
       
    58         exception if path is not a valid repository)"""
       
    59         self.ui = ui
       
    60         self.path = path
       
    61         self.rev = rev
       
    62 
       
    63         self.encoding = 'utf-8'
       
    64 
       
    65     def before(self):
       
    66         pass
       
    67 
       
    68     def after(self):
       
    69         pass
       
    70 
       
    71     def setrevmap(self, revmap):
       
    72         """set the map of already-converted revisions"""
       
    73         pass
       
    74 
       
    75     def getheads(self):
       
    76         """Return a list of this repository's heads"""
       
    77         raise NotImplementedError()
       
    78 
       
    79     def getfile(self, name, rev):
       
    80         """Return a pair (data, mode) where data is the file content
       
    81         as a string and mode one of '', 'x' or 'l'. rev is the
       
    82         identifier returned by a previous call to getchanges(). Raise
       
    83         IOError to indicate that name was deleted in rev.
       
    84         """
       
    85         raise NotImplementedError()
       
    86 
       
    87     def getchanges(self, version):
       
    88         """Returns a tuple of (files, copies).
       
    89 
       
    90         files is a sorted list of (filename, id) tuples for all files
       
    91         changed between version and its first parent returned by
       
    92         getcommit(). id is the source revision id of the file.
       
    93 
       
    94         copies is a dictionary of dest: source
       
    95         """
       
    96         raise NotImplementedError()
       
    97 
       
    98     def getcommit(self, version):
       
    99         """Return the commit object for version"""
       
   100         raise NotImplementedError()
       
   101 
       
   102     def gettags(self):
       
   103         """Return the tags as a dictionary of name: revision
       
   104 
       
   105         Tag names must be UTF-8 strings.
       
   106         """
       
   107         raise NotImplementedError()
       
   108 
       
   109     def recode(self, s, encoding=None):
       
   110         if not encoding:
       
   111             encoding = self.encoding or 'utf-8'
       
   112 
       
   113         if isinstance(s, unicode):
       
   114             return s.encode("utf-8")
       
   115         try:
       
   116             return s.decode(encoding).encode("utf-8")
       
   117         except:
       
   118             try:
       
   119                 return s.decode("latin-1").encode("utf-8")
       
   120             except:
       
   121                 return s.decode(encoding, "replace").encode("utf-8")
       
   122 
       
   123     def getchangedfiles(self, rev, i):
       
   124         """Return the files changed by rev compared to parent[i].
       
   125 
       
   126         i is an index selecting one of the parents of rev.  The return
       
   127         value should be the list of files that are different in rev and
       
   128         this parent.
       
   129 
       
   130         If rev has no parents, i is None.
       
   131 
       
   132         This function is only needed to support --filemap
       
   133         """
       
   134         raise NotImplementedError()
       
   135 
       
   136     def converted(self, rev, sinkrev):
       
   137         '''Notify the source that a revision has been converted.'''
       
   138         pass
       
   139 
       
   140     def hasnativeorder(self):
       
   141         """Return true if this source has a meaningful, native revision
       
   142         order. For instance, Mercurial revisions are store sequentially
       
   143         while there is no such global ordering with Darcs.
       
   144         """
       
   145         return False
       
   146 
       
   147     def lookuprev(self, rev):
       
   148         """If rev is a meaningful revision reference in source, return
       
   149         the referenced identifier in the same format used by getcommit().
       
   150         return None otherwise.
       
   151         """
       
   152         return None
       
   153 
       
   154 class converter_sink(object):
       
   155     """Conversion sink (target) interface"""
       
   156 
       
   157     def __init__(self, ui, path):
       
   158         """Initialize conversion sink (or raise NoRepo("message")
       
   159         exception if path is not a valid repository)
       
   160 
       
   161         created is a list of paths to remove if a fatal error occurs
       
   162         later"""
       
   163         self.ui = ui
       
   164         self.path = path
       
   165         self.created = []
       
   166 
       
   167     def getheads(self):
       
   168         """Return a list of this repository's heads"""
       
   169         raise NotImplementedError()
       
   170 
       
   171     def revmapfile(self):
       
   172         """Path to a file that will contain lines
       
   173         source_rev_id sink_rev_id
       
   174         mapping equivalent revision identifiers for each system."""
       
   175         raise NotImplementedError()
       
   176 
       
   177     def authorfile(self):
       
   178         """Path to a file that will contain lines
       
   179         srcauthor=dstauthor
       
   180         mapping equivalent authors identifiers for each system."""
       
   181         return None
       
   182 
       
   183     def putcommit(self, files, copies, parents, commit, source, revmap):
       
   184         """Create a revision with all changed files listed in 'files'
       
   185         and having listed parents. 'commit' is a commit object
       
   186         containing at a minimum the author, date, and message for this
       
   187         changeset.  'files' is a list of (path, version) tuples,
       
   188         'copies' is a dictionary mapping destinations to sources,
       
   189         'source' is the source repository, and 'revmap' is a mapfile
       
   190         of source revisions to converted revisions. Only getfile() and
       
   191         lookuprev() should be called on 'source'.
       
   192 
       
   193         Note that the sink repository is not told to update itself to
       
   194         a particular revision (or even what that revision would be)
       
   195         before it receives the file data.
       
   196         """
       
   197         raise NotImplementedError()
       
   198 
       
   199     def puttags(self, tags):
       
   200         """Put tags into sink.
       
   201 
       
   202         tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
       
   203         Return a pair (tag_revision, tag_parent_revision), or (None, None)
       
   204         if nothing was changed.
       
   205         """
       
   206         raise NotImplementedError()
       
   207 
       
   208     def setbranch(self, branch, pbranches):
       
   209         """Set the current branch name. Called before the first putcommit
       
   210         on the branch.
       
   211         branch: branch name for subsequent commits
       
   212         pbranches: (converted parent revision, parent branch) tuples"""
       
   213         pass
       
   214 
       
   215     def setfilemapmode(self, active):
       
   216         """Tell the destination that we're using a filemap
       
   217 
       
   218         Some converter_sources (svn in particular) can claim that a file
       
   219         was changed in a revision, even if there was no change.  This method
       
   220         tells the destination that we're using a filemap and that it should
       
   221         filter empty revisions.
       
   222         """
       
   223         pass
       
   224 
       
   225     def before(self):
       
   226         pass
       
   227 
       
   228     def after(self):
       
   229         pass
       
   230 
       
   231 
       
   232 class commandline(object):
       
   233     def __init__(self, ui, command):
       
   234         self.ui = ui
       
   235         self.command = command
       
   236 
       
   237     def prerun(self):
       
   238         pass
       
   239 
       
   240     def postrun(self):
       
   241         pass
       
   242 
       
   243     def _cmdline(self, cmd, *args, **kwargs):
       
   244         cmdline = [self.command, cmd] + list(args)
       
   245         for k, v in kwargs.iteritems():
       
   246             if len(k) == 1:
       
   247                 cmdline.append('-' + k)
       
   248             else:
       
   249                 cmdline.append('--' + k.replace('_', '-'))
       
   250             try:
       
   251                 if len(k) == 1:
       
   252                     cmdline.append('' + v)
       
   253                 else:
       
   254                     cmdline[-1] += '=' + v
       
   255             except TypeError:
       
   256                 pass
       
   257         cmdline = [util.shellquote(arg) for arg in cmdline]
       
   258         if not self.ui.debugflag:
       
   259             cmdline += ['2>', util.nulldev]
       
   260         cmdline += ['<', util.nulldev]
       
   261         cmdline = ' '.join(cmdline)
       
   262         return cmdline
       
   263 
       
   264     def _run(self, cmd, *args, **kwargs):
       
   265         cmdline = self._cmdline(cmd, *args, **kwargs)
       
   266         self.ui.debug('running: %s\n' % (cmdline,))
       
   267         self.prerun()
       
   268         try:
       
   269             return util.popen(cmdline)
       
   270         finally:
       
   271             self.postrun()
       
   272 
       
   273     def run(self, cmd, *args, **kwargs):
       
   274         fp = self._run(cmd, *args, **kwargs)
       
   275         output = fp.read()
       
   276         self.ui.debug(output)
       
   277         return output, fp.close()
       
   278 
       
   279     def runlines(self, cmd, *args, **kwargs):
       
   280         fp = self._run(cmd, *args, **kwargs)
       
   281         output = fp.readlines()
       
   282         self.ui.debug(''.join(output))
       
   283         return output, fp.close()
       
   284 
       
   285     def checkexit(self, status, output=''):
       
   286         if status:
       
   287             if output:
       
   288                 self.ui.warn(_('%s error:\n') % self.command)
       
   289                 self.ui.warn(output)
       
   290             msg = util.explain_exit(status)[0]
       
   291             raise util.Abort('%s %s' % (self.command, msg))
       
   292 
       
   293     def run0(self, cmd, *args, **kwargs):
       
   294         output, status = self.run(cmd, *args, **kwargs)
       
   295         self.checkexit(status, output)
       
   296         return output
       
   297 
       
   298     def runlines0(self, cmd, *args, **kwargs):
       
   299         output, status = self.runlines(cmd, *args, **kwargs)
       
   300         self.checkexit(status, ''.join(output))
       
   301         return output
       
   302 
       
   303     def getargmax(self):
       
   304         if '_argmax' in self.__dict__:
       
   305             return self._argmax
       
   306 
       
   307         # POSIX requires at least 4096 bytes for ARG_MAX
       
   308         self._argmax = 4096
       
   309         try:
       
   310             self._argmax = os.sysconf("SC_ARG_MAX")
       
   311         except:
       
   312             pass
       
   313 
       
   314         # Windows shells impose their own limits on command line length,
       
   315         # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
       
   316         # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
       
   317         # details about cmd.exe limitations.
       
   318 
       
   319         # Since ARG_MAX is for command line _and_ environment, lower our limit
       
   320         # (and make happy Windows shells while doing this).
       
   321 
       
   322         self._argmax = self._argmax / 2 - 1
       
   323         return self._argmax
       
   324 
       
   325     def limit_arglist(self, arglist, cmd, *args, **kwargs):
       
   326         limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
       
   327         bytes = 0
       
   328         fl = []
       
   329         for fn in arglist:
       
   330             b = len(fn) + 3
       
   331             if bytes + b < limit or len(fl) == 0:
       
   332                 fl.append(fn)
       
   333                 bytes += b
       
   334             else:
       
   335                 yield fl
       
   336                 fl = [fn]
       
   337                 bytes = b
       
   338         if fl:
       
   339             yield fl
       
   340 
       
   341     def xargs(self, arglist, cmd, *args, **kwargs):
       
   342         for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
       
   343             self.run0(cmd, *(list(args) + l), **kwargs)
       
   344 
       
   345 class mapfile(dict):
       
   346     def __init__(self, ui, path):
       
   347         super(mapfile, self).__init__()
       
   348         self.ui = ui
       
   349         self.path = path
       
   350         self.fp = None
       
   351         self.order = []
       
   352         self._read()
       
   353 
       
   354     def _read(self):
       
   355         if not self.path:
       
   356             return
       
   357         try:
       
   358             fp = open(self.path, 'r')
       
   359         except IOError, err:
       
   360             if err.errno != errno.ENOENT:
       
   361                 raise
       
   362             return
       
   363         for i, line in enumerate(fp):
       
   364             try:
       
   365                 key, value = line.splitlines()[0].rsplit(' ', 1)
       
   366             except ValueError:
       
   367                 raise util.Abort(
       
   368                     _('syntax error in %s(%d): key/value pair expected')
       
   369                     % (self.path, i + 1))
       
   370             if key not in self:
       
   371                 self.order.append(key)
       
   372             super(mapfile, self).__setitem__(key, value)
       
   373         fp.close()
       
   374 
       
   375     def __setitem__(self, key, value):
       
   376         if self.fp is None:
       
   377             try:
       
   378                 self.fp = open(self.path, 'a')
       
   379             except IOError, err:
       
   380                 raise util.Abort(_('could not open map file %r: %s') %
       
   381                                  (self.path, err.strerror))
       
   382         self.fp.write('%s %s\n' % (key, value))
       
   383         self.fp.flush()
       
   384         super(mapfile, self).__setitem__(key, value)
       
   385 
       
   386     def close(self):
       
   387         if self.fp:
       
   388             self.fp.close()
       
   389             self.fp = None