hgext/convert/monotone.py
changeset 6306 2f9de4aaea9e
child 6307 6840668e3bf6
equal deleted inserted replaced
6305:e8d447d91cdb 6306:2f9de4aaea9e
       
     1 # monotone support for the convert extension
       
     2 
       
     3 import os
       
     4 import re
       
     5 import time
       
     6 from mercurial import util
       
     7 
       
     8 from common import NoRepo, commit, converter_source, checktool
       
     9 
       
    10 class monotone_source(converter_source):
       
    11     def __init__(self, ui, path=None, rev=None):
       
    12         converter_source.__init__(self, ui, path, rev)
       
    13         
       
    14         self.ui = ui
       
    15         self.path = path
       
    16 
       
    17          
       
    18         # regular expressions for parsing monotone output
       
    19         
       
    20         space    = r'\s*'
       
    21         name     = r'\s+"((?:[^"]|\\")*)"\s*'
       
    22         value    = name
       
    23         revision = r'\s+\[(\w+)\]\s*'
       
    24         lines    = r'(?:.|\n)+'
       
    25         
       
    26         self.dir_re      = re.compile(space + "dir"      + name)
       
    27         self.file_re     = re.compile(space + "file"     + name + "content" + revision)
       
    28         self.add_file_re = re.compile(space + "add_file" + name + "content" + revision)
       
    29         self.patch_re    = re.compile(space + "patch"    + name + "from" + revision + "to" + revision)
       
    30         self.rename_re   = re.compile(space + "rename"   + name + "to" + name)
       
    31         self.tag_re      = re.compile(space + "tag"      + name + "revision" + revision)
       
    32         self.cert_re     = re.compile(lines + space + "name" + name + "value" + value)
       
    33 
       
    34         attr = space + "file" + lines + space + "attr" + space
       
    35         self.attr_execute_re = re.compile(attr  + '"mtn:execute"' + space + '"true"')
       
    36 
       
    37         # cached data
       
    38         
       
    39         self.manifest_rev = None
       
    40         self.manifest = None
       
    41         self.files = None   
       
    42         self.dirs  = None     
       
    43         
       
    44         norepo = NoRepo("%s does not look like a monotone repo" % path)
       
    45         if not os.path.exists(path):
       
    46             raise norepo
       
    47         
       
    48         checktool('mtn')
       
    49         
       
    50         # test if there are are any revisions
       
    51         self.rev = None
       
    52         try :
       
    53             self.getheads()
       
    54         except :
       
    55             raise norepo        
       
    56 
       
    57         self.rev = rev
       
    58 
       
    59     
       
    60     def mtncmd(self, arg):
       
    61         cmdline = "mtn -d %s automate %s" % (util.shellquote(self.path), arg)
       
    62         self.ui.debug(cmdline, '\n')
       
    63         p = util.popen(cmdline)
       
    64         result = p.read()
       
    65         if p.close():
       
    66             raise IOError()
       
    67         return result
       
    68     
       
    69     def mtnloadmanifest(self, rev):
       
    70         if self.manifest_rev == rev:
       
    71             return
       
    72         self.manifest_rev = rev
       
    73         self.manifest = self.mtncmd("get_manifest_of %s" % rev).split("\n\n")
       
    74         
       
    75         manifest = self.manifest
       
    76         files = {}
       
    77         dirs = {}
       
    78 
       
    79         for e in manifest:
       
    80             m = self.file_re.match(e)
       
    81             if m:                
       
    82                 attr = ""
       
    83                 name = m.group(1)
       
    84                 node = m.group(2)
       
    85                 if self.attr_execute_re.match(e):
       
    86                     attr += "x"
       
    87                 files[name] = (node, attr)
       
    88             m = self.dir_re.match(e)
       
    89             if m:
       
    90                 dirs[m.group(1)] = True
       
    91         
       
    92         self.files = files
       
    93         self.dirs = dirs
       
    94 
       
    95     def mtnisfile(self, name, rev):
       
    96         # a non-file could be a directory or a deleted or renamed file
       
    97         self.mtnloadmanifest(rev)
       
    98         try :
       
    99             self.files[name]
       
   100             return True
       
   101         except KeyError:
       
   102             return False
       
   103             
       
   104     def mtnisdir(self, name, rev):
       
   105         self.mtnloadmanifest(rev)
       
   106         try :
       
   107             self.dirs[name]
       
   108             return True
       
   109         except KeyError:
       
   110             return False
       
   111     
       
   112     def mtngetcerts(self, rev):
       
   113         certs = {"author":"<missing>", "date":"<missing>",
       
   114             "changelog":"<missing>", "branch":"<missing>"}
       
   115         cert_list = self.mtncmd("certs %s" % rev).split("\n\n")
       
   116         for e in cert_list:
       
   117             m = self.cert_re.match(e)
       
   118             if m:
       
   119                 certs[m.group(1)] = m.group(2)
       
   120         return certs
       
   121         
       
   122     def mtngetparents(self, rev):
       
   123         parents = self.mtncmd("parents %s" % rev).strip("\n").split("\n")
       
   124         p = []
       
   125         for x in parents:
       
   126             if len(x) >= 40: # blank revs have been seen otherwise
       
   127                 p.append(x)
       
   128         return p
       
   129 
       
   130     def mtnrenamefiles(self, files, fromdir, todir):
       
   131         renamed = {}
       
   132         for tofile in files:
       
   133             suffix = tofile.lstrip(todir)
       
   134             if todir + suffix == tofile:
       
   135                 renamed[tofile] = (fromdir + suffix).lstrip("/")
       
   136         return renamed
       
   137 
       
   138     
       
   139     # implement the converter_source interface:
       
   140     
       
   141     def getheads(self):
       
   142         if not self.rev or self.rev == "":
       
   143             return self.mtncmd("leaves").splitlines()
       
   144         else:
       
   145             return [self.rev]
       
   146 
       
   147     def getchanges(self, rev):
       
   148         revision = self.mtncmd("get_revision %s" % rev).split("\n\n")
       
   149         files = {}
       
   150         copies = {}
       
   151         for e in revision:
       
   152             m = self.add_file_re.match(e)
       
   153             if m:
       
   154                 files[m.group(1)] = rev
       
   155             m = self.patch_re.match(e)
       
   156             if m:
       
   157                 files[m.group(1)] = rev
       
   158 
       
   159             # Delete/rename is handled later when the convert engine
       
   160             # discovers an IOError exception from getfile,
       
   161             # but only if we add the "from" file to the list of changes.
       
   162             m = self.rename_re.match(e)
       
   163             if m:
       
   164                 toname = m.group(2)
       
   165                 fromname = m.group(1)
       
   166                 if self.mtnisfile(toname, rev):
       
   167                     copies[toname] = fromname
       
   168                     files[toname] = rev
       
   169                     files[fromname] = rev
       
   170                 if self.mtnisdir(toname, rev):
       
   171                     renamed = self.mtnrenamefiles(self.files, fromname, toname)
       
   172                     for tofile, fromfile in renamed.items():
       
   173                         self.ui.debug (("copying file in renamed dir from '%s' to '%s'" % (fromfile, tofile)), "\n")
       
   174                         files[tofile] = rev
       
   175                     for fromfile in renamed.values():
       
   176                         files[fromfile] = rev
       
   177 
       
   178         return (files.items(), copies)
       
   179         
       
   180     def getmode(self, name, rev):
       
   181         self.mtnloadmanifest(rev)
       
   182         try :
       
   183             node, attr = self.files[name]
       
   184             return attr
       
   185         except KeyError:
       
   186             return ""
       
   187         
       
   188     def getfile(self, name, rev):
       
   189         if not self.mtnisfile(name, rev):
       
   190             raise IOError() # file was deleted or renamed
       
   191         return self.mtncmd("get_file_of %s -r %s" % (util.shellquote(name), rev))
       
   192     
       
   193     def getcommit(self, rev):        
       
   194         certs   = self.mtngetcerts(rev)
       
   195         return commit(
       
   196             author=certs["author"],
       
   197             date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
       
   198             desc=certs["changelog"],
       
   199             rev=rev,
       
   200             parents=self.mtngetparents(rev),
       
   201             branch=certs["branch"])
       
   202 
       
   203     def gettags(self):
       
   204         tags = {}
       
   205         for e in self.mtncmd("tags").split("\n\n"):
       
   206             m = self.tag_re.match(e)
       
   207             if m:
       
   208                 tags[m.group(1)] = m.group(2)
       
   209         return tags
       
   210 
       
   211     def getchangedfiles(self, rev, i):
       
   212         # This function is only needed to support --filemap
       
   213         # ... and we don't support that
       
   214         raise NotImplementedError()