eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/convert/gnuarch.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # gnuarch.py - GNU Arch support for the convert extension
       
     2 #
       
     3 #  Copyright 2008, 2009 Aleix Conchillo Flaque <aleix@member.fsf.org>
       
     4 #  and others
       
     5 #
       
     6 # This software may be used and distributed according to the terms of the
       
     7 # GNU General Public License version 2 or any later version.
       
     8 
       
     9 from common import NoRepo, commandline, commit, converter_source
       
    10 from mercurial.i18n import _
       
    11 from mercurial import encoding, util
       
    12 import os, shutil, tempfile, stat
       
    13 from email.Parser import Parser
       
    14 
       
    15 class gnuarch_source(converter_source, commandline):
       
    16 
       
    17     class gnuarch_rev(object):
       
    18         def __init__(self, rev):
       
    19             self.rev = rev
       
    20             self.summary = ''
       
    21             self.date = None
       
    22             self.author = ''
       
    23             self.continuationof = None
       
    24             self.add_files = []
       
    25             self.mod_files = []
       
    26             self.del_files = []
       
    27             self.ren_files = {}
       
    28             self.ren_dirs = {}
       
    29 
       
    30     def __init__(self, ui, path, rev=None):
       
    31         super(gnuarch_source, self).__init__(ui, path, rev=rev)
       
    32 
       
    33         if not os.path.exists(os.path.join(path, '{arch}')):
       
    34             raise NoRepo(_("%s does not look like a GNU Arch repository")
       
    35                          % path)
       
    36 
       
    37         # Could use checktool, but we want to check for baz or tla.
       
    38         self.execmd = None
       
    39         if util.find_exe('baz'):
       
    40             self.execmd = 'baz'
       
    41         else:
       
    42             if util.find_exe('tla'):
       
    43                 self.execmd = 'tla'
       
    44             else:
       
    45                 raise util.Abort(_('cannot find a GNU Arch tool'))
       
    46 
       
    47         commandline.__init__(self, ui, self.execmd)
       
    48 
       
    49         self.path = os.path.realpath(path)
       
    50         self.tmppath = None
       
    51 
       
    52         self.treeversion = None
       
    53         self.lastrev = None
       
    54         self.changes = {}
       
    55         self.parents = {}
       
    56         self.tags = {}
       
    57         self.catlogparser = Parser()
       
    58         self.encoding = encoding.encoding
       
    59         self.archives = []
       
    60 
       
    61     def before(self):
       
    62         # Get registered archives
       
    63         self.archives = [i.rstrip('\n')
       
    64                          for i in self.runlines0('archives', '-n')]
       
    65 
       
    66         if self.execmd == 'tla':
       
    67             output = self.run0('tree-version', self.path)
       
    68         else:
       
    69             output = self.run0('tree-version', '-d', self.path)
       
    70         self.treeversion = output.strip()
       
    71 
       
    72         # Get name of temporary directory
       
    73         version = self.treeversion.split('/')
       
    74         self.tmppath = os.path.join(tempfile.gettempdir(),
       
    75                                     'hg-%s' % version[1])
       
    76 
       
    77         # Generate parents dictionary
       
    78         self.parents[None] = []
       
    79         treeversion = self.treeversion
       
    80         child = None
       
    81         while treeversion:
       
    82             self.ui.status(_('analyzing tree version %s...\n') % treeversion)
       
    83 
       
    84             archive = treeversion.split('/')[0]
       
    85             if archive not in self.archives:
       
    86                 self.ui.status(_('tree analysis stopped because it points to '
       
    87                                  'an unregistered archive %s...\n') % archive)
       
    88                 break
       
    89 
       
    90             # Get the complete list of revisions for that tree version
       
    91             output, status = self.runlines('revisions', '-r', '-f', treeversion)
       
    92             self.checkexit(status, 'failed retrieveing revisions for %s'
       
    93                            % treeversion)
       
    94 
       
    95             # No new iteration unless a revision has a continuation-of header
       
    96             treeversion = None
       
    97 
       
    98             for l in output:
       
    99                 rev = l.strip()
       
   100                 self.changes[rev] = self.gnuarch_rev(rev)
       
   101                 self.parents[rev] = []
       
   102 
       
   103                 # Read author, date and summary
       
   104                 catlog, status = self.run('cat-log', '-d', self.path, rev)
       
   105                 if status:
       
   106                     catlog  = self.run0('cat-archive-log', rev)
       
   107                 self._parsecatlog(catlog, rev)
       
   108 
       
   109                 # Populate the parents map
       
   110                 self.parents[child].append(rev)
       
   111 
       
   112                 # Keep track of the current revision as the child of the next
       
   113                 # revision scanned
       
   114                 child = rev
       
   115 
       
   116                 # Check if we have to follow the usual incremental history
       
   117                 # or if we have to 'jump' to a different treeversion given
       
   118                 # by the continuation-of header.
       
   119                 if self.changes[rev].continuationof:
       
   120                     treeversion = '--'.join(
       
   121                         self.changes[rev].continuationof.split('--')[:-1])
       
   122                     break
       
   123 
       
   124                 # If we reached a base-0 revision w/o any continuation-of
       
   125                 # header, it means the tree history ends here.
       
   126                 if rev[-6:] == 'base-0':
       
   127                     break
       
   128 
       
   129     def after(self):
       
   130         self.ui.debug('cleaning up %s\n' % self.tmppath)
       
   131         shutil.rmtree(self.tmppath, ignore_errors=True)
       
   132 
       
   133     def getheads(self):
       
   134         return self.parents[None]
       
   135 
       
   136     def getfile(self, name, rev):
       
   137         if rev != self.lastrev:
       
   138             raise util.Abort(_('internal calling inconsistency'))
       
   139 
       
   140         # Raise IOError if necessary (i.e. deleted files).
       
   141         if not os.path.lexists(os.path.join(self.tmppath, name)):
       
   142             raise IOError
       
   143 
       
   144         return self._getfile(name, rev)
       
   145 
       
   146     def getchanges(self, rev):
       
   147         self._update(rev)
       
   148         changes = []
       
   149         copies = {}
       
   150 
       
   151         for f in self.changes[rev].add_files:
       
   152             changes.append((f, rev))
       
   153 
       
   154         for f in self.changes[rev].mod_files:
       
   155             changes.append((f, rev))
       
   156 
       
   157         for f in self.changes[rev].del_files:
       
   158             changes.append((f, rev))
       
   159 
       
   160         for src in self.changes[rev].ren_files:
       
   161             to = self.changes[rev].ren_files[src]
       
   162             changes.append((src, rev))
       
   163             changes.append((to, rev))
       
   164             copies[to] = src
       
   165 
       
   166         for src in self.changes[rev].ren_dirs:
       
   167             to = self.changes[rev].ren_dirs[src]
       
   168             chgs, cps = self._rendirchanges(src, to)
       
   169             changes += [(f, rev) for f in chgs]
       
   170             copies.update(cps)
       
   171 
       
   172         self.lastrev = rev
       
   173         return sorted(set(changes)), copies
       
   174 
       
   175     def getcommit(self, rev):
       
   176         changes = self.changes[rev]
       
   177         return commit(author=changes.author, date=changes.date,
       
   178                       desc=changes.summary, parents=self.parents[rev], rev=rev)
       
   179 
       
   180     def gettags(self):
       
   181         return self.tags
       
   182 
       
   183     def _execute(self, cmd, *args, **kwargs):
       
   184         cmdline = [self.execmd, cmd]
       
   185         cmdline += args
       
   186         cmdline = [util.shellquote(arg) for arg in cmdline]
       
   187         cmdline += ['>', util.nulldev, '2>', util.nulldev]
       
   188         cmdline = util.quotecommand(' '.join(cmdline))
       
   189         self.ui.debug(cmdline, '\n')
       
   190         return os.system(cmdline)
       
   191 
       
   192     def _update(self, rev):
       
   193         self.ui.debug('applying revision %s...\n' % rev)
       
   194         changeset, status = self.runlines('replay', '-d', self.tmppath,
       
   195                                               rev)
       
   196         if status:
       
   197             # Something went wrong while merging (baz or tla
       
   198             # issue?), get latest revision and try from there
       
   199             shutil.rmtree(self.tmppath, ignore_errors=True)
       
   200             self._obtainrevision(rev)
       
   201         else:
       
   202             old_rev = self.parents[rev][0]
       
   203             self.ui.debug('computing changeset between %s and %s...\n'
       
   204                           % (old_rev, rev))
       
   205             self._parsechangeset(changeset, rev)
       
   206 
       
   207     def _getfile(self, name, rev):
       
   208         mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
       
   209         if stat.S_ISLNK(mode):
       
   210             data = os.readlink(os.path.join(self.tmppath, name))
       
   211             mode = mode and 'l' or ''
       
   212         else:
       
   213             data = open(os.path.join(self.tmppath, name), 'rb').read()
       
   214             mode = (mode & 0111) and 'x' or ''
       
   215         return data, mode
       
   216 
       
   217     def _exclude(self, name):
       
   218         exclude = ['{arch}', '.arch-ids', '.arch-inventory']
       
   219         for exc in exclude:
       
   220             if name.find(exc) != -1:
       
   221                 return True
       
   222         return False
       
   223 
       
   224     def _readcontents(self, path):
       
   225         files = []
       
   226         contents = os.listdir(path)
       
   227         while len(contents) > 0:
       
   228             c = contents.pop()
       
   229             p = os.path.join(path, c)
       
   230             # os.walk could be used, but here we avoid internal GNU
       
   231             # Arch files and directories, thus saving a lot time.
       
   232             if not self._exclude(p):
       
   233                 if os.path.isdir(p):
       
   234                     contents += [os.path.join(c, f) for f in os.listdir(p)]
       
   235                 else:
       
   236                     files.append(c)
       
   237         return files
       
   238 
       
   239     def _rendirchanges(self, src, dest):
       
   240         changes = []
       
   241         copies = {}
       
   242         files = self._readcontents(os.path.join(self.tmppath, dest))
       
   243         for f in files:
       
   244             s = os.path.join(src, f)
       
   245             d = os.path.join(dest, f)
       
   246             changes.append(s)
       
   247             changes.append(d)
       
   248             copies[d] = s
       
   249         return changes, copies
       
   250 
       
   251     def _obtainrevision(self, rev):
       
   252         self.ui.debug('obtaining revision %s...\n' % rev)
       
   253         output = self._execute('get', rev, self.tmppath)
       
   254         self.checkexit(output)
       
   255         self.ui.debug('analyzing revision %s...\n' % rev)
       
   256         files = self._readcontents(self.tmppath)
       
   257         self.changes[rev].add_files += files
       
   258 
       
   259     def _stripbasepath(self, path):
       
   260         if path.startswith('./'):
       
   261             return path[2:]
       
   262         return path
       
   263 
       
   264     def _parsecatlog(self, data, rev):
       
   265         try:
       
   266             catlog = self.catlogparser.parsestr(data)
       
   267 
       
   268             # Commit date
       
   269             self.changes[rev].date = util.datestr(
       
   270                 util.strdate(catlog['Standard-date'],
       
   271                              '%Y-%m-%d %H:%M:%S'))
       
   272 
       
   273             # Commit author
       
   274             self.changes[rev].author = self.recode(catlog['Creator'])
       
   275 
       
   276             # Commit description
       
   277             self.changes[rev].summary = '\n\n'.join((catlog['Summary'],
       
   278                                                     catlog.get_payload()))
       
   279             self.changes[rev].summary = self.recode(self.changes[rev].summary)
       
   280 
       
   281             # Commit revision origin when dealing with a branch or tag
       
   282             if 'Continuation-of' in catlog:
       
   283                 self.changes[rev].continuationof = self.recode(
       
   284                     catlog['Continuation-of'])
       
   285         except Exception:
       
   286             raise util.Abort(_('could not parse cat-log of %s') % rev)
       
   287 
       
   288     def _parsechangeset(self, data, rev):
       
   289         for l in data:
       
   290             l = l.strip()
       
   291             # Added file (ignore added directory)
       
   292             if l.startswith('A') and not l.startswith('A/'):
       
   293                 file = self._stripbasepath(l[1:].strip())
       
   294                 if not self._exclude(file):
       
   295                     self.changes[rev].add_files.append(file)
       
   296             # Deleted file (ignore deleted directory)
       
   297             elif l.startswith('D') and not l.startswith('D/'):
       
   298                 file = self._stripbasepath(l[1:].strip())
       
   299                 if not self._exclude(file):
       
   300                     self.changes[rev].del_files.append(file)
       
   301             # Modified binary file
       
   302             elif l.startswith('Mb'):
       
   303                 file = self._stripbasepath(l[2:].strip())
       
   304                 if not self._exclude(file):
       
   305                     self.changes[rev].mod_files.append(file)
       
   306             # Modified link
       
   307             elif l.startswith('M->'):
       
   308                 file = self._stripbasepath(l[3:].strip())
       
   309                 if not self._exclude(file):
       
   310                     self.changes[rev].mod_files.append(file)
       
   311             # Modified file
       
   312             elif l.startswith('M'):
       
   313                 file = self._stripbasepath(l[1:].strip())
       
   314                 if not self._exclude(file):
       
   315                     self.changes[rev].mod_files.append(file)
       
   316             # Renamed file (or link)
       
   317             elif l.startswith('=>'):
       
   318                 files = l[2:].strip().split(' ')
       
   319                 if len(files) == 1:
       
   320                     files = l[2:].strip().split('\t')
       
   321                 src = self._stripbasepath(files[0])
       
   322                 dst = self._stripbasepath(files[1])
       
   323                 if not self._exclude(src) and not self._exclude(dst):
       
   324                     self.changes[rev].ren_files[src] = dst
       
   325             # Conversion from file to link or from link to file (modified)
       
   326             elif l.startswith('ch'):
       
   327                 file = self._stripbasepath(l[2:].strip())
       
   328                 if not self._exclude(file):
       
   329                     self.changes[rev].mod_files.append(file)
       
   330             # Renamed directory
       
   331             elif l.startswith('/>'):
       
   332                 dirs = l[2:].strip().split(' ')
       
   333                 if len(dirs) == 1:
       
   334                     dirs = l[2:].strip().split('\t')
       
   335                 src = self._stripbasepath(dirs[0])
       
   336                 dst = self._stripbasepath(dirs[1])
       
   337                 if not self._exclude(src) and not self._exclude(dst):
       
   338                     self.changes[rev].ren_dirs[src] = dst