##// END OF EJS Templates
changelog: inline trivial call for extra data unescaping
Matt Mackall -
r5745:234e40e6 default
parent child Browse files
Show More
@@ -1,196 +1,192
1 # changelog.py - changelog class for mercurial
1 # changelog.py - changelog class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from revlog import *
8 from revlog import *
9 from i18n import _
9 from i18n import _
10 import os, time, util
10 import os, time, util
11
11
12 def _string_escape(text):
12 def _string_escape(text):
13 """
13 """
14 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
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
15 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
16 >>> s
16 >>> s
17 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
17 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
18 >>> res = _string_escape(s)
18 >>> res = _string_escape(s)
19 >>> s == _string_unescape(res)
19 >>> s == res.decode('string_escape')
20 True
20 True
21 """
21 """
22 # subset of the string_escape codec
22 # subset of the string_escape codec
23 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
23 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
24 return text.replace('\0', '\\0')
24 return text.replace('\0', '\\0')
25
25
26 def _string_unescape(text):
27 return text.decode('string_escape')
28
29 class appender:
26 class appender:
30 '''the changelog index must be update last on disk, so we use this class
27 '''the changelog index must be update last on disk, so we use this class
31 to delay writes to it'''
28 to delay writes to it'''
32 def __init__(self, fp, buf):
29 def __init__(self, fp, buf):
33 self.data = buf
30 self.data = buf
34 self.fp = fp
31 self.fp = fp
35 self.offset = fp.tell()
32 self.offset = fp.tell()
36 self.size = util.fstat(fp).st_size
33 self.size = util.fstat(fp).st_size
37
34
38 def end(self):
35 def end(self):
39 return self.size + len("".join(self.data))
36 return self.size + len("".join(self.data))
40 def tell(self):
37 def tell(self):
41 return self.offset
38 return self.offset
42 def flush(self):
39 def flush(self):
43 pass
40 pass
44 def close(self):
41 def close(self):
45 self.fp.close()
42 self.fp.close()
46
43
47 def seek(self, offset, whence=0):
44 def seek(self, offset, whence=0):
48 '''virtual file offset spans real file and data'''
45 '''virtual file offset spans real file and data'''
49 if whence == 0:
46 if whence == 0:
50 self.offset = offset
47 self.offset = offset
51 elif whence == 1:
48 elif whence == 1:
52 self.offset += offset
49 self.offset += offset
53 elif whence == 2:
50 elif whence == 2:
54 self.offset = self.end() + offset
51 self.offset = self.end() + offset
55 if self.offset < self.size:
52 if self.offset < self.size:
56 self.fp.seek(self.offset)
53 self.fp.seek(self.offset)
57
54
58 def read(self, count=-1):
55 def read(self, count=-1):
59 '''only trick here is reads that span real file and data'''
56 '''only trick here is reads that span real file and data'''
60 ret = ""
57 ret = ""
61 if self.offset < self.size:
58 if self.offset < self.size:
62 s = self.fp.read(count)
59 s = self.fp.read(count)
63 ret = s
60 ret = s
64 self.offset += len(s)
61 self.offset += len(s)
65 if count > 0:
62 if count > 0:
66 count -= len(s)
63 count -= len(s)
67 if count != 0:
64 if count != 0:
68 doff = self.offset - self.size
65 doff = self.offset - self.size
69 self.data.insert(0, "".join(self.data))
66 self.data.insert(0, "".join(self.data))
70 del self.data[1:]
67 del self.data[1:]
71 s = self.data[0][doff:doff+count]
68 s = self.data[0][doff:doff+count]
72 self.offset += len(s)
69 self.offset += len(s)
73 ret += s
70 ret += s
74 return ret
71 return ret
75
72
76 def write(self, s):
73 def write(self, s):
77 self.data.append(str(s))
74 self.data.append(str(s))
78 self.offset += len(s)
75 self.offset += len(s)
79
76
80 class changelog(revlog):
77 class changelog(revlog):
81 def __init__(self, opener):
78 def __init__(self, opener):
82 revlog.__init__(self, opener, "00changelog.i")
79 revlog.__init__(self, opener, "00changelog.i")
83
80
84 def delayupdate(self):
81 def delayupdate(self):
85 "delay visibility of index updates to other readers"
82 "delay visibility of index updates to other readers"
86 self._realopener = self.opener
83 self._realopener = self.opener
87 self.opener = self._delayopener
84 self.opener = self._delayopener
88 self._delaycount = self.count()
85 self._delaycount = self.count()
89 self._delaybuf = []
86 self._delaybuf = []
90 self._delayname = None
87 self._delayname = None
91
88
92 def finalize(self, tr):
89 def finalize(self, tr):
93 "finalize index updates"
90 "finalize index updates"
94 self.opener = self._realopener
91 self.opener = self._realopener
95 # move redirected index data back into place
92 # move redirected index data back into place
96 if self._delayname:
93 if self._delayname:
97 util.rename(self._delayname + ".a", self._delayname)
94 util.rename(self._delayname + ".a", self._delayname)
98 elif self._delaybuf:
95 elif self._delaybuf:
99 fp = self.opener(self.indexfile, 'a')
96 fp = self.opener(self.indexfile, 'a')
100 fp.write("".join(self._delaybuf))
97 fp.write("".join(self._delaybuf))
101 fp.close()
98 fp.close()
102 del self._delaybuf
99 del self._delaybuf
103 # split when we're done
100 # split when we're done
104 self.checkinlinesize(tr)
101 self.checkinlinesize(tr)
105
102
106 def _delayopener(self, name, mode='r'):
103 def _delayopener(self, name, mode='r'):
107 fp = self._realopener(name, mode)
104 fp = self._realopener(name, mode)
108 # only divert the index
105 # only divert the index
109 if not name == self.indexfile:
106 if not name == self.indexfile:
110 return fp
107 return fp
111 # if we're doing an initial clone, divert to another file
108 # if we're doing an initial clone, divert to another file
112 if self._delaycount == 0:
109 if self._delaycount == 0:
113 self._delayname = fp.name
110 self._delayname = fp.name
114 return self._realopener(name + ".a", mode)
111 return self._realopener(name + ".a", mode)
115 # otherwise, divert to memory
112 # otherwise, divert to memory
116 return appender(fp, self._delaybuf)
113 return appender(fp, self._delaybuf)
117
114
118 def checkinlinesize(self, tr, fp=None):
115 def checkinlinesize(self, tr, fp=None):
119 if self.opener == self._delayopener:
116 if self.opener == self._delayopener:
120 return
117 return
121 return revlog.checkinlinesize(self, tr, fp)
118 return revlog.checkinlinesize(self, tr, fp)
122
119
123 def decode_extra(self, text):
120 def decode_extra(self, text):
124 extra = {}
121 extra = {}
125 for l in text.split('\0'):
122 for l in text.split('\0'):
126 if not l:
123 if l:
127 continue
124 k, v = text.decode('string_escape').split(':', 1)
128 k, v = _string_unescape(l).split(':', 1)
125 extra[k] = v
129 extra[k] = v
130 return extra
126 return extra
131
127
132 def encode_extra(self, d):
128 def encode_extra(self, d):
133 # keys must be sorted to produce a deterministic changelog entry
129 # keys must be sorted to produce a deterministic changelog entry
134 keys = d.keys()
130 keys = d.keys()
135 keys.sort()
131 keys.sort()
136 items = [_string_escape('%s:%s' % (k, d[k])) for k in keys]
132 items = [_string_escape('%s:%s' % (k, d[k])) for k in keys]
137 return "\0".join(items)
133 return "\0".join(items)
138
134
139 def read(self, node):
135 def read(self, node):
140 """
136 """
141 format used:
137 format used:
142 nodeid\n : manifest node in ascii
138 nodeid\n : manifest node in ascii
143 user\n : user, no \n or \r allowed
139 user\n : user, no \n or \r allowed
144 time tz extra\n : date (time is int or float, timezone is int)
140 time tz extra\n : date (time is int or float, timezone is int)
145 : extra is metadatas, encoded and separated by '\0'
141 : extra is metadatas, encoded and separated by '\0'
146 : older versions ignore it
142 : older versions ignore it
147 files\n\n : files modified by the cset, no \n or \r allowed
143 files\n\n : files modified by the cset, no \n or \r allowed
148 (.*) : comment (free text, ideally utf-8)
144 (.*) : comment (free text, ideally utf-8)
149
145
150 changelog v0 doesn't use extra
146 changelog v0 doesn't use extra
151 """
147 """
152 text = self.revision(node)
148 text = self.revision(node)
153 if not text:
149 if not text:
154 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
150 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
155 last = text.index("\n\n")
151 last = text.index("\n\n")
156 desc = util.tolocal(text[last + 2:])
152 desc = util.tolocal(text[last + 2:])
157 l = text[:last].split('\n')
153 l = text[:last].split('\n')
158 manifest = bin(l[0])
154 manifest = bin(l[0])
159 user = util.tolocal(l[1])
155 user = util.tolocal(l[1])
160
156
161 extra_data = l[2].split(' ', 2)
157 extra_data = l[2].split(' ', 2)
162 if len(extra_data) != 3:
158 if len(extra_data) != 3:
163 time = float(extra_data.pop(0))
159 time = float(extra_data.pop(0))
164 try:
160 try:
165 # various tools did silly things with the time zone field.
161 # various tools did silly things with the time zone field.
166 timezone = int(extra_data[0])
162 timezone = int(extra_data[0])
167 except:
163 except:
168 timezone = 0
164 timezone = 0
169 extra = {}
165 extra = {}
170 else:
166 else:
171 time, timezone, extra = extra_data
167 time, timezone, extra = extra_data
172 time, timezone = float(time), int(timezone)
168 time, timezone = float(time), int(timezone)
173 extra = self.decode_extra(extra)
169 extra = self.decode_extra(extra)
174 if not extra.get('branch'):
170 if not extra.get('branch'):
175 extra['branch'] = 'default'
171 extra['branch'] = 'default'
176 files = l[3:]
172 files = l[3:]
177 return (manifest, user, (time, timezone), files, desc, extra)
173 return (manifest, user, (time, timezone), files, desc, extra)
178
174
179 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
175 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
180 user=None, date=None, extra={}):
176 user=None, date=None, extra={}):
181
177
182 user, desc = util.fromlocal(user), util.fromlocal(desc)
178 user, desc = util.fromlocal(user), util.fromlocal(desc)
183
179
184 if date:
180 if date:
185 parseddate = "%d %d" % util.parsedate(date)
181 parseddate = "%d %d" % util.parsedate(date)
186 else:
182 else:
187 parseddate = "%d %d" % util.makedate()
183 parseddate = "%d %d" % util.makedate()
188 if extra and extra.get("branch") in ("default", ""):
184 if extra and extra.get("branch") in ("default", ""):
189 del extra["branch"]
185 del extra["branch"]
190 if extra:
186 if extra:
191 extra = self.encode_extra(extra)
187 extra = self.encode_extra(extra)
192 parseddate = "%s %s" % (parseddate, extra)
188 parseddate = "%s %s" % (parseddate, extra)
193 list.sort()
189 list.sort()
194 l = [hex(manifest), user, parseddate] + list + ["", desc]
190 l = [hex(manifest), user, parseddate] + list + ["", desc]
195 text = "\n".join(l)
191 text = "\n".join(l)
196 return self.addrevision(text, transaction, self.count(), p1, p2)
192 return self.addrevision(text, transaction, self.count(), p1, p2)
General Comments 0
You need to be logged in to leave comments. Login now