##// END OF EJS Templates
changelog: turn {de,en}code_extra methods into functions...
Martin Geisler -
r8443:53ff4a5a default
parent child Browse files
Show More
@@ -1,225 +1,225 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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from node import bin, hex, nullid
8 from node import bin, hex, nullid
9 from i18n import _
9 from i18n import _
10 import util, error, revlog, encoding
10 import util, error, revlog, encoding
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 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
26 class appender:
39 class appender:
27 '''the changelog index must be updated last on disk, so we use this class
40 '''the changelog index must be updated last on disk, so we use this class
28 to delay writes to it'''
41 to delay writes to it'''
29 def __init__(self, fp, buf):
42 def __init__(self, fp, buf):
30 self.data = buf
43 self.data = buf
31 self.fp = fp
44 self.fp = fp
32 self.offset = fp.tell()
45 self.offset = fp.tell()
33 self.size = util.fstat(fp).st_size
46 self.size = util.fstat(fp).st_size
34
47
35 def end(self):
48 def end(self):
36 return self.size + len("".join(self.data))
49 return self.size + len("".join(self.data))
37 def tell(self):
50 def tell(self):
38 return self.offset
51 return self.offset
39 def flush(self):
52 def flush(self):
40 pass
53 pass
41 def close(self):
54 def close(self):
42 self.fp.close()
55 self.fp.close()
43
56
44 def seek(self, offset, whence=0):
57 def seek(self, offset, whence=0):
45 '''virtual file offset spans real file and data'''
58 '''virtual file offset spans real file and data'''
46 if whence == 0:
59 if whence == 0:
47 self.offset = offset
60 self.offset = offset
48 elif whence == 1:
61 elif whence == 1:
49 self.offset += offset
62 self.offset += offset
50 elif whence == 2:
63 elif whence == 2:
51 self.offset = self.end() + offset
64 self.offset = self.end() + offset
52 if self.offset < self.size:
65 if self.offset < self.size:
53 self.fp.seek(self.offset)
66 self.fp.seek(self.offset)
54
67
55 def read(self, count=-1):
68 def read(self, count=-1):
56 '''only trick here is reads that span real file and data'''
69 '''only trick here is reads that span real file and data'''
57 ret = ""
70 ret = ""
58 if self.offset < self.size:
71 if self.offset < self.size:
59 s = self.fp.read(count)
72 s = self.fp.read(count)
60 ret = s
73 ret = s
61 self.offset += len(s)
74 self.offset += len(s)
62 if count > 0:
75 if count > 0:
63 count -= len(s)
76 count -= len(s)
64 if count != 0:
77 if count != 0:
65 doff = self.offset - self.size
78 doff = self.offset - self.size
66 self.data.insert(0, "".join(self.data))
79 self.data.insert(0, "".join(self.data))
67 del self.data[1:]
80 del self.data[1:]
68 s = self.data[0][doff:doff+count]
81 s = self.data[0][doff:doff+count]
69 self.offset += len(s)
82 self.offset += len(s)
70 ret += s
83 ret += s
71 return ret
84 return ret
72
85
73 def write(self, s):
86 def write(self, s):
74 self.data.append(str(s))
87 self.data.append(str(s))
75 self.offset += len(s)
88 self.offset += len(s)
76
89
77 class changelog(revlog.revlog):
90 class changelog(revlog.revlog):
78 def __init__(self, opener):
91 def __init__(self, opener):
79 revlog.revlog.__init__(self, opener, "00changelog.i")
92 revlog.revlog.__init__(self, opener, "00changelog.i")
80
93
81 def delayupdate(self):
94 def delayupdate(self):
82 "delay visibility of index updates to other readers"
95 "delay visibility of index updates to other readers"
83 self._realopener = self.opener
96 self._realopener = self.opener
84 self.opener = self._delayopener
97 self.opener = self._delayopener
85 self._delaycount = len(self)
98 self._delaycount = len(self)
86 self._delaybuf = []
99 self._delaybuf = []
87 self._delayname = None
100 self._delayname = None
88
101
89 def finalize(self, tr):
102 def finalize(self, tr):
90 "finalize index updates"
103 "finalize index updates"
91 self.opener = self._realopener
104 self.opener = self._realopener
92 # move redirected index data back into place
105 # move redirected index data back into place
93 if self._delayname:
106 if self._delayname:
94 util.rename(self._delayname + ".a", self._delayname)
107 util.rename(self._delayname + ".a", self._delayname)
95 elif self._delaybuf:
108 elif self._delaybuf:
96 fp = self.opener(self.indexfile, 'a')
109 fp = self.opener(self.indexfile, 'a')
97 fp.write("".join(self._delaybuf))
110 fp.write("".join(self._delaybuf))
98 fp.close()
111 fp.close()
99 self._delaybuf = []
112 self._delaybuf = []
100 # split when we're done
113 # split when we're done
101 self.checkinlinesize(tr)
114 self.checkinlinesize(tr)
102
115
103 def _delayopener(self, name, mode='r'):
116 def _delayopener(self, name, mode='r'):
104 fp = self._realopener(name, mode)
117 fp = self._realopener(name, mode)
105 # only divert the index
118 # only divert the index
106 if not name == self.indexfile:
119 if not name == self.indexfile:
107 return fp
120 return fp
108 # if we're doing an initial clone, divert to another file
121 # if we're doing an initial clone, divert to another file
109 if self._delaycount == 0:
122 if self._delaycount == 0:
110 self._delayname = fp.name
123 self._delayname = fp.name
111 if not len(self):
124 if not len(self):
112 # make sure to truncate the file
125 # make sure to truncate the file
113 mode = mode.replace('a', 'w')
126 mode = mode.replace('a', 'w')
114 return self._realopener(name + ".a", mode)
127 return self._realopener(name + ".a", mode)
115 # otherwise, divert to memory
128 # otherwise, divert to memory
116 return appender(fp, self._delaybuf)
129 return appender(fp, self._delaybuf)
117
130
118 def readpending(self, file):
131 def readpending(self, file):
119 r = revlog.revlog(self.opener, file)
132 r = revlog.revlog(self.opener, file)
120 self.index = r.index
133 self.index = r.index
121 self.nodemap = r.nodemap
134 self.nodemap = r.nodemap
122 self._chunkcache = r._chunkcache
135 self._chunkcache = r._chunkcache
123
136
124 def writepending(self):
137 def writepending(self):
125 "create a file containing the unfinalized state for pretxnchangegroup"
138 "create a file containing the unfinalized state for pretxnchangegroup"
126 if self._delaybuf:
139 if self._delaybuf:
127 # make a temporary copy of the index
140 # make a temporary copy of the index
128 fp1 = self._realopener(self.indexfile)
141 fp1 = self._realopener(self.indexfile)
129 fp2 = self._realopener(self.indexfile + ".a", "w")
142 fp2 = self._realopener(self.indexfile + ".a", "w")
130 fp2.write(fp1.read())
143 fp2.write(fp1.read())
131 # add pending data
144 # add pending data
132 fp2.write("".join(self._delaybuf))
145 fp2.write("".join(self._delaybuf))
133 fp2.close()
146 fp2.close()
134 # switch modes so finalize can simply rename
147 # switch modes so finalize can simply rename
135 self._delaybuf = []
148 self._delaybuf = []
136 self._delayname = fp1.name
149 self._delayname = fp1.name
137
150
138 if self._delayname:
151 if self._delayname:
139 return True
152 return True
140
153
141 return False
154 return False
142
155
143 def checkinlinesize(self, tr, fp=None):
156 def checkinlinesize(self, tr, fp=None):
144 if self.opener == self._delayopener:
157 if self.opener == self._delayopener:
145 return
158 return
146 return revlog.revlog.checkinlinesize(self, tr, fp)
159 return revlog.revlog.checkinlinesize(self, tr, fp)
147
160
148 def decode_extra(self, text):
149 extra = {}
150 for l in text.split('\0'):
151 if l:
152 k, v = l.decode('string_escape').split(':', 1)
153 extra[k] = v
154 return extra
155
156 def encode_extra(self, d):
157 # keys must be sorted to produce a deterministic changelog entry
158 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
159 return "\0".join(items)
160
161 def read(self, node):
161 def read(self, node):
162 """
162 """
163 format used:
163 format used:
164 nodeid\n : manifest node in ascii
164 nodeid\n : manifest node in ascii
165 user\n : user, no \n or \r allowed
165 user\n : user, no \n or \r allowed
166 time tz extra\n : date (time is int or float, timezone is int)
166 time tz extra\n : date (time is int or float, timezone is int)
167 : extra is metadatas, encoded and separated by '\0'
167 : extra is metadatas, encoded and separated by '\0'
168 : older versions ignore it
168 : older versions ignore it
169 files\n\n : files modified by the cset, no \n or \r allowed
169 files\n\n : files modified by the cset, no \n or \r allowed
170 (.*) : comment (free text, ideally utf-8)
170 (.*) : comment (free text, ideally utf-8)
171
171
172 changelog v0 doesn't use extra
172 changelog v0 doesn't use extra
173 """
173 """
174 text = self.revision(node)
174 text = self.revision(node)
175 if not text:
175 if not text:
176 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
176 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
177 last = text.index("\n\n")
177 last = text.index("\n\n")
178 desc = encoding.tolocal(text[last + 2:])
178 desc = encoding.tolocal(text[last + 2:])
179 l = text[:last].split('\n')
179 l = text[:last].split('\n')
180 manifest = bin(l[0])
180 manifest = bin(l[0])
181 user = encoding.tolocal(l[1])
181 user = encoding.tolocal(l[1])
182
182
183 extra_data = l[2].split(' ', 2)
183 extra_data = l[2].split(' ', 2)
184 if len(extra_data) != 3:
184 if len(extra_data) != 3:
185 time = float(extra_data.pop(0))
185 time = float(extra_data.pop(0))
186 try:
186 try:
187 # various tools did silly things with the time zone field.
187 # various tools did silly things with the time zone field.
188 timezone = int(extra_data[0])
188 timezone = int(extra_data[0])
189 except:
189 except:
190 timezone = 0
190 timezone = 0
191 extra = {}
191 extra = {}
192 else:
192 else:
193 time, timezone, extra = extra_data
193 time, timezone, extra = extra_data
194 time, timezone = float(time), int(timezone)
194 time, timezone = float(time), int(timezone)
195 extra = self.decode_extra(extra)
195 extra = decodeextra(extra)
196 if not extra.get('branch'):
196 if not extra.get('branch'):
197 extra['branch'] = 'default'
197 extra['branch'] = 'default'
198 files = l[3:]
198 files = l[3:]
199 return (manifest, user, (time, timezone), files, desc, extra)
199 return (manifest, user, (time, timezone), files, desc, extra)
200
200
201 def add(self, manifest, files, desc, transaction, p1, p2,
201 def add(self, manifest, files, desc, transaction, p1, p2,
202 user, date=None, extra={}):
202 user, date=None, extra={}):
203 user = user.strip()
203 user = user.strip()
204 # An empty username or a username with a "\n" will make the
204 # An empty username or a username with a "\n" will make the
205 # revision text contain two "\n\n" sequences -> corrupt
205 # revision text contain two "\n\n" sequences -> corrupt
206 # repository since read cannot unpack the revision.
206 # repository since read cannot unpack the revision.
207 if not user:
207 if not user:
208 raise error.RevlogError(_("empty username"))
208 raise error.RevlogError(_("empty username"))
209 if "\n" in user:
209 if "\n" in user:
210 raise error.RevlogError(_("username %s contains a newline")
210 raise error.RevlogError(_("username %s contains a newline")
211 % repr(user))
211 % repr(user))
212 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
212 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
213
213
214 if date:
214 if date:
215 parseddate = "%d %d" % util.parsedate(date)
215 parseddate = "%d %d" % util.parsedate(date)
216 else:
216 else:
217 parseddate = "%d %d" % util.makedate()
217 parseddate = "%d %d" % util.makedate()
218 if extra and extra.get("branch") in ("default", ""):
218 if extra and extra.get("branch") in ("default", ""):
219 del extra["branch"]
219 del extra["branch"]
220 if extra:
220 if extra:
221 extra = self.encode_extra(extra)
221 extra = encodeextra(extra)
222 parseddate = "%s %s" % (parseddate, extra)
222 parseddate = "%s %s" % (parseddate, extra)
223 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
223 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
224 text = "\n".join(l)
224 text = "\n".join(l)
225 return self.addrevision(text, transaction, len(self), p1, p2)
225 return self.addrevision(text, transaction, len(self), p1, p2)
General Comments 0
You need to be logged in to leave comments. Login now