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