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