##// END OF EJS Templates
changelog: factor out _delayname
Matt Mackall -
r9164:b0d995b6 default
parent child Browse files
Show More
@@ -1,232 +1,230 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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
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 class changelog(revlog.revlog):
90 class changelog(revlog.revlog):
91 def __init__(self, opener):
91 def __init__(self, opener):
92 self._realopener = opener
92 self._realopener = opener
93 self._delayed = False
93 self._delayed = False
94 self._divert = False
94 self._divert = False
95 revlog.revlog.__init__(self, self._delayopener, "00changelog.i")
95 revlog.revlog.__init__(self, self._delayopener, "00changelog.i")
96
96
97
98 def delayupdate(self):
97 def delayupdate(self):
99 "delay visibility of index updates to other readers"
98 "delay visibility of index updates to other readers"
100 self._delayed = True
99 self._delayed = True
101 self._divert = (len(self) == 0)
100 self._divert = (len(self) == 0)
102 self._delaybuf = []
101 self._delaybuf = []
103 self._delayname = None
104
102
105 def finalize(self, tr):
103 def finalize(self, tr):
106 "finalize index updates"
104 "finalize index updates"
107 self._delayed = False
105 self._delayed = False
108 # move redirected index data back into place
106 # move redirected index data back into place
109 if self._delayname:
107 if self._divert:
110 util.rename(self._delayname + ".a", self._delayname)
108 n = self._realopener(self.indexfile).name
109 util.rename(n + ".a", n)
111 elif self._delaybuf:
110 elif self._delaybuf:
112 fp = self.opener(self.indexfile, 'a')
111 fp = self.opener(self.indexfile, 'a')
113 fp.write("".join(self._delaybuf))
112 fp.write("".join(self._delaybuf))
114 fp.close()
113 fp.close()
115 self._delaybuf = []
114 self._delaybuf = []
116 # split when we're done
115 # split when we're done
117 self.checkinlinesize(tr)
116 self.checkinlinesize(tr)
118
117
119 def _delayopener(self, name, mode='r'):
118 def _delayopener(self, name, mode='r'):
120 fp = self._realopener(name, mode)
119 fp = self._realopener(name, mode)
121 # only divert the index
120 # only divert the index
122 if not self._delayed or not name == self.indexfile:
121 if not self._delayed or not name == self.indexfile:
123 return fp
122 return fp
124 # if we're doing an initial clone, divert to another file
123 # if we're doing an initial clone, divert to another file
125 if self._divert:
124 if self._divert:
126 self._delayname = fp.name
127 if not len(self):
125 if not len(self):
128 # make sure to truncate the file
126 # make sure to truncate the file
129 mode = mode.replace('a', 'w')
127 mode = mode.replace('a', 'w')
130 return self._realopener(name + ".a", mode)
128 return self._realopener(name + ".a", mode)
131 # otherwise, divert to memory
129 # otherwise, divert to memory
132 return appender(fp, self._delaybuf)
130 return appender(fp, self._delaybuf)
133
131
134 def readpending(self, file):
132 def readpending(self, file):
135 r = revlog.revlog(self.opener, file)
133 r = revlog.revlog(self.opener, file)
136 self.index = r.index
134 self.index = r.index
137 self.nodemap = r.nodemap
135 self.nodemap = r.nodemap
138 self._chunkcache = r._chunkcache
136 self._chunkcache = r._chunkcache
139
137
140 def writepending(self):
138 def writepending(self):
141 "create a file containing the unfinalized state for pretxnchangegroup"
139 "create a file containing the unfinalized state for pretxnchangegroup"
142 if self._delaybuf:
140 if self._delaybuf:
143 # make a temporary copy of the index
141 # make a temporary copy of the index
144 fp1 = self._realopener(self.indexfile)
142 fp1 = self._realopener(self.indexfile)
145 fp2 = self._realopener(self.indexfile + ".a", "w")
143 fp2 = self._realopener(self.indexfile + ".a", "w")
146 fp2.write(fp1.read())
144 fp2.write(fp1.read())
147 # add pending data
145 # add pending data
148 fp2.write("".join(self._delaybuf))
146 fp2.write("".join(self._delaybuf))
149 fp2.close()
147 fp2.close()
150 # switch modes so finalize can simply rename
148 # switch modes so finalize can simply rename
151 self._delaybuf = []
149 self._delaybuf = []
152 self._delayname = fp1.name
150 self._divert = True
153
151
154 if self._delayname:
152 if self._divert:
155 return True
153 return True
156
154
157 return False
155 return False
158
156
159 def checkinlinesize(self, tr, fp=None):
157 def checkinlinesize(self, tr, fp=None):
160 if self.opener == self._delayopener:
158 if self.opener == self._delayopener:
161 return
159 return
162 return revlog.revlog.checkinlinesize(self, tr, fp)
160 return revlog.revlog.checkinlinesize(self, tr, fp)
163
161
164 def read(self, node):
162 def read(self, node):
165 """
163 """
166 format used:
164 format used:
167 nodeid\n : manifest node in ascii
165 nodeid\n : manifest node in ascii
168 user\n : user, no \n or \r allowed
166 user\n : user, no \n or \r allowed
169 time tz extra\n : date (time is int or float, timezone is int)
167 time tz extra\n : date (time is int or float, timezone is int)
170 : extra is metadatas, encoded and separated by '\0'
168 : extra is metadatas, encoded and separated by '\0'
171 : older versions ignore it
169 : older versions ignore it
172 files\n\n : files modified by the cset, no \n or \r allowed
170 files\n\n : files modified by the cset, no \n or \r allowed
173 (.*) : comment (free text, ideally utf-8)
171 (.*) : comment (free text, ideally utf-8)
174
172
175 changelog v0 doesn't use extra
173 changelog v0 doesn't use extra
176 """
174 """
177 text = self.revision(node)
175 text = self.revision(node)
178 if not text:
176 if not text:
179 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
177 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
180 last = text.index("\n\n")
178 last = text.index("\n\n")
181 desc = encoding.tolocal(text[last + 2:])
179 desc = encoding.tolocal(text[last + 2:])
182 l = text[:last].split('\n')
180 l = text[:last].split('\n')
183 manifest = bin(l[0])
181 manifest = bin(l[0])
184 user = encoding.tolocal(l[1])
182 user = encoding.tolocal(l[1])
185
183
186 extra_data = l[2].split(' ', 2)
184 extra_data = l[2].split(' ', 2)
187 if len(extra_data) != 3:
185 if len(extra_data) != 3:
188 time = float(extra_data.pop(0))
186 time = float(extra_data.pop(0))
189 try:
187 try:
190 # various tools did silly things with the time zone field.
188 # various tools did silly things with the time zone field.
191 timezone = int(extra_data[0])
189 timezone = int(extra_data[0])
192 except:
190 except:
193 timezone = 0
191 timezone = 0
194 extra = {}
192 extra = {}
195 else:
193 else:
196 time, timezone, extra = extra_data
194 time, timezone, extra = extra_data
197 time, timezone = float(time), int(timezone)
195 time, timezone = float(time), int(timezone)
198 extra = decodeextra(extra)
196 extra = decodeextra(extra)
199 if not extra.get('branch'):
197 if not extra.get('branch'):
200 extra['branch'] = 'default'
198 extra['branch'] = 'default'
201 files = l[3:]
199 files = l[3:]
202 return (manifest, user, (time, timezone), files, desc, extra)
200 return (manifest, user, (time, timezone), files, desc, extra)
203
201
204 def add(self, manifest, files, desc, transaction, p1, p2,
202 def add(self, manifest, files, desc, transaction, p1, p2,
205 user, date=None, extra={}):
203 user, date=None, extra={}):
206 user = user.strip()
204 user = user.strip()
207 # An empty username or a username with a "\n" will make the
205 # An empty username or a username with a "\n" will make the
208 # revision text contain two "\n\n" sequences -> corrupt
206 # revision text contain two "\n\n" sequences -> corrupt
209 # repository since read cannot unpack the revision.
207 # repository since read cannot unpack the revision.
210 if not user:
208 if not user:
211 raise error.RevlogError(_("empty username"))
209 raise error.RevlogError(_("empty username"))
212 if "\n" in user:
210 if "\n" in user:
213 raise error.RevlogError(_("username %s contains a newline")
211 raise error.RevlogError(_("username %s contains a newline")
214 % repr(user))
212 % repr(user))
215
213
216 # strip trailing whitespace and leading and trailing empty lines
214 # strip trailing whitespace and leading and trailing empty lines
217 desc = '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
215 desc = '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
218
216
219 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
217 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
220
218
221 if date:
219 if date:
222 parseddate = "%d %d" % util.parsedate(date)
220 parseddate = "%d %d" % util.parsedate(date)
223 else:
221 else:
224 parseddate = "%d %d" % util.makedate()
222 parseddate = "%d %d" % util.makedate()
225 if extra and extra.get("branch") in ("default", ""):
223 if extra and extra.get("branch") in ("default", ""):
226 del extra["branch"]
224 del extra["branch"]
227 if extra:
225 if extra:
228 extra = encodeextra(extra)
226 extra = encodeextra(extra)
229 parseddate = "%s %s" % (parseddate, extra)
227 parseddate = "%s %s" % (parseddate, extra)
230 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
228 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
231 text = "\n".join(l)
229 text = "\n".join(l)
232 return self.addrevision(text, transaction, len(self), p1, p2)
230 return self.addrevision(text, transaction, len(self), p1, p2)
General Comments 0
You need to be logged in to leave comments. Login now