eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/convert/monotone.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # monotone.py - monotone support for the convert extension
       
     2 #
       
     3 #  Copyright 2008, 2009 Mikkel Fahnoe Jorgensen <mikkel@dvide.com> and
       
     4 #  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 import os, re
       
    10 from mercurial import util
       
    11 from common import NoRepo, commit, converter_source, checktool
       
    12 from common import commandline
       
    13 from mercurial.i18n import _
       
    14 
       
    15 class monotone_source(converter_source, commandline):
       
    16     def __init__(self, ui, path=None, rev=None):
       
    17         converter_source.__init__(self, ui, path, rev)
       
    18         commandline.__init__(self, ui, 'mtn')
       
    19 
       
    20         self.ui = ui
       
    21         self.path = path
       
    22 
       
    23         norepo = NoRepo(_("%s does not look like a monotone repository")
       
    24                         % path)
       
    25         if not os.path.exists(os.path.join(path, '_MTN')):
       
    26             # Could be a monotone repository (SQLite db file)
       
    27             try:
       
    28                 header = file(path, 'rb').read(16)
       
    29             except:
       
    30                 header = ''
       
    31             if header != 'SQLite format 3\x00':
       
    32                 raise norepo
       
    33 
       
    34         # regular expressions for parsing monotone output
       
    35         space    = r'\s*'
       
    36         name     = r'\s+"((?:\\"|[^"])*)"\s*'
       
    37         value    = name
       
    38         revision = r'\s+\[(\w+)\]\s*'
       
    39         lines    = r'(?:.|\n)+'
       
    40 
       
    41         self.dir_re      = re.compile(space + "dir" + name)
       
    42         self.file_re     = re.compile(space + "file" + name +
       
    43                                       "content" + revision)
       
    44         self.add_file_re = re.compile(space + "add_file" + name +
       
    45                                       "content" + revision)
       
    46         self.patch_re    = re.compile(space + "patch" + name +
       
    47                                       "from" + revision + "to" + revision)
       
    48         self.rename_re   = re.compile(space + "rename" + name + "to" + name)
       
    49         self.delete_re   = re.compile(space + "delete" + name)
       
    50         self.tag_re      = re.compile(space + "tag" + name + "revision" +
       
    51                                       revision)
       
    52         self.cert_re     = re.compile(lines + space + "name" + name +
       
    53                                       "value" + value)
       
    54 
       
    55         attr = space + "file" + lines + space + "attr" + space
       
    56         self.attr_execute_re = re.compile(attr  + '"mtn:execute"' +
       
    57                                           space + '"true"')
       
    58 
       
    59         # cached data
       
    60         self.manifest_rev = None
       
    61         self.manifest = None
       
    62         self.files = None
       
    63         self.dirs  = None
       
    64 
       
    65         checktool('mtn', abort=False)
       
    66 
       
    67         # test if there are any revisions
       
    68         self.rev = None
       
    69         try:
       
    70             self.getheads()
       
    71         except:
       
    72             raise norepo
       
    73         self.rev = rev
       
    74 
       
    75     def mtnrun(self, *args, **kwargs):
       
    76         kwargs['d'] = self.path
       
    77         return self.run0('automate', *args, **kwargs)
       
    78 
       
    79     def mtnloadmanifest(self, rev):
       
    80         if self.manifest_rev == rev:
       
    81             return
       
    82         self.manifest = self.mtnrun("get_manifest_of", rev).split("\n\n")
       
    83         self.manifest_rev = rev
       
    84         self.files = {}
       
    85         self.dirs = {}
       
    86 
       
    87         for e in self.manifest:
       
    88             m = self.file_re.match(e)
       
    89             if m:
       
    90                 attr = ""
       
    91                 name = m.group(1)
       
    92                 node = m.group(2)
       
    93                 if self.attr_execute_re.match(e):
       
    94                     attr += "x"
       
    95                 self.files[name] = (node, attr)
       
    96             m = self.dir_re.match(e)
       
    97             if m:
       
    98                 self.dirs[m.group(1)] = True
       
    99 
       
   100     def mtnisfile(self, name, rev):
       
   101         # a non-file could be a directory or a deleted or renamed file
       
   102         self.mtnloadmanifest(rev)
       
   103         return name in self.files
       
   104 
       
   105     def mtnisdir(self, name, rev):
       
   106         self.mtnloadmanifest(rev)
       
   107         return name in self.dirs
       
   108 
       
   109     def mtngetcerts(self, rev):
       
   110         certs = {"author":"<missing>", "date":"<missing>",
       
   111             "changelog":"<missing>", "branch":"<missing>"}
       
   112         certlist = self.mtnrun("certs", rev)
       
   113         # mtn < 0.45:
       
   114         #   key "test@selenic.com"
       
   115         # mtn >= 0.45:
       
   116         #   key [ff58a7ffb771907c4ff68995eada1c4da068d328]
       
   117         certlist = re.split('\n\n      key ["\[]', certlist)
       
   118         for e in certlist:
       
   119             m = self.cert_re.match(e)
       
   120             if m:
       
   121                 name, value = m.groups()
       
   122                 value = value.replace(r'\"', '"')
       
   123                 value = value.replace(r'\\', '\\')
       
   124                 certs[name] = value
       
   125         # Monotone may have subsecond dates: 2005-02-05T09:39:12.364306
       
   126         # and all times are stored in UTC
       
   127         certs["date"] = certs["date"].split('.')[0] + " UTC"
       
   128         return certs
       
   129 
       
   130     # implement the converter_source interface:
       
   131 
       
   132     def getheads(self):
       
   133         if not self.rev:
       
   134             return self.mtnrun("leaves").splitlines()
       
   135         else:
       
   136             return [self.rev]
       
   137 
       
   138     def getchanges(self, rev):
       
   139         #revision = self.mtncmd("get_revision %s" % rev).split("\n\n")
       
   140         revision = self.mtnrun("get_revision", rev).split("\n\n")
       
   141         files = {}
       
   142         ignoremove = {}
       
   143         renameddirs = []
       
   144         copies = {}
       
   145         for e in revision:
       
   146             m = self.add_file_re.match(e)
       
   147             if m:
       
   148                 files[m.group(1)] = rev
       
   149                 ignoremove[m.group(1)] = rev
       
   150             m = self.patch_re.match(e)
       
   151             if m:
       
   152                 files[m.group(1)] = rev
       
   153             # Delete/rename is handled later when the convert engine
       
   154             # discovers an IOError exception from getfile,
       
   155             # but only if we add the "from" file to the list of changes.
       
   156             m = self.delete_re.match(e)
       
   157             if m:
       
   158                 files[m.group(1)] = rev
       
   159             m = self.rename_re.match(e)
       
   160             if m:
       
   161                 toname = m.group(2)
       
   162                 fromname = m.group(1)
       
   163                 if self.mtnisfile(toname, rev):
       
   164                     ignoremove[toname] = 1
       
   165                     copies[toname] = fromname
       
   166                     files[toname] = rev
       
   167                     files[fromname] = rev
       
   168                 elif self.mtnisdir(toname, rev):
       
   169                     renameddirs.append((fromname, toname))
       
   170 
       
   171         # Directory renames can be handled only once we have recorded
       
   172         # all new files
       
   173         for fromdir, todir in renameddirs:
       
   174             renamed = {}
       
   175             for tofile in self.files:
       
   176                 if tofile in ignoremove:
       
   177                     continue
       
   178                 if tofile.startswith(todir + '/'):
       
   179                     renamed[tofile] = fromdir + tofile[len(todir):]
       
   180                     # Avoid chained moves like:
       
   181                     # d1(/a) => d3/d1(/a)
       
   182                     # d2 => d3
       
   183                     ignoremove[tofile] = 1
       
   184             for tofile, fromfile in renamed.items():
       
   185                 self.ui.debug (_("copying file in renamed directory "
       
   186                                  "from '%s' to '%s'")
       
   187                                % (fromfile, tofile), '\n')
       
   188                 files[tofile] = rev
       
   189                 copies[tofile] = fromfile
       
   190             for fromfile in renamed.values():
       
   191                 files[fromfile] = rev
       
   192 
       
   193         return (files.items(), copies)
       
   194 
       
   195     def getfile(self, name, rev):
       
   196         if not self.mtnisfile(name, rev):
       
   197             raise IOError() # file was deleted or renamed
       
   198         try:
       
   199             data = self.mtnrun("get_file_of", name, r=rev)
       
   200         except:
       
   201             raise IOError() # file was deleted or renamed
       
   202         self.mtnloadmanifest(rev)
       
   203         node, attr = self.files.get(name, (None, ""))
       
   204         return data, attr
       
   205 
       
   206     def getcommit(self, rev):
       
   207         certs   = self.mtngetcerts(rev)
       
   208         return commit(
       
   209             author=certs["author"],
       
   210             date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
       
   211             desc=certs["changelog"],
       
   212             rev=rev,
       
   213             parents=self.mtnrun("parents", rev).splitlines(),
       
   214             branch=certs["branch"])
       
   215 
       
   216     def gettags(self):
       
   217         tags = {}
       
   218         for e in self.mtnrun("tags").split("\n\n"):
       
   219             m = self.tag_re.match(e)
       
   220             if m:
       
   221                 tags[m.group(1)] = m.group(2)
       
   222         return tags
       
   223 
       
   224     def getchangedfiles(self, rev, i):
       
   225         # This function is only needed to support --filemap
       
   226         # ... and we don't support that
       
   227         raise NotImplementedError()