##// END OF EJS Templates
changelog: update read pending documentation...
Pierre-Yves David -
r25814:dc1a4926 default
parent child Browse files
Show More
@@ -1,391 +1,391 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 or any later version.
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 _defaultextra = {'branch': 'default'}
13 13
14 14 def _string_escape(text):
15 15 """
16 16 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
17 17 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
18 18 >>> s
19 19 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
20 20 >>> res = _string_escape(s)
21 21 >>> s == res.decode('string_escape')
22 22 True
23 23 """
24 24 # subset of the string_escape codec
25 25 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
26 26 return text.replace('\0', '\\0')
27 27
28 28 def decodeextra(text):
29 29 """
30 30 >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
31 31 ... ).iteritems())
32 32 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
33 33 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
34 34 ... 'baz': chr(92) + chr(0) + '2'})
35 35 ... ).iteritems())
36 36 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
37 37 """
38 38 extra = _defaultextra.copy()
39 39 for l in text.split('\0'):
40 40 if l:
41 41 if '\\0' in l:
42 42 # fix up \0 without getting into trouble with \\0
43 43 l = l.replace('\\\\', '\\\\\n')
44 44 l = l.replace('\\0', '\0')
45 45 l = l.replace('\n', '')
46 46 k, v = l.decode('string_escape').split(':', 1)
47 47 extra[k] = v
48 48 return extra
49 49
50 50 def encodeextra(d):
51 51 # keys must be sorted to produce a deterministic changelog entry
52 52 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
53 53 return "\0".join(items)
54 54
55 55 def stripdesc(desc):
56 56 """strip trailing whitespace and leading and trailing empty lines"""
57 57 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
58 58
59 59 class appender(object):
60 60 '''the changelog index must be updated last on disk, so we use this class
61 61 to delay writes to it'''
62 62 def __init__(self, vfs, name, mode, buf):
63 63 self.data = buf
64 64 fp = vfs(name, mode)
65 65 self.fp = fp
66 66 self.offset = fp.tell()
67 67 self.size = vfs.fstat(fp).st_size
68 68
69 69 def end(self):
70 70 return self.size + len("".join(self.data))
71 71 def tell(self):
72 72 return self.offset
73 73 def flush(self):
74 74 pass
75 75 def close(self):
76 76 self.fp.close()
77 77
78 78 def seek(self, offset, whence=0):
79 79 '''virtual file offset spans real file and data'''
80 80 if whence == 0:
81 81 self.offset = offset
82 82 elif whence == 1:
83 83 self.offset += offset
84 84 elif whence == 2:
85 85 self.offset = self.end() + offset
86 86 if self.offset < self.size:
87 87 self.fp.seek(self.offset)
88 88
89 89 def read(self, count=-1):
90 90 '''only trick here is reads that span real file and data'''
91 91 ret = ""
92 92 if self.offset < self.size:
93 93 s = self.fp.read(count)
94 94 ret = s
95 95 self.offset += len(s)
96 96 if count > 0:
97 97 count -= len(s)
98 98 if count != 0:
99 99 doff = self.offset - self.size
100 100 self.data.insert(0, "".join(self.data))
101 101 del self.data[1:]
102 102 s = self.data[0][doff:doff + count]
103 103 self.offset += len(s)
104 104 ret += s
105 105 return ret
106 106
107 107 def write(self, s):
108 108 self.data.append(str(s))
109 109 self.offset += len(s)
110 110
111 111 def _divertopener(opener, target):
112 112 """build an opener that writes in 'target.a' instead of 'target'"""
113 113 def _divert(name, mode='r'):
114 114 if name != target:
115 115 return opener(name, mode)
116 116 return opener(name + ".a", mode)
117 117 return _divert
118 118
119 119 def _delayopener(opener, target, buf):
120 120 """build an opener that stores chunks in 'buf' instead of 'target'"""
121 121 def _delay(name, mode='r'):
122 122 if name != target:
123 123 return opener(name, mode)
124 124 return appender(opener, name, mode, buf)
125 125 return _delay
126 126
127 127 class changelog(revlog.revlog):
128 128 def __init__(self, opener):
129 129 revlog.revlog.__init__(self, opener, "00changelog.i")
130 130 if self._initempty:
131 131 # changelogs don't benefit from generaldelta
132 132 self.version &= ~revlog.REVLOGGENERALDELTA
133 133 self._generaldelta = False
134 134 self._realopener = opener
135 135 self._delayed = False
136 136 self._delaybuf = None
137 137 self._divert = False
138 138 self.filteredrevs = frozenset()
139 139
140 140 def tip(self):
141 141 """filtered version of revlog.tip"""
142 142 for i in xrange(len(self) -1, -2, -1):
143 143 if i not in self.filteredrevs:
144 144 return self.node(i)
145 145
146 146 def __contains__(self, rev):
147 147 """filtered version of revlog.__contains__"""
148 148 return (0 <= rev < len(self)
149 149 and rev not in self.filteredrevs)
150 150
151 151 def __iter__(self):
152 152 """filtered version of revlog.__iter__"""
153 153 if len(self.filteredrevs) == 0:
154 154 return revlog.revlog.__iter__(self)
155 155
156 156 def filterediter():
157 157 for i in xrange(len(self)):
158 158 if i not in self.filteredrevs:
159 159 yield i
160 160
161 161 return filterediter()
162 162
163 163 def revs(self, start=0, stop=None):
164 164 """filtered version of revlog.revs"""
165 165 for i in super(changelog, self).revs(start, stop):
166 166 if i not in self.filteredrevs:
167 167 yield i
168 168
169 169 @util.propertycache
170 170 def nodemap(self):
171 171 # XXX need filtering too
172 172 self.rev(self.node(0))
173 173 return self._nodecache
174 174
175 175 def headrevs(self):
176 176 if self.filteredrevs:
177 177 try:
178 178 return self.index.headrevsfiltered(self.filteredrevs)
179 179 # AttributeError covers non-c-extension environments and
180 180 # old c extensions without filter handling.
181 181 except AttributeError:
182 182 return self._headrevs()
183 183
184 184 return super(changelog, self).headrevs()
185 185
186 186 def strip(self, *args, **kwargs):
187 187 # XXX make something better than assert
188 188 # We can't expect proper strip behavior if we are filtered.
189 189 assert not self.filteredrevs
190 190 super(changelog, self).strip(*args, **kwargs)
191 191
192 192 def rev(self, node):
193 193 """filtered version of revlog.rev"""
194 194 r = super(changelog, self).rev(node)
195 195 if r in self.filteredrevs:
196 196 raise error.FilteredLookupError(hex(node), self.indexfile,
197 197 _('filtered node'))
198 198 return r
199 199
200 200 def node(self, rev):
201 201 """filtered version of revlog.node"""
202 202 if rev in self.filteredrevs:
203 203 raise error.FilteredIndexError(rev)
204 204 return super(changelog, self).node(rev)
205 205
206 206 def linkrev(self, rev):
207 207 """filtered version of revlog.linkrev"""
208 208 if rev in self.filteredrevs:
209 209 raise error.FilteredIndexError(rev)
210 210 return super(changelog, self).linkrev(rev)
211 211
212 212 def parentrevs(self, rev):
213 213 """filtered version of revlog.parentrevs"""
214 214 if rev in self.filteredrevs:
215 215 raise error.FilteredIndexError(rev)
216 216 return super(changelog, self).parentrevs(rev)
217 217
218 218 def flags(self, rev):
219 219 """filtered version of revlog.flags"""
220 220 if rev in self.filteredrevs:
221 221 raise error.FilteredIndexError(rev)
222 222 return super(changelog, self).flags(rev)
223 223
224 224 def delayupdate(self, tr):
225 225 "delay visibility of index updates to other readers"
226 226
227 227 if not self._delayed:
228 228 if len(self) == 0:
229 229 self._divert = True
230 230 if self._realopener.exists(self.indexfile + '.a'):
231 231 self._realopener.unlink(self.indexfile + '.a')
232 232 self.opener = _divertopener(self._realopener, self.indexfile)
233 233 else:
234 234 self._delaybuf = []
235 235 self.opener = _delayopener(self._realopener, self.indexfile,
236 236 self._delaybuf)
237 237 self._delayed = True
238 238 tr.addpending('cl-%i' % id(self), self._writepending)
239 239 tr.addfinalize('cl-%i' % id(self), self._finalize)
240 240
241 241 def _finalize(self, tr):
242 242 "finalize index updates"
243 243 self._delayed = False
244 244 self.opener = self._realopener
245 245 # move redirected index data back into place
246 246 if self._divert:
247 247 assert not self._delaybuf
248 248 tmpname = self.indexfile + ".a"
249 249 nfile = self.opener.open(tmpname)
250 250 nfile.close()
251 251 self.opener.rename(tmpname, self.indexfile)
252 252 elif self._delaybuf:
253 253 fp = self.opener(self.indexfile, 'a')
254 254 fp.write("".join(self._delaybuf))
255 255 fp.close()
256 256 self._delaybuf = None
257 257 self._divert = False
258 258 # split when we're done
259 259 self.checkinlinesize(tr)
260 260
261 261 def readpending(self, file):
262 262 """read index data from a "pending" file
263 263
264 264 During a transaction, the actual changeset data is already stored in the
265 265 main file, but not yet finalized in the on-disk index. Instead, a
266 266 "pending" index is written by the transaction logic. If this function
267 267 is running, we are likely in a subprocess invoked in a hook. The
268 268 subprocess is informed that it is within a transaction and needs to
269 269 access its content.
270 270
271 271 This function will read all the index data out of the pending file and
272 extend the main index."""
272 overwrite the main index."""
273 273
274 274 if not self.opener.exists(file):
275 275 return # no pending data for changelog
276 276 r = revlog.revlog(self.opener, file)
277 277 self.index = r.index
278 278 self.nodemap = r.nodemap
279 279 self._nodecache = r._nodecache
280 280 self._chunkcache = r._chunkcache
281 281
282 282 def _writepending(self, tr):
283 283 "create a file containing the unfinalized state for pretxnchangegroup"
284 284 if self._delaybuf:
285 285 # make a temporary copy of the index
286 286 fp1 = self._realopener(self.indexfile)
287 287 pendingfilename = self.indexfile + ".a"
288 288 # register as a temp file to ensure cleanup on failure
289 289 tr.registertmp(pendingfilename)
290 290 # write existing data
291 291 fp2 = self._realopener(pendingfilename, "w")
292 292 fp2.write(fp1.read())
293 293 # add pending data
294 294 fp2.write("".join(self._delaybuf))
295 295 fp2.close()
296 296 # switch modes so finalize can simply rename
297 297 self._delaybuf = None
298 298 self._divert = True
299 299 self.opener = _divertopener(self._realopener, self.indexfile)
300 300
301 301 if self._divert:
302 302 return True
303 303
304 304 return False
305 305
306 306 def checkinlinesize(self, tr, fp=None):
307 307 if not self._delayed:
308 308 revlog.revlog.checkinlinesize(self, tr, fp)
309 309
310 310 def read(self, node):
311 311 """
312 312 format used:
313 313 nodeid\n : manifest node in ascii
314 314 user\n : user, no \n or \r allowed
315 315 time tz extra\n : date (time is int or float, timezone is int)
316 316 : extra is metadata, encoded and separated by '\0'
317 317 : older versions ignore it
318 318 files\n\n : files modified by the cset, no \n or \r allowed
319 319 (.*) : comment (free text, ideally utf-8)
320 320
321 321 changelog v0 doesn't use extra
322 322 """
323 323 text = self.revision(node)
324 324 if not text:
325 325 return (nullid, "", (0, 0), [], "", _defaultextra)
326 326 last = text.index("\n\n")
327 327 desc = encoding.tolocal(text[last + 2:])
328 328 l = text[:last].split('\n')
329 329 manifest = bin(l[0])
330 330 user = encoding.tolocal(l[1])
331 331
332 332 tdata = l[2].split(' ', 2)
333 333 if len(tdata) != 3:
334 334 time = float(tdata[0])
335 335 try:
336 336 # various tools did silly things with the time zone field.
337 337 timezone = int(tdata[1])
338 338 except ValueError:
339 339 timezone = 0
340 340 extra = _defaultextra
341 341 else:
342 342 time, timezone = float(tdata[0]), int(tdata[1])
343 343 extra = decodeextra(tdata[2])
344 344
345 345 files = l[3:]
346 346 return (manifest, user, (time, timezone), files, desc, extra)
347 347
348 348 def add(self, manifest, files, desc, transaction, p1, p2,
349 349 user, date=None, extra=None):
350 350 # Convert to UTF-8 encoded bytestrings as the very first
351 351 # thing: calling any method on a localstr object will turn it
352 352 # into a str object and the cached UTF-8 string is thus lost.
353 353 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
354 354
355 355 user = user.strip()
356 356 # An empty username or a username with a "\n" will make the
357 357 # revision text contain two "\n\n" sequences -> corrupt
358 358 # repository since read cannot unpack the revision.
359 359 if not user:
360 360 raise error.RevlogError(_("empty username"))
361 361 if "\n" in user:
362 362 raise error.RevlogError(_("username %s contains a newline")
363 363 % repr(user))
364 364
365 365 desc = stripdesc(desc)
366 366
367 367 if date:
368 368 parseddate = "%d %d" % util.parsedate(date)
369 369 else:
370 370 parseddate = "%d %d" % util.makedate()
371 371 if extra:
372 372 branch = extra.get("branch")
373 373 if branch in ("default", ""):
374 374 del extra["branch"]
375 375 elif branch in (".", "null", "tip"):
376 376 raise error.RevlogError(_('the name \'%s\' is reserved')
377 377 % branch)
378 378 if extra:
379 379 extra = encodeextra(extra)
380 380 parseddate = "%s %s" % (parseddate, extra)
381 381 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
382 382 text = "\n".join(l)
383 383 return self.addrevision(text, transaction, len(self), p1, p2)
384 384
385 385 def branchinfo(self, rev):
386 386 """return the branch name and open/close state of a revision
387 387
388 388 This function exists because creating a changectx object
389 389 just to access this is costly."""
390 390 extra = self.read(rev)[5]
391 391 return encoding.tolocal(extra.get("branch")), 'close' in extra
General Comments 0
You need to be logged in to leave comments. Login now