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