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