##// END OF EJS Templates
changelog: refuse to add revisions with empty usernames...
Martin Geisler -
r8424:c5b3d3e3 default
parent child Browse files
Show More
@@ -1,221 +1,226
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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import bin, hex, nullid
9 9 from i18n import _
10 10 import util, error, revlog
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 class appender:
27 27 '''the changelog index must be updated last on disk, so we use this class
28 28 to delay writes to it'''
29 29 def __init__(self, fp, buf):
30 30 self.data = buf
31 31 self.fp = fp
32 32 self.offset = fp.tell()
33 33 self.size = util.fstat(fp).st_size
34 34
35 35 def end(self):
36 36 return self.size + len("".join(self.data))
37 37 def tell(self):
38 38 return self.offset
39 39 def flush(self):
40 40 pass
41 41 def close(self):
42 42 self.fp.close()
43 43
44 44 def seek(self, offset, whence=0):
45 45 '''virtual file offset spans real file and data'''
46 46 if whence == 0:
47 47 self.offset = offset
48 48 elif whence == 1:
49 49 self.offset += offset
50 50 elif whence == 2:
51 51 self.offset = self.end() + offset
52 52 if self.offset < self.size:
53 53 self.fp.seek(self.offset)
54 54
55 55 def read(self, count=-1):
56 56 '''only trick here is reads that span real file and data'''
57 57 ret = ""
58 58 if self.offset < self.size:
59 59 s = self.fp.read(count)
60 60 ret = s
61 61 self.offset += len(s)
62 62 if count > 0:
63 63 count -= len(s)
64 64 if count != 0:
65 65 doff = self.offset - self.size
66 66 self.data.insert(0, "".join(self.data))
67 67 del self.data[1:]
68 68 s = self.data[0][doff:doff+count]
69 69 self.offset += len(s)
70 70 ret += s
71 71 return ret
72 72
73 73 def write(self, s):
74 74 self.data.append(str(s))
75 75 self.offset += len(s)
76 76
77 77 class changelog(revlog.revlog):
78 78 def __init__(self, opener):
79 79 revlog.revlog.__init__(self, opener, "00changelog.i")
80 80
81 81 def delayupdate(self):
82 82 "delay visibility of index updates to other readers"
83 83 self._realopener = self.opener
84 84 self.opener = self._delayopener
85 85 self._delaycount = len(self)
86 86 self._delaybuf = []
87 87 self._delayname = None
88 88
89 89 def finalize(self, tr):
90 90 "finalize index updates"
91 91 self.opener = self._realopener
92 92 # move redirected index data back into place
93 93 if self._delayname:
94 94 util.rename(self._delayname + ".a", self._delayname)
95 95 elif self._delaybuf:
96 96 fp = self.opener(self.indexfile, 'a')
97 97 fp.write("".join(self._delaybuf))
98 98 fp.close()
99 99 self._delaybuf = []
100 100 # split when we're done
101 101 self.checkinlinesize(tr)
102 102
103 103 def _delayopener(self, name, mode='r'):
104 104 fp = self._realopener(name, mode)
105 105 # only divert the index
106 106 if not name == self.indexfile:
107 107 return fp
108 108 # if we're doing an initial clone, divert to another file
109 109 if self._delaycount == 0:
110 110 self._delayname = fp.name
111 111 if not len(self):
112 112 # make sure to truncate the file
113 113 mode = mode.replace('a', 'w')
114 114 return self._realopener(name + ".a", mode)
115 115 # otherwise, divert to memory
116 116 return appender(fp, self._delaybuf)
117 117
118 118 def readpending(self, file):
119 119 r = revlog.revlog(self.opener, file)
120 120 self.index = r.index
121 121 self.nodemap = r.nodemap
122 122 self._chunkcache = r._chunkcache
123 123
124 124 def writepending(self):
125 125 "create a file containing the unfinalized state for pretxnchangegroup"
126 126 if self._delaybuf:
127 127 # make a temporary copy of the index
128 128 fp1 = self._realopener(self.indexfile)
129 129 fp2 = self._realopener(self.indexfile + ".a", "w")
130 130 fp2.write(fp1.read())
131 131 # add pending data
132 132 fp2.write("".join(self._delaybuf))
133 133 fp2.close()
134 134 # switch modes so finalize can simply rename
135 135 self._delaybuf = []
136 136 self._delayname = fp1.name
137 137
138 138 if self._delayname:
139 139 return True
140 140
141 141 return False
142 142
143 143 def checkinlinesize(self, tr, fp=None):
144 144 if self.opener == self._delayopener:
145 145 return
146 146 return revlog.revlog.checkinlinesize(self, tr, fp)
147 147
148 148 def decode_extra(self, text):
149 149 extra = {}
150 150 for l in text.split('\0'):
151 151 if l:
152 152 k, v = l.decode('string_escape').split(':', 1)
153 153 extra[k] = v
154 154 return extra
155 155
156 156 def encode_extra(self, d):
157 157 # keys must be sorted to produce a deterministic changelog entry
158 158 items = [_string_escape('%s:%s' % (k, d[k])) for k in util.sort(d)]
159 159 return "\0".join(items)
160 160
161 161 def read(self, node):
162 162 """
163 163 format used:
164 164 nodeid\n : manifest node in ascii
165 165 user\n : user, no \n or \r allowed
166 166 time tz extra\n : date (time is int or float, timezone is int)
167 167 : extra is metadatas, encoded and separated by '\0'
168 168 : older versions ignore it
169 169 files\n\n : files modified by the cset, no \n or \r allowed
170 170 (.*) : comment (free text, ideally utf-8)
171 171
172 172 changelog v0 doesn't use extra
173 173 """
174 174 text = self.revision(node)
175 175 if not text:
176 176 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
177 177 last = text.index("\n\n")
178 178 desc = util.tolocal(text[last + 2:])
179 179 l = text[:last].split('\n')
180 180 manifest = bin(l[0])
181 181 user = util.tolocal(l[1])
182 182
183 183 extra_data = l[2].split(' ', 2)
184 184 if len(extra_data) != 3:
185 185 time = float(extra_data.pop(0))
186 186 try:
187 187 # various tools did silly things with the time zone field.
188 188 timezone = int(extra_data[0])
189 189 except:
190 190 timezone = 0
191 191 extra = {}
192 192 else:
193 193 time, timezone, extra = extra_data
194 194 time, timezone = float(time), int(timezone)
195 195 extra = self.decode_extra(extra)
196 196 if not extra.get('branch'):
197 197 extra['branch'] = 'default'
198 198 files = l[3:]
199 199 return (manifest, user, (time, timezone), files, desc, extra)
200 200
201 201 def add(self, manifest, files, desc, transaction, p1=None, p2=None,
202 202 user=None, date=None, extra={}):
203 203
204 204 user = user.strip()
205 # An empty username or a username with a "\n" will make the
206 # revision text contain two "\n\n" sequences -> corrupt
207 # repository since read cannot unpack the revision.
208 if not user:
209 raise error.RevlogError(_("empty username"))
205 210 if "\n" in user:
206 211 raise error.RevlogError(_("username %s contains a newline")
207 212 % repr(user))
208 213 user, desc = util.fromlocal(user), util.fromlocal(desc)
209 214
210 215 if date:
211 216 parseddate = "%d %d" % util.parsedate(date)
212 217 else:
213 218 parseddate = "%d %d" % util.makedate()
214 219 if extra and extra.get("branch") in ("default", ""):
215 220 del extra["branch"]
216 221 if extra:
217 222 extra = self.encode_extra(extra)
218 223 parseddate = "%s %s" % (parseddate, extra)
219 224 l = [hex(manifest), user, parseddate] + util.sort(files) + ["", desc]
220 225 text = "\n".join(l)
221 226 return self.addrevision(text, transaction, len(self), p1, p2)
@@ -1,31 +1,36
1 1 #!/bin/sh
2 2
3 3 unset HGUSER
4 4 EMAIL="My Name <myname@example.com>"
5 5 export EMAIL
6 6
7 7 hg init test
8 8 cd test
9 9 touch asdf
10 10 hg add asdf
11 11 hg commit -d '1000000 0' -m commit-1
12 12 hg tip
13 13
14 14 unset EMAIL
15 15 echo 1234 > asdf
16 16 hg commit -d '1000000 0' -u "foo@bar.com" -m commit-1
17 17 hg tip
18 18 echo "[ui]" >> .hg/hgrc
19 19 echo "username = foobar <foo@bar.com>" >> .hg/hgrc
20 20 echo 12 > asdf
21 21 hg commit -d '1000000 0' -m commit-1
22 22 hg tip
23 23 echo 1 > asdf
24 24 hg commit -d '1000000 0' -u "foo@bar.com" -m commit-1
25 25 hg tip
26 26 echo 123 > asdf
27 27 echo "[ui]" > .hg/hgrc
28 28 echo "username = " >> .hg/hgrc
29 29 hg commit -d '1000000 0' -m commit-1
30 30 rm .hg/hgrc
31 31 hg commit -d '1000000 0' -m commit-1 2>&1 | sed -e "s/'[^']*'/user@host/"
32
33 echo space > asdf
34 hg commit -d '1000000 0' -u ' ' -m commit-1
35
36 true
@@ -1,26 +1,29
1 1 changeset: 0:9426b370c206
2 2 tag: tip
3 3 user: My Name <myname@example.com>
4 4 date: Mon Jan 12 13:46:40 1970 +0000
5 5 summary: commit-1
6 6
7 7 changeset: 1:4997f15a1b24
8 8 tag: tip
9 9 user: foo@bar.com
10 10 date: Mon Jan 12 13:46:40 1970 +0000
11 11 summary: commit-1
12 12
13 13 changeset: 2:72b8012b424e
14 14 tag: tip
15 15 user: foobar <foo@bar.com>
16 16 date: Mon Jan 12 13:46:40 1970 +0000
17 17 summary: commit-1
18 18
19 19 changeset: 3:35ff3067bedd
20 20 tag: tip
21 21 user: foo@bar.com
22 22 date: Mon Jan 12 13:46:40 1970 +0000
23 23 summary: commit-1
24 24
25 25 abort: Please specify a username.
26 26 No username found, using user@host instead
27 transaction abort!
28 rollback completed
29 abort: empty username!
General Comments 0
You need to be logged in to leave comments. Login now