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