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