##// END OF EJS Templates
changelog: _delaycount -> _divert
Matt Mackall -
r9163:f193b643 default
parent child Browse files
Show More
@@ -1,230 +1,232 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):
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 self._realopener = opener
92 self._realopener = opener
93 self._delayed = False
93 self._delayed = False
94 self._divert = False
94 revlog.revlog.__init__(self, self._delayopener, "00changelog.i")
95 revlog.revlog.__init__(self, self._delayopener, "00changelog.i")
95
96
97
96 def delayupdate(self):
98 def delayupdate(self):
97 "delay visibility of index updates to other readers"
99 "delay visibility of index updates to other readers"
98 self._delayed = True
100 self._delayed = True
99 self._delaycount = len(self)
101 self._divert = (len(self) == 0)
100 self._delaybuf = []
102 self._delaybuf = []
101 self._delayname = None
103 self._delayname = None
102
104
103 def finalize(self, tr):
105 def finalize(self, tr):
104 "finalize index updates"
106 "finalize index updates"
105 self._delayed = False
107 self._delayed = False
106 # move redirected index data back into place
108 # move redirected index data back into place
107 if self._delayname:
109 if self._delayname:
108 util.rename(self._delayname + ".a", self._delayname)
110 util.rename(self._delayname + ".a", self._delayname)
109 elif self._delaybuf:
111 elif self._delaybuf:
110 fp = self.opener(self.indexfile, 'a')
112 fp = self.opener(self.indexfile, 'a')
111 fp.write("".join(self._delaybuf))
113 fp.write("".join(self._delaybuf))
112 fp.close()
114 fp.close()
113 self._delaybuf = []
115 self._delaybuf = []
114 # split when we're done
116 # split when we're done
115 self.checkinlinesize(tr)
117 self.checkinlinesize(tr)
116
118
117 def _delayopener(self, name, mode='r'):
119 def _delayopener(self, name, mode='r'):
118 fp = self._realopener(name, mode)
120 fp = self._realopener(name, mode)
119 # only divert the index
121 # only divert the index
120 if not self._delayed or not name == self.indexfile:
122 if not self._delayed or not name == self.indexfile:
121 return fp
123 return fp
122 # if we're doing an initial clone, divert to another file
124 # if we're doing an initial clone, divert to another file
123 if self._delaycount == 0:
125 if self._divert:
124 self._delayname = fp.name
126 self._delayname = fp.name
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._delayname = fp1.name
152 self._delayname = fp1.name
151
153
152 if self._delayname:
154 if self._delayname:
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 self.opener == self._delayopener:
159 return
161 return
160 return revlog.revlog.checkinlinesize(self, tr, fp)
162 return revlog.revlog.checkinlinesize(self, tr, fp)
161
163
162 def read(self, node):
164 def read(self, node):
163 """
165 """
164 format used:
166 format used:
165 nodeid\n : manifest node in ascii
167 nodeid\n : manifest node in ascii
166 user\n : user, no \n or \r allowed
168 user\n : user, no \n or \r allowed
167 time tz extra\n : date (time is int or float, timezone is int)
169 time tz extra\n : date (time is int or float, timezone is int)
168 : extra is metadatas, encoded and separated by '\0'
170 : extra is metadatas, encoded and separated by '\0'
169 : older versions ignore it
171 : older versions ignore it
170 files\n\n : files modified by the cset, no \n or \r allowed
172 files\n\n : files modified by the cset, no \n or \r allowed
171 (.*) : comment (free text, ideally utf-8)
173 (.*) : comment (free text, ideally utf-8)
172
174
173 changelog v0 doesn't use extra
175 changelog v0 doesn't use extra
174 """
176 """
175 text = self.revision(node)
177 text = self.revision(node)
176 if not text:
178 if not text:
177 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
179 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
178 last = text.index("\n\n")
180 last = text.index("\n\n")
179 desc = encoding.tolocal(text[last + 2:])
181 desc = encoding.tolocal(text[last + 2:])
180 l = text[:last].split('\n')
182 l = text[:last].split('\n')
181 manifest = bin(l[0])
183 manifest = bin(l[0])
182 user = encoding.tolocal(l[1])
184 user = encoding.tolocal(l[1])
183
185
184 extra_data = l[2].split(' ', 2)
186 extra_data = l[2].split(' ', 2)
185 if len(extra_data) != 3:
187 if len(extra_data) != 3:
186 time = float(extra_data.pop(0))
188 time = float(extra_data.pop(0))
187 try:
189 try:
188 # various tools did silly things with the time zone field.
190 # various tools did silly things with the time zone field.
189 timezone = int(extra_data[0])
191 timezone = int(extra_data[0])
190 except:
192 except:
191 timezone = 0
193 timezone = 0
192 extra = {}
194 extra = {}
193 else:
195 else:
194 time, timezone, extra = extra_data
196 time, timezone, extra = extra_data
195 time, timezone = float(time), int(timezone)
197 time, timezone = float(time), int(timezone)
196 extra = decodeextra(extra)
198 extra = decodeextra(extra)
197 if not extra.get('branch'):
199 if not extra.get('branch'):
198 extra['branch'] = 'default'
200 extra['branch'] = 'default'
199 files = l[3:]
201 files = l[3:]
200 return (manifest, user, (time, timezone), files, desc, extra)
202 return (manifest, user, (time, timezone), files, desc, extra)
201
203
202 def add(self, manifest, files, desc, transaction, p1, p2,
204 def add(self, manifest, files, desc, transaction, p1, p2,
203 user, date=None, extra={}):
205 user, date=None, extra={}):
204 user = user.strip()
206 user = user.strip()
205 # An empty username or a username with a "\n" will make the
207 # An empty username or a username with a "\n" will make the
206 # revision text contain two "\n\n" sequences -> corrupt
208 # revision text contain two "\n\n" sequences -> corrupt
207 # repository since read cannot unpack the revision.
209 # repository since read cannot unpack the revision.
208 if not user:
210 if not user:
209 raise error.RevlogError(_("empty username"))
211 raise error.RevlogError(_("empty username"))
210 if "\n" in user:
212 if "\n" in user:
211 raise error.RevlogError(_("username %s contains a newline")
213 raise error.RevlogError(_("username %s contains a newline")
212 % repr(user))
214 % repr(user))
213
215
214 # strip trailing whitespace and leading and trailing empty lines
216 # strip trailing whitespace and leading and trailing empty lines
215 desc = '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
217 desc = '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
216
218
217 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
219 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
218
220
219 if date:
221 if date:
220 parseddate = "%d %d" % util.parsedate(date)
222 parseddate = "%d %d" % util.parsedate(date)
221 else:
223 else:
222 parseddate = "%d %d" % util.makedate()
224 parseddate = "%d %d" % util.makedate()
223 if extra and extra.get("branch") in ("default", ""):
225 if extra and extra.get("branch") in ("default", ""):
224 del extra["branch"]
226 del extra["branch"]
225 if extra:
227 if extra:
226 extra = encodeextra(extra)
228 extra = encodeextra(extra)
227 parseddate = "%s %s" % (parseddate, extra)
229 parseddate = "%s %s" % (parseddate, extra)
228 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
230 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
229 text = "\n".join(l)
231 text = "\n".join(l)
230 return self.addrevision(text, transaction, len(self), p1, p2)
232 return self.addrevision(text, transaction, len(self), p1, p2)
General Comments 0
You need to be logged in to leave comments. Login now