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