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