eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/changelog.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # changelog.py - changelog class for mercurial
       
     2 #
       
     3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
       
     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 from node import bin, hex, nullid
       
     9 from i18n import _
       
    10 import util, error, revlog, encoding
       
    11 
       
    12 def _string_escape(text):
       
    13     """
       
    14     >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
       
    15     >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
       
    16     >>> s
       
    17     'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
       
    18     >>> res = _string_escape(s)
       
    19     >>> s == res.decode('string_escape')
       
    20     True
       
    21     """
       
    22     # subset of the string_escape codec
       
    23     text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
       
    24     return text.replace('\0', '\\0')
       
    25 
       
    26 def decodeextra(text):
       
    27     extra = {}
       
    28     for l in text.split('\0'):
       
    29         if l:
       
    30             k, v = l.decode('string_escape').split(':', 1)
       
    31             extra[k] = v
       
    32     return extra
       
    33 
       
    34 def encodeextra(d):
       
    35     # keys must be sorted to produce a deterministic changelog entry
       
    36     items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
       
    37     return "\0".join(items)
       
    38 
       
    39 class appender(object):
       
    40     '''the changelog index must be updated last on disk, so we use this class
       
    41     to delay writes to it'''
       
    42     def __init__(self, fp, buf):
       
    43         self.data = buf
       
    44         self.fp = fp
       
    45         self.offset = fp.tell()
       
    46         self.size = util.fstat(fp).st_size
       
    47 
       
    48     def end(self):
       
    49         return self.size + len("".join(self.data))
       
    50     def tell(self):
       
    51         return self.offset
       
    52     def flush(self):
       
    53         pass
       
    54     def close(self):
       
    55         self.fp.close()
       
    56 
       
    57     def seek(self, offset, whence=0):
       
    58         '''virtual file offset spans real file and data'''
       
    59         if whence == 0:
       
    60             self.offset = offset
       
    61         elif whence == 1:
       
    62             self.offset += offset
       
    63         elif whence == 2:
       
    64             self.offset = self.end() + offset
       
    65         if self.offset < self.size:
       
    66             self.fp.seek(self.offset)
       
    67 
       
    68     def read(self, count=-1):
       
    69         '''only trick here is reads that span real file and data'''
       
    70         ret = ""
       
    71         if self.offset < self.size:
       
    72             s = self.fp.read(count)
       
    73             ret = s
       
    74             self.offset += len(s)
       
    75             if count > 0:
       
    76                 count -= len(s)
       
    77         if count != 0:
       
    78             doff = self.offset - self.size
       
    79             self.data.insert(0, "".join(self.data))
       
    80             del self.data[1:]
       
    81             s = self.data[0][doff:doff + count]
       
    82             self.offset += len(s)
       
    83             ret += s
       
    84         return ret
       
    85 
       
    86     def write(self, s):
       
    87         self.data.append(str(s))
       
    88         self.offset += len(s)
       
    89 
       
    90 def delayopener(opener, target, divert, buf):
       
    91     def o(name, mode='r'):
       
    92         if name != target:
       
    93             return opener(name, mode)
       
    94         if divert:
       
    95             return opener(name + ".a", mode.replace('a', 'w'))
       
    96         # otherwise, divert to memory
       
    97         return appender(opener(name, mode), buf)
       
    98     return o
       
    99 
       
   100 class changelog(revlog.revlog):
       
   101     def __init__(self, opener):
       
   102         revlog.revlog.__init__(self, opener, "00changelog.i")
       
   103         self._realopener = opener
       
   104         self._delayed = False
       
   105         self._divert = False
       
   106 
       
   107     def delayupdate(self):
       
   108         "delay visibility of index updates to other readers"
       
   109         self._delayed = True
       
   110         self._divert = (len(self) == 0)
       
   111         self._delaybuf = []
       
   112         self.opener = delayopener(self._realopener, self.indexfile,
       
   113                                   self._divert, self._delaybuf)
       
   114 
       
   115     def finalize(self, tr):
       
   116         "finalize index updates"
       
   117         self._delayed = False
       
   118         self.opener = self._realopener
       
   119         # move redirected index data back into place
       
   120         if self._divert:
       
   121             n = self.opener(self.indexfile + ".a").name
       
   122             util.rename(n, n[:-2])
       
   123         elif self._delaybuf:
       
   124             fp = self.opener(self.indexfile, 'a')
       
   125             fp.write("".join(self._delaybuf))
       
   126             fp.close()
       
   127             self._delaybuf = []
       
   128         # split when we're done
       
   129         self.checkinlinesize(tr)
       
   130 
       
   131     def readpending(self, file):
       
   132         r = revlog.revlog(self.opener, file)
       
   133         self.index = r.index
       
   134         self.nodemap = r.nodemap
       
   135         self._chunkcache = r._chunkcache
       
   136 
       
   137     def writepending(self):
       
   138         "create a file containing the unfinalized state for pretxnchangegroup"
       
   139         if self._delaybuf:
       
   140             # make a temporary copy of the index
       
   141             fp1 = self._realopener(self.indexfile)
       
   142             fp2 = self._realopener(self.indexfile + ".a", "w")
       
   143             fp2.write(fp1.read())
       
   144             # add pending data
       
   145             fp2.write("".join(self._delaybuf))
       
   146             fp2.close()
       
   147             # switch modes so finalize can simply rename
       
   148             self._delaybuf = []
       
   149             self._divert = True
       
   150 
       
   151         if self._divert:
       
   152             return True
       
   153 
       
   154         return False
       
   155 
       
   156     def checkinlinesize(self, tr, fp=None):
       
   157         if not self._delayed:
       
   158             revlog.revlog.checkinlinesize(self, tr, fp)
       
   159 
       
   160     def read(self, node):
       
   161         """
       
   162         format used:
       
   163         nodeid\n        : manifest node in ascii
       
   164         user\n          : user, no \n or \r allowed
       
   165         time tz extra\n : date (time is int or float, timezone is int)
       
   166                         : extra is metadatas, encoded and separated by '\0'
       
   167                         : older versions ignore it
       
   168         files\n\n       : files modified by the cset, no \n or \r allowed
       
   169         (.*)            : comment (free text, ideally utf-8)
       
   170 
       
   171         changelog v0 doesn't use extra
       
   172         """
       
   173         text = self.revision(node)
       
   174         if not text:
       
   175             return (nullid, "", (0, 0), [], "", {'branch': 'default'})
       
   176         last = text.index("\n\n")
       
   177         desc = encoding.tolocal(text[last + 2:])
       
   178         l = text[:last].split('\n')
       
   179         manifest = bin(l[0])
       
   180         user = encoding.tolocal(l[1])
       
   181 
       
   182         extra_data = l[2].split(' ', 2)
       
   183         if len(extra_data) != 3:
       
   184             time = float(extra_data.pop(0))
       
   185             try:
       
   186                 # various tools did silly things with the time zone field.
       
   187                 timezone = int(extra_data[0])
       
   188             except:
       
   189                 timezone = 0
       
   190             extra = {}
       
   191         else:
       
   192             time, timezone, extra = extra_data
       
   193             time, timezone = float(time), int(timezone)
       
   194             extra = decodeextra(extra)
       
   195         if not extra.get('branch'):
       
   196             extra['branch'] = 'default'
       
   197         files = l[3:]
       
   198         return (manifest, user, (time, timezone), files, desc, extra)
       
   199 
       
   200     def add(self, manifest, files, desc, transaction, p1, p2,
       
   201                   user, date=None, extra=None):
       
   202         user = user.strip()
       
   203         # An empty username or a username with a "\n" will make the
       
   204         # revision text contain two "\n\n" sequences -> corrupt
       
   205         # repository since read cannot unpack the revision.
       
   206         if not user:
       
   207             raise error.RevlogError(_("empty username"))
       
   208         if "\n" in user:
       
   209             raise error.RevlogError(_("username %s contains a newline")
       
   210                                     % repr(user))
       
   211 
       
   212         # strip trailing whitespace and leading and trailing empty lines
       
   213         desc = '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
       
   214 
       
   215         user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
       
   216 
       
   217         if date:
       
   218             parseddate = "%d %d" % util.parsedate(date)
       
   219         else:
       
   220             parseddate = "%d %d" % util.makedate()
       
   221         if extra:
       
   222             branch = extra.get("branch")
       
   223             if branch in ("default", ""):
       
   224                 del extra["branch"]
       
   225             elif branch in (".", "null", "tip"):
       
   226                 raise error.RevlogError(_('the name \'%s\' is reserved')
       
   227                                         % branch)
       
   228         if extra:
       
   229             extra = encodeextra(extra)
       
   230             parseddate = "%s %s" % (parseddate, extra)
       
   231         l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
       
   232         text = "\n".join(l)
       
   233         return self.addrevision(text, transaction, len(self), p1, p2)