##// END OF EJS Templates
util: get rid of is_win_9x wart
Matt Mackall -
r5659:3da652f2 default
parent child Browse files
Show More
@@ -1,1311 +1,1309 b''
1 """
1 """
2 revlog.py - storage back-end for mercurial
2 revlog.py - storage back-end for mercurial
3
3
4 This provides efficient delta storage with O(1) retrieve and append
4 This provides efficient delta storage with O(1) retrieve and append
5 and O(changes) merge between branches
5 and O(changes) merge between branches
6
6
7 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
7 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
8
8
9 This software may be used and distributed according to the terms
9 This software may be used and distributed according to the terms
10 of the GNU General Public License, incorporated herein by reference.
10 of the GNU General Public License, incorporated herein by reference.
11 """
11 """
12
12
13 from node import *
13 from node import *
14 from i18n import _
14 from i18n import _
15 import binascii, changegroup, errno, ancestor, mdiff, os
15 import binascii, changegroup, errno, ancestor, mdiff, os
16 import sha, struct, util, zlib
16 import sha, struct, util, zlib
17
17
18 _pack = struct.pack
18 _pack = struct.pack
19 _unpack = struct.unpack
19 _unpack = struct.unpack
20 _compress = zlib.compress
20 _compress = zlib.compress
21 _decompress = zlib.decompress
21 _decompress = zlib.decompress
22 _sha = sha.new
22 _sha = sha.new
23
23
24 # revlog flags
24 # revlog flags
25 REVLOGV0 = 0
25 REVLOGV0 = 0
26 REVLOGNG = 1
26 REVLOGNG = 1
27 REVLOGNGINLINEDATA = (1 << 16)
27 REVLOGNGINLINEDATA = (1 << 16)
28 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
28 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
29 REVLOG_DEFAULT_FORMAT = REVLOGNG
29 REVLOG_DEFAULT_FORMAT = REVLOGNG
30 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
30 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
31
31
32 class RevlogError(Exception):
32 class RevlogError(Exception):
33 pass
33 pass
34
34
35 class LookupError(RevlogError):
35 class LookupError(RevlogError):
36 def __init__(self, name, message=None):
36 def __init__(self, name, message=None):
37 if message is None:
37 if message is None:
38 message = _('not found: %s') % name
38 message = _('not found: %s') % name
39 RevlogError.__init__(self, message)
39 RevlogError.__init__(self, message)
40 self.name = name
40 self.name = name
41
41
42 def getoffset(q):
42 def getoffset(q):
43 return int(q >> 16)
43 return int(q >> 16)
44
44
45 def gettype(q):
45 def gettype(q):
46 return int(q & 0xFFFF)
46 return int(q & 0xFFFF)
47
47
48 def offset_type(offset, type):
48 def offset_type(offset, type):
49 return long(long(offset) << 16 | type)
49 return long(long(offset) << 16 | type)
50
50
51 def hash(text, p1, p2):
51 def hash(text, p1, p2):
52 """generate a hash from the given text and its parent hashes
52 """generate a hash from the given text and its parent hashes
53
53
54 This hash combines both the current file contents and its history
54 This hash combines both the current file contents and its history
55 in a manner that makes it easy to distinguish nodes with the same
55 in a manner that makes it easy to distinguish nodes with the same
56 content in the revision graph.
56 content in the revision graph.
57 """
57 """
58 l = [p1, p2]
58 l = [p1, p2]
59 l.sort()
59 l.sort()
60 s = _sha(l[0])
60 s = _sha(l[0])
61 s.update(l[1])
61 s.update(l[1])
62 s.update(text)
62 s.update(text)
63 return s.digest()
63 return s.digest()
64
64
65 def compress(text):
65 def compress(text):
66 """ generate a possibly-compressed representation of text """
66 """ generate a possibly-compressed representation of text """
67 if not text:
67 if not text:
68 return ("", text)
68 return ("", text)
69 l = len(text)
69 l = len(text)
70 bin = None
70 bin = None
71 if l < 44:
71 if l < 44:
72 pass
72 pass
73 elif l > 1000000:
73 elif l > 1000000:
74 # zlib makes an internal copy, thus doubling memory usage for
74 # zlib makes an internal copy, thus doubling memory usage for
75 # large files, so lets do this in pieces
75 # large files, so lets do this in pieces
76 z = zlib.compressobj()
76 z = zlib.compressobj()
77 p = []
77 p = []
78 pos = 0
78 pos = 0
79 while pos < l:
79 while pos < l:
80 pos2 = pos + 2**20
80 pos2 = pos + 2**20
81 p.append(z.compress(text[pos:pos2]))
81 p.append(z.compress(text[pos:pos2]))
82 pos = pos2
82 pos = pos2
83 p.append(z.flush())
83 p.append(z.flush())
84 if sum(map(len, p)) < l:
84 if sum(map(len, p)) < l:
85 bin = "".join(p)
85 bin = "".join(p)
86 else:
86 else:
87 bin = _compress(text)
87 bin = _compress(text)
88 if bin is None or len(bin) > l:
88 if bin is None or len(bin) > l:
89 if text[0] == '\0':
89 if text[0] == '\0':
90 return ("", text)
90 return ("", text)
91 return ('u', text)
91 return ('u', text)
92 return ("", bin)
92 return ("", bin)
93
93
94 def decompress(bin):
94 def decompress(bin):
95 """ decompress the given input """
95 """ decompress the given input """
96 if not bin:
96 if not bin:
97 return bin
97 return bin
98 t = bin[0]
98 t = bin[0]
99 if t == '\0':
99 if t == '\0':
100 return bin
100 return bin
101 if t == 'x':
101 if t == 'x':
102 return _decompress(bin)
102 return _decompress(bin)
103 if t == 'u':
103 if t == 'u':
104 return bin[1:]
104 return bin[1:]
105 raise RevlogError(_("unknown compression type %r") % t)
105 raise RevlogError(_("unknown compression type %r") % t)
106
106
107 class lazyparser(object):
107 class lazyparser(object):
108 """
108 """
109 this class avoids the need to parse the entirety of large indices
109 this class avoids the need to parse the entirety of large indices
110 """
110 """
111
111
112 # lazyparser is not safe to use on windows if win32 extensions not
112 # lazyparser is not safe to use on windows if win32 extensions not
113 # available. it keeps file handle open, which make it not possible
113 # available. it keeps file handle open, which make it not possible
114 # to break hardlinks on local cloned repos.
114 # to break hardlinks on local cloned repos.
115 safe_to_use = os.name != 'nt' or (not util.is_win_9x() and
116 hasattr(util, 'win32api'))
117
115
118 def __init__(self, dataf, size):
116 def __init__(self, dataf, size):
119 self.dataf = dataf
117 self.dataf = dataf
120 self.s = struct.calcsize(indexformatng)
118 self.s = struct.calcsize(indexformatng)
121 self.datasize = size
119 self.datasize = size
122 self.l = size/self.s
120 self.l = size/self.s
123 self.index = [None] * self.l
121 self.index = [None] * self.l
124 self.map = {nullid: nullrev}
122 self.map = {nullid: nullrev}
125 self.allmap = 0
123 self.allmap = 0
126 self.all = 0
124 self.all = 0
127 self.mapfind_count = 0
125 self.mapfind_count = 0
128
126
129 def loadmap(self):
127 def loadmap(self):
130 """
128 """
131 during a commit, we need to make sure the rev being added is
129 during a commit, we need to make sure the rev being added is
132 not a duplicate. This requires loading the entire index,
130 not a duplicate. This requires loading the entire index,
133 which is fairly slow. loadmap can load up just the node map,
131 which is fairly slow. loadmap can load up just the node map,
134 which takes much less time.
132 which takes much less time.
135 """
133 """
136 if self.allmap:
134 if self.allmap:
137 return
135 return
138 end = self.datasize
136 end = self.datasize
139 self.allmap = 1
137 self.allmap = 1
140 cur = 0
138 cur = 0
141 count = 0
139 count = 0
142 blocksize = self.s * 256
140 blocksize = self.s * 256
143 self.dataf.seek(0)
141 self.dataf.seek(0)
144 while cur < end:
142 while cur < end:
145 data = self.dataf.read(blocksize)
143 data = self.dataf.read(blocksize)
146 off = 0
144 off = 0
147 for x in xrange(256):
145 for x in xrange(256):
148 n = data[off + ngshaoffset:off + ngshaoffset + 20]
146 n = data[off + ngshaoffset:off + ngshaoffset + 20]
149 self.map[n] = count
147 self.map[n] = count
150 count += 1
148 count += 1
151 if count >= self.l:
149 if count >= self.l:
152 break
150 break
153 off += self.s
151 off += self.s
154 cur += blocksize
152 cur += blocksize
155
153
156 def loadblock(self, blockstart, blocksize, data=None):
154 def loadblock(self, blockstart, blocksize, data=None):
157 if self.all:
155 if self.all:
158 return
156 return
159 if data is None:
157 if data is None:
160 self.dataf.seek(blockstart)
158 self.dataf.seek(blockstart)
161 if blockstart + blocksize > self.datasize:
159 if blockstart + blocksize > self.datasize:
162 # the revlog may have grown since we've started running,
160 # the revlog may have grown since we've started running,
163 # but we don't have space in self.index for more entries.
161 # but we don't have space in self.index for more entries.
164 # limit blocksize so that we don't get too much data.
162 # limit blocksize so that we don't get too much data.
165 blocksize = max(self.datasize - blockstart, 0)
163 blocksize = max(self.datasize - blockstart, 0)
166 data = self.dataf.read(blocksize)
164 data = self.dataf.read(blocksize)
167 lend = len(data) / self.s
165 lend = len(data) / self.s
168 i = blockstart / self.s
166 i = blockstart / self.s
169 off = 0
167 off = 0
170 # lazyindex supports __delitem__
168 # lazyindex supports __delitem__
171 if lend > len(self.index) - i:
169 if lend > len(self.index) - i:
172 lend = len(self.index) - i
170 lend = len(self.index) - i
173 for x in xrange(lend):
171 for x in xrange(lend):
174 if self.index[i + x] == None:
172 if self.index[i + x] == None:
175 b = data[off : off + self.s]
173 b = data[off : off + self.s]
176 self.index[i + x] = b
174 self.index[i + x] = b
177 n = b[ngshaoffset:ngshaoffset + 20]
175 n = b[ngshaoffset:ngshaoffset + 20]
178 self.map[n] = i + x
176 self.map[n] = i + x
179 off += self.s
177 off += self.s
180
178
181 def findnode(self, node):
179 def findnode(self, node):
182 """search backwards through the index file for a specific node"""
180 """search backwards through the index file for a specific node"""
183 if self.allmap:
181 if self.allmap:
184 return None
182 return None
185
183
186 # hg log will cause many many searches for the manifest
184 # hg log will cause many many searches for the manifest
187 # nodes. After we get called a few times, just load the whole
185 # nodes. After we get called a few times, just load the whole
188 # thing.
186 # thing.
189 if self.mapfind_count > 8:
187 if self.mapfind_count > 8:
190 self.loadmap()
188 self.loadmap()
191 if node in self.map:
189 if node in self.map:
192 return node
190 return node
193 return None
191 return None
194 self.mapfind_count += 1
192 self.mapfind_count += 1
195 last = self.l - 1
193 last = self.l - 1
196 while self.index[last] != None:
194 while self.index[last] != None:
197 if last == 0:
195 if last == 0:
198 self.all = 1
196 self.all = 1
199 self.allmap = 1
197 self.allmap = 1
200 return None
198 return None
201 last -= 1
199 last -= 1
202 end = (last + 1) * self.s
200 end = (last + 1) * self.s
203 blocksize = self.s * 256
201 blocksize = self.s * 256
204 while end >= 0:
202 while end >= 0:
205 start = max(end - blocksize, 0)
203 start = max(end - blocksize, 0)
206 self.dataf.seek(start)
204 self.dataf.seek(start)
207 data = self.dataf.read(end - start)
205 data = self.dataf.read(end - start)
208 findend = end - start
206 findend = end - start
209 while True:
207 while True:
210 # we're searching backwards, so we have to make sure
208 # we're searching backwards, so we have to make sure
211 # we don't find a changeset where this node is a parent
209 # we don't find a changeset where this node is a parent
212 off = data.find(node, 0, findend)
210 off = data.find(node, 0, findend)
213 findend = off
211 findend = off
214 if off >= 0:
212 if off >= 0:
215 i = off / self.s
213 i = off / self.s
216 off = i * self.s
214 off = i * self.s
217 n = data[off + ngshaoffset:off + ngshaoffset + 20]
215 n = data[off + ngshaoffset:off + ngshaoffset + 20]
218 if n == node:
216 if n == node:
219 self.map[n] = i + start / self.s
217 self.map[n] = i + start / self.s
220 return node
218 return node
221 else:
219 else:
222 break
220 break
223 end -= blocksize
221 end -= blocksize
224 return None
222 return None
225
223
226 def loadindex(self, i=None, end=None):
224 def loadindex(self, i=None, end=None):
227 if self.all:
225 if self.all:
228 return
226 return
229 all = False
227 all = False
230 if i == None:
228 if i == None:
231 blockstart = 0
229 blockstart = 0
232 blocksize = (65536 / self.s) * self.s
230 blocksize = (65536 / self.s) * self.s
233 end = self.datasize
231 end = self.datasize
234 all = True
232 all = True
235 else:
233 else:
236 if end:
234 if end:
237 blockstart = i * self.s
235 blockstart = i * self.s
238 end = end * self.s
236 end = end * self.s
239 blocksize = end - blockstart
237 blocksize = end - blockstart
240 else:
238 else:
241 blockstart = (i & ~1023) * self.s
239 blockstart = (i & ~1023) * self.s
242 blocksize = self.s * 1024
240 blocksize = self.s * 1024
243 end = blockstart + blocksize
241 end = blockstart + blocksize
244 while blockstart < end:
242 while blockstart < end:
245 self.loadblock(blockstart, blocksize)
243 self.loadblock(blockstart, blocksize)
246 blockstart += blocksize
244 blockstart += blocksize
247 if all:
245 if all:
248 self.all = True
246 self.all = True
249
247
250 class lazyindex(object):
248 class lazyindex(object):
251 """a lazy version of the index array"""
249 """a lazy version of the index array"""
252 def __init__(self, parser):
250 def __init__(self, parser):
253 self.p = parser
251 self.p = parser
254 def __len__(self):
252 def __len__(self):
255 return len(self.p.index)
253 return len(self.p.index)
256 def load(self, pos):
254 def load(self, pos):
257 if pos < 0:
255 if pos < 0:
258 pos += len(self.p.index)
256 pos += len(self.p.index)
259 self.p.loadindex(pos)
257 self.p.loadindex(pos)
260 return self.p.index[pos]
258 return self.p.index[pos]
261 def __getitem__(self, pos):
259 def __getitem__(self, pos):
262 return _unpack(indexformatng, self.p.index[pos] or self.load(pos))
260 return _unpack(indexformatng, self.p.index[pos] or self.load(pos))
263 def __setitem__(self, pos, item):
261 def __setitem__(self, pos, item):
264 self.p.index[pos] = _pack(indexformatng, *item)
262 self.p.index[pos] = _pack(indexformatng, *item)
265 def __delitem__(self, pos):
263 def __delitem__(self, pos):
266 del self.p.index[pos]
264 del self.p.index[pos]
267 def insert(self, pos, e):
265 def insert(self, pos, e):
268 self.p.index.insert(pos, _pack(indexformatng, *e))
266 self.p.index.insert(pos, _pack(indexformatng, *e))
269 def append(self, e):
267 def append(self, e):
270 self.p.index.append(_pack(indexformatng, *e))
268 self.p.index.append(_pack(indexformatng, *e))
271
269
272 class lazymap(object):
270 class lazymap(object):
273 """a lazy version of the node map"""
271 """a lazy version of the node map"""
274 def __init__(self, parser):
272 def __init__(self, parser):
275 self.p = parser
273 self.p = parser
276 def load(self, key):
274 def load(self, key):
277 n = self.p.findnode(key)
275 n = self.p.findnode(key)
278 if n == None:
276 if n == None:
279 raise KeyError(key)
277 raise KeyError(key)
280 def __contains__(self, key):
278 def __contains__(self, key):
281 if key in self.p.map:
279 if key in self.p.map:
282 return True
280 return True
283 self.p.loadmap()
281 self.p.loadmap()
284 return key in self.p.map
282 return key in self.p.map
285 def __iter__(self):
283 def __iter__(self):
286 yield nullid
284 yield nullid
287 for i in xrange(self.p.l):
285 for i in xrange(self.p.l):
288 ret = self.p.index[i]
286 ret = self.p.index[i]
289 if not ret:
287 if not ret:
290 self.p.loadindex(i)
288 self.p.loadindex(i)
291 ret = self.p.index[i]
289 ret = self.p.index[i]
292 if isinstance(ret, str):
290 if isinstance(ret, str):
293 ret = _unpack(indexformatng, ret)
291 ret = _unpack(indexformatng, ret)
294 yield ret[7]
292 yield ret[7]
295 def __getitem__(self, key):
293 def __getitem__(self, key):
296 try:
294 try:
297 return self.p.map[key]
295 return self.p.map[key]
298 except KeyError:
296 except KeyError:
299 try:
297 try:
300 self.load(key)
298 self.load(key)
301 return self.p.map[key]
299 return self.p.map[key]
302 except KeyError:
300 except KeyError:
303 raise KeyError("node " + hex(key))
301 raise KeyError("node " + hex(key))
304 def __setitem__(self, key, val):
302 def __setitem__(self, key, val):
305 self.p.map[key] = val
303 self.p.map[key] = val
306 def __delitem__(self, key):
304 def __delitem__(self, key):
307 del self.p.map[key]
305 del self.p.map[key]
308
306
309 indexformatv0 = ">4l20s20s20s"
307 indexformatv0 = ">4l20s20s20s"
310 v0shaoffset = 56
308 v0shaoffset = 56
311
309
312 class revlogoldio(object):
310 class revlogoldio(object):
313 def __init__(self):
311 def __init__(self):
314 self.size = struct.calcsize(indexformatv0)
312 self.size = struct.calcsize(indexformatv0)
315
313
316 def parseindex(self, fp, inline):
314 def parseindex(self, fp, inline):
317 s = self.size
315 s = self.size
318 index = []
316 index = []
319 nodemap = {nullid: nullrev}
317 nodemap = {nullid: nullrev}
320 n = off = 0
318 n = off = 0
321 data = fp.read()
319 data = fp.read()
322 l = len(data)
320 l = len(data)
323 while off + s <= l:
321 while off + s <= l:
324 cur = data[off:off + s]
322 cur = data[off:off + s]
325 off += s
323 off += s
326 e = _unpack(indexformatv0, cur)
324 e = _unpack(indexformatv0, cur)
327 # transform to revlogv1 format
325 # transform to revlogv1 format
328 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
326 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
329 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
327 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
330 index.append(e2)
328 index.append(e2)
331 nodemap[e[6]] = n
329 nodemap[e[6]] = n
332 n += 1
330 n += 1
333
331
334 return index, nodemap, None
332 return index, nodemap, None
335
333
336 def packentry(self, entry, node, version, rev):
334 def packentry(self, entry, node, version, rev):
337 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
335 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
338 node(entry[5]), node(entry[6]), entry[7])
336 node(entry[5]), node(entry[6]), entry[7])
339 return _pack(indexformatv0, *e2)
337 return _pack(indexformatv0, *e2)
340
338
341 # index ng:
339 # index ng:
342 # 6 bytes offset
340 # 6 bytes offset
343 # 2 bytes flags
341 # 2 bytes flags
344 # 4 bytes compressed length
342 # 4 bytes compressed length
345 # 4 bytes uncompressed length
343 # 4 bytes uncompressed length
346 # 4 bytes: base rev
344 # 4 bytes: base rev
347 # 4 bytes link rev
345 # 4 bytes link rev
348 # 4 bytes parent 1 rev
346 # 4 bytes parent 1 rev
349 # 4 bytes parent 2 rev
347 # 4 bytes parent 2 rev
350 # 32 bytes: nodeid
348 # 32 bytes: nodeid
351 indexformatng = ">Qiiiiii20s12x"
349 indexformatng = ">Qiiiiii20s12x"
352 ngshaoffset = 32
350 ngshaoffset = 32
353 versionformat = ">I"
351 versionformat = ">I"
354
352
355 class revlogio(object):
353 class revlogio(object):
356 def __init__(self):
354 def __init__(self):
357 self.size = struct.calcsize(indexformatng)
355 self.size = struct.calcsize(indexformatng)
358
356
359 def parseindex(self, fp, inline):
357 def parseindex(self, fp, inline):
360 try:
358 try:
361 size = util.fstat(fp).st_size
359 size = util.fstat(fp).st_size
362 except AttributeError:
360 except AttributeError:
363 size = 0
361 size = 0
364
362
365 if lazyparser.safe_to_use and not inline and size > 1000000:
363 if util.openhardlinks() and not inline and size > 1000000:
366 # big index, let's parse it on demand
364 # big index, let's parse it on demand
367 parser = lazyparser(fp, size)
365 parser = lazyparser(fp, size)
368 index = lazyindex(parser)
366 index = lazyindex(parser)
369 nodemap = lazymap(parser)
367 nodemap = lazymap(parser)
370 e = list(index[0])
368 e = list(index[0])
371 type = gettype(e[0])
369 type = gettype(e[0])
372 e[0] = offset_type(0, type)
370 e[0] = offset_type(0, type)
373 index[0] = e
371 index[0] = e
374 return index, nodemap, None
372 return index, nodemap, None
375
373
376 s = self.size
374 s = self.size
377 cache = None
375 cache = None
378 index = []
376 index = []
379 nodemap = {nullid: nullrev}
377 nodemap = {nullid: nullrev}
380 n = off = 0
378 n = off = 0
381 # if we're not using lazymap, always read the whole index
379 # if we're not using lazymap, always read the whole index
382 data = fp.read()
380 data = fp.read()
383 l = len(data) - s
381 l = len(data) - s
384 append = index.append
382 append = index.append
385 if inline:
383 if inline:
386 cache = (0, data)
384 cache = (0, data)
387 while off <= l:
385 while off <= l:
388 e = _unpack(indexformatng, data[off:off + s])
386 e = _unpack(indexformatng, data[off:off + s])
389 nodemap[e[7]] = n
387 nodemap[e[7]] = n
390 append(e)
388 append(e)
391 n += 1
389 n += 1
392 if e[1] < 0:
390 if e[1] < 0:
393 break
391 break
394 off += e[1] + s
392 off += e[1] + s
395 else:
393 else:
396 while off <= l:
394 while off <= l:
397 e = _unpack(indexformatng, data[off:off + s])
395 e = _unpack(indexformatng, data[off:off + s])
398 nodemap[e[7]] = n
396 nodemap[e[7]] = n
399 append(e)
397 append(e)
400 n += 1
398 n += 1
401 off += s
399 off += s
402
400
403 e = list(index[0])
401 e = list(index[0])
404 type = gettype(e[0])
402 type = gettype(e[0])
405 e[0] = offset_type(0, type)
403 e[0] = offset_type(0, type)
406 index[0] = e
404 index[0] = e
407
405
408 return index, nodemap, cache
406 return index, nodemap, cache
409
407
410 def packentry(self, entry, node, version, rev):
408 def packentry(self, entry, node, version, rev):
411 p = _pack(indexformatng, *entry)
409 p = _pack(indexformatng, *entry)
412 if rev == 0:
410 if rev == 0:
413 p = _pack(versionformat, version) + p[4:]
411 p = _pack(versionformat, version) + p[4:]
414 return p
412 return p
415
413
416 class revlog(object):
414 class revlog(object):
417 """
415 """
418 the underlying revision storage object
416 the underlying revision storage object
419
417
420 A revlog consists of two parts, an index and the revision data.
418 A revlog consists of two parts, an index and the revision data.
421
419
422 The index is a file with a fixed record size containing
420 The index is a file with a fixed record size containing
423 information on each revision, includings its nodeid (hash), the
421 information on each revision, includings its nodeid (hash), the
424 nodeids of its parents, the position and offset of its data within
422 nodeids of its parents, the position and offset of its data within
425 the data file, and the revision it's based on. Finally, each entry
423 the data file, and the revision it's based on. Finally, each entry
426 contains a linkrev entry that can serve as a pointer to external
424 contains a linkrev entry that can serve as a pointer to external
427 data.
425 data.
428
426
429 The revision data itself is a linear collection of data chunks.
427 The revision data itself is a linear collection of data chunks.
430 Each chunk represents a revision and is usually represented as a
428 Each chunk represents a revision and is usually represented as a
431 delta against the previous chunk. To bound lookup time, runs of
429 delta against the previous chunk. To bound lookup time, runs of
432 deltas are limited to about 2 times the length of the original
430 deltas are limited to about 2 times the length of the original
433 version data. This makes retrieval of a version proportional to
431 version data. This makes retrieval of a version proportional to
434 its size, or O(1) relative to the number of revisions.
432 its size, or O(1) relative to the number of revisions.
435
433
436 Both pieces of the revlog are written to in an append-only
434 Both pieces of the revlog are written to in an append-only
437 fashion, which means we never need to rewrite a file to insert or
435 fashion, which means we never need to rewrite a file to insert or
438 remove data, and can use some simple techniques to avoid the need
436 remove data, and can use some simple techniques to avoid the need
439 for locking while reading.
437 for locking while reading.
440 """
438 """
441 def __init__(self, opener, indexfile):
439 def __init__(self, opener, indexfile):
442 """
440 """
443 create a revlog object
441 create a revlog object
444
442
445 opener is a function that abstracts the file opening operation
443 opener is a function that abstracts the file opening operation
446 and can be used to implement COW semantics or the like.
444 and can be used to implement COW semantics or the like.
447 """
445 """
448 self.indexfile = indexfile
446 self.indexfile = indexfile
449 self.datafile = indexfile[:-2] + ".d"
447 self.datafile = indexfile[:-2] + ".d"
450 self.opener = opener
448 self.opener = opener
451 self._cache = None
449 self._cache = None
452 self._chunkcache = None
450 self._chunkcache = None
453 self.nodemap = {nullid: nullrev}
451 self.nodemap = {nullid: nullrev}
454 self.index = []
452 self.index = []
455
453
456 v = REVLOG_DEFAULT_VERSION
454 v = REVLOG_DEFAULT_VERSION
457 if hasattr(opener, "defversion"):
455 if hasattr(opener, "defversion"):
458 v = opener.defversion
456 v = opener.defversion
459 if v & REVLOGNG:
457 if v & REVLOGNG:
460 v |= REVLOGNGINLINEDATA
458 v |= REVLOGNGINLINEDATA
461
459
462 i = ""
460 i = ""
463 try:
461 try:
464 f = self.opener(self.indexfile)
462 f = self.opener(self.indexfile)
465 i = f.read(4)
463 i = f.read(4)
466 f.seek(0)
464 f.seek(0)
467 if len(i) > 0:
465 if len(i) > 0:
468 v = struct.unpack(versionformat, i)[0]
466 v = struct.unpack(versionformat, i)[0]
469 except IOError, inst:
467 except IOError, inst:
470 if inst.errno != errno.ENOENT:
468 if inst.errno != errno.ENOENT:
471 raise
469 raise
472
470
473 self.version = v
471 self.version = v
474 self._inline = v & REVLOGNGINLINEDATA
472 self._inline = v & REVLOGNGINLINEDATA
475 flags = v & ~0xFFFF
473 flags = v & ~0xFFFF
476 fmt = v & 0xFFFF
474 fmt = v & 0xFFFF
477 if fmt == REVLOGV0 and flags:
475 if fmt == REVLOGV0 and flags:
478 raise RevlogError(_("index %s unknown flags %#04x for format v0")
476 raise RevlogError(_("index %s unknown flags %#04x for format v0")
479 % (self.indexfile, flags >> 16))
477 % (self.indexfile, flags >> 16))
480 elif fmt == REVLOGNG and flags & ~REVLOGNGINLINEDATA:
478 elif fmt == REVLOGNG and flags & ~REVLOGNGINLINEDATA:
481 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
479 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
482 % (self.indexfile, flags >> 16))
480 % (self.indexfile, flags >> 16))
483 elif fmt > REVLOGNG:
481 elif fmt > REVLOGNG:
484 raise RevlogError(_("index %s unknown format %d")
482 raise RevlogError(_("index %s unknown format %d")
485 % (self.indexfile, fmt))
483 % (self.indexfile, fmt))
486
484
487 self._io = revlogio()
485 self._io = revlogio()
488 if self.version == REVLOGV0:
486 if self.version == REVLOGV0:
489 self._io = revlogoldio()
487 self._io = revlogoldio()
490 if i:
488 if i:
491 d = self._io.parseindex(f, self._inline)
489 d = self._io.parseindex(f, self._inline)
492 self.index, self.nodemap, self._chunkcache = d
490 self.index, self.nodemap, self._chunkcache = d
493
491
494 # add the magic null revision at -1
492 # add the magic null revision at -1
495 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
493 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
496
494
497 def _loadindex(self, start, end):
495 def _loadindex(self, start, end):
498 """load a block of indexes all at once from the lazy parser"""
496 """load a block of indexes all at once from the lazy parser"""
499 if isinstance(self.index, lazyindex):
497 if isinstance(self.index, lazyindex):
500 self.index.p.loadindex(start, end)
498 self.index.p.loadindex(start, end)
501
499
502 def _loadindexmap(self):
500 def _loadindexmap(self):
503 """loads both the map and the index from the lazy parser"""
501 """loads both the map and the index from the lazy parser"""
504 if isinstance(self.index, lazyindex):
502 if isinstance(self.index, lazyindex):
505 p = self.index.p
503 p = self.index.p
506 p.loadindex()
504 p.loadindex()
507 self.nodemap = p.map
505 self.nodemap = p.map
508
506
509 def _loadmap(self):
507 def _loadmap(self):
510 """loads the map from the lazy parser"""
508 """loads the map from the lazy parser"""
511 if isinstance(self.nodemap, lazymap):
509 if isinstance(self.nodemap, lazymap):
512 self.nodemap.p.loadmap()
510 self.nodemap.p.loadmap()
513 self.nodemap = self.nodemap.p.map
511 self.nodemap = self.nodemap.p.map
514
512
515 def tip(self):
513 def tip(self):
516 return self.node(len(self.index) - 2)
514 return self.node(len(self.index) - 2)
517 def count(self):
515 def count(self):
518 return len(self.index) - 1
516 return len(self.index) - 1
519
517
520 def rev(self, node):
518 def rev(self, node):
521 try:
519 try:
522 return self.nodemap[node]
520 return self.nodemap[node]
523 except KeyError:
521 except KeyError:
524 raise LookupError(hex(node), _('%s: no node %s') % (self.indexfile, hex(node)))
522 raise LookupError(hex(node), _('%s: no node %s') % (self.indexfile, hex(node)))
525 def node(self, rev):
523 def node(self, rev):
526 return self.index[rev][7]
524 return self.index[rev][7]
527 def linkrev(self, node):
525 def linkrev(self, node):
528 return self.index[self.rev(node)][4]
526 return self.index[self.rev(node)][4]
529 def parents(self, node):
527 def parents(self, node):
530 d = self.index[self.rev(node)][5:7]
528 d = self.index[self.rev(node)][5:7]
531 return (self.node(d[0]), self.node(d[1]))
529 return (self.node(d[0]), self.node(d[1]))
532 def parentrevs(self, rev):
530 def parentrevs(self, rev):
533 return self.index[rev][5:7]
531 return self.index[rev][5:7]
534 def start(self, rev):
532 def start(self, rev):
535 return int(self.index[rev][0] >> 16)
533 return int(self.index[rev][0] >> 16)
536 def end(self, rev):
534 def end(self, rev):
537 return self.start(rev) + self.length(rev)
535 return self.start(rev) + self.length(rev)
538 def length(self, rev):
536 def length(self, rev):
539 return self.index[rev][1]
537 return self.index[rev][1]
540 def base(self, rev):
538 def base(self, rev):
541 return self.index[rev][3]
539 return self.index[rev][3]
542
540
543 def size(self, rev):
541 def size(self, rev):
544 """return the length of the uncompressed text for a given revision"""
542 """return the length of the uncompressed text for a given revision"""
545 l = self.index[rev][2]
543 l = self.index[rev][2]
546 if l >= 0:
544 if l >= 0:
547 return l
545 return l
548
546
549 t = self.revision(self.node(rev))
547 t = self.revision(self.node(rev))
550 return len(t)
548 return len(t)
551
549
552 # alternate implementation, The advantage to this code is it
550 # alternate implementation, The advantage to this code is it
553 # will be faster for a single revision. But, the results are not
551 # will be faster for a single revision. But, the results are not
554 # cached, so finding the size of every revision will be slower.
552 # cached, so finding the size of every revision will be slower.
555 """
553 """
556 if self.cache and self.cache[1] == rev:
554 if self.cache and self.cache[1] == rev:
557 return len(self.cache[2])
555 return len(self.cache[2])
558
556
559 base = self.base(rev)
557 base = self.base(rev)
560 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
558 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
561 base = self.cache[1]
559 base = self.cache[1]
562 text = self.cache[2]
560 text = self.cache[2]
563 else:
561 else:
564 text = self.revision(self.node(base))
562 text = self.revision(self.node(base))
565
563
566 l = len(text)
564 l = len(text)
567 for x in xrange(base + 1, rev + 1):
565 for x in xrange(base + 1, rev + 1):
568 l = mdiff.patchedsize(l, self.chunk(x))
566 l = mdiff.patchedsize(l, self.chunk(x))
569 return l
567 return l
570 """
568 """
571
569
572 def reachable(self, node, stop=None):
570 def reachable(self, node, stop=None):
573 """return a hash of all nodes ancestral to a given node, including
571 """return a hash of all nodes ancestral to a given node, including
574 the node itself, stopping when stop is matched"""
572 the node itself, stopping when stop is matched"""
575 reachable = {}
573 reachable = {}
576 visit = [node]
574 visit = [node]
577 reachable[node] = 1
575 reachable[node] = 1
578 if stop:
576 if stop:
579 stopn = self.rev(stop)
577 stopn = self.rev(stop)
580 else:
578 else:
581 stopn = 0
579 stopn = 0
582 while visit:
580 while visit:
583 n = visit.pop(0)
581 n = visit.pop(0)
584 if n == stop:
582 if n == stop:
585 continue
583 continue
586 if n == nullid:
584 if n == nullid:
587 continue
585 continue
588 for p in self.parents(n):
586 for p in self.parents(n):
589 if self.rev(p) < stopn:
587 if self.rev(p) < stopn:
590 continue
588 continue
591 if p not in reachable:
589 if p not in reachable:
592 reachable[p] = 1
590 reachable[p] = 1
593 visit.append(p)
591 visit.append(p)
594 return reachable
592 return reachable
595
593
596 def nodesbetween(self, roots=None, heads=None):
594 def nodesbetween(self, roots=None, heads=None):
597 """Return a tuple containing three elements. Elements 1 and 2 contain
595 """Return a tuple containing three elements. Elements 1 and 2 contain
598 a final list bases and heads after all the unreachable ones have been
596 a final list bases and heads after all the unreachable ones have been
599 pruned. Element 0 contains a topologically sorted list of all
597 pruned. Element 0 contains a topologically sorted list of all
600
598
601 nodes that satisfy these constraints:
599 nodes that satisfy these constraints:
602 1. All nodes must be descended from a node in roots (the nodes on
600 1. All nodes must be descended from a node in roots (the nodes on
603 roots are considered descended from themselves).
601 roots are considered descended from themselves).
604 2. All nodes must also be ancestors of a node in heads (the nodes in
602 2. All nodes must also be ancestors of a node in heads (the nodes in
605 heads are considered to be their own ancestors).
603 heads are considered to be their own ancestors).
606
604
607 If roots is unspecified, nullid is assumed as the only root.
605 If roots is unspecified, nullid is assumed as the only root.
608 If heads is unspecified, it is taken to be the output of the
606 If heads is unspecified, it is taken to be the output of the
609 heads method (i.e. a list of all nodes in the repository that
607 heads method (i.e. a list of all nodes in the repository that
610 have no children)."""
608 have no children)."""
611 nonodes = ([], [], [])
609 nonodes = ([], [], [])
612 if roots is not None:
610 if roots is not None:
613 roots = list(roots)
611 roots = list(roots)
614 if not roots:
612 if not roots:
615 return nonodes
613 return nonodes
616 lowestrev = min([self.rev(n) for n in roots])
614 lowestrev = min([self.rev(n) for n in roots])
617 else:
615 else:
618 roots = [nullid] # Everybody's a descendent of nullid
616 roots = [nullid] # Everybody's a descendent of nullid
619 lowestrev = nullrev
617 lowestrev = nullrev
620 if (lowestrev == nullrev) and (heads is None):
618 if (lowestrev == nullrev) and (heads is None):
621 # We want _all_ the nodes!
619 # We want _all_ the nodes!
622 return ([self.node(r) for r in xrange(0, self.count())],
620 return ([self.node(r) for r in xrange(0, self.count())],
623 [nullid], list(self.heads()))
621 [nullid], list(self.heads()))
624 if heads is None:
622 if heads is None:
625 # All nodes are ancestors, so the latest ancestor is the last
623 # All nodes are ancestors, so the latest ancestor is the last
626 # node.
624 # node.
627 highestrev = self.count() - 1
625 highestrev = self.count() - 1
628 # Set ancestors to None to signal that every node is an ancestor.
626 # Set ancestors to None to signal that every node is an ancestor.
629 ancestors = None
627 ancestors = None
630 # Set heads to an empty dictionary for later discovery of heads
628 # Set heads to an empty dictionary for later discovery of heads
631 heads = {}
629 heads = {}
632 else:
630 else:
633 heads = list(heads)
631 heads = list(heads)
634 if not heads:
632 if not heads:
635 return nonodes
633 return nonodes
636 ancestors = {}
634 ancestors = {}
637 # Turn heads into a dictionary so we can remove 'fake' heads.
635 # Turn heads into a dictionary so we can remove 'fake' heads.
638 # Also, later we will be using it to filter out the heads we can't
636 # Also, later we will be using it to filter out the heads we can't
639 # find from roots.
637 # find from roots.
640 heads = dict.fromkeys(heads, 0)
638 heads = dict.fromkeys(heads, 0)
641 # Start at the top and keep marking parents until we're done.
639 # Start at the top and keep marking parents until we're done.
642 nodestotag = heads.keys()
640 nodestotag = heads.keys()
643 # Remember where the top was so we can use it as a limit later.
641 # Remember where the top was so we can use it as a limit later.
644 highestrev = max([self.rev(n) for n in nodestotag])
642 highestrev = max([self.rev(n) for n in nodestotag])
645 while nodestotag:
643 while nodestotag:
646 # grab a node to tag
644 # grab a node to tag
647 n = nodestotag.pop()
645 n = nodestotag.pop()
648 # Never tag nullid
646 # Never tag nullid
649 if n == nullid:
647 if n == nullid:
650 continue
648 continue
651 # A node's revision number represents its place in a
649 # A node's revision number represents its place in a
652 # topologically sorted list of nodes.
650 # topologically sorted list of nodes.
653 r = self.rev(n)
651 r = self.rev(n)
654 if r >= lowestrev:
652 if r >= lowestrev:
655 if n not in ancestors:
653 if n not in ancestors:
656 # If we are possibly a descendent of one of the roots
654 # If we are possibly a descendent of one of the roots
657 # and we haven't already been marked as an ancestor
655 # and we haven't already been marked as an ancestor
658 ancestors[n] = 1 # Mark as ancestor
656 ancestors[n] = 1 # Mark as ancestor
659 # Add non-nullid parents to list of nodes to tag.
657 # Add non-nullid parents to list of nodes to tag.
660 nodestotag.extend([p for p in self.parents(n) if
658 nodestotag.extend([p for p in self.parents(n) if
661 p != nullid])
659 p != nullid])
662 elif n in heads: # We've seen it before, is it a fake head?
660 elif n in heads: # We've seen it before, is it a fake head?
663 # So it is, real heads should not be the ancestors of
661 # So it is, real heads should not be the ancestors of
664 # any other heads.
662 # any other heads.
665 heads.pop(n)
663 heads.pop(n)
666 if not ancestors:
664 if not ancestors:
667 return nonodes
665 return nonodes
668 # Now that we have our set of ancestors, we want to remove any
666 # Now that we have our set of ancestors, we want to remove any
669 # roots that are not ancestors.
667 # roots that are not ancestors.
670
668
671 # If one of the roots was nullid, everything is included anyway.
669 # If one of the roots was nullid, everything is included anyway.
672 if lowestrev > nullrev:
670 if lowestrev > nullrev:
673 # But, since we weren't, let's recompute the lowest rev to not
671 # But, since we weren't, let's recompute the lowest rev to not
674 # include roots that aren't ancestors.
672 # include roots that aren't ancestors.
675
673
676 # Filter out roots that aren't ancestors of heads
674 # Filter out roots that aren't ancestors of heads
677 roots = [n for n in roots if n in ancestors]
675 roots = [n for n in roots if n in ancestors]
678 # Recompute the lowest revision
676 # Recompute the lowest revision
679 if roots:
677 if roots:
680 lowestrev = min([self.rev(n) for n in roots])
678 lowestrev = min([self.rev(n) for n in roots])
681 else:
679 else:
682 # No more roots? Return empty list
680 # No more roots? Return empty list
683 return nonodes
681 return nonodes
684 else:
682 else:
685 # We are descending from nullid, and don't need to care about
683 # We are descending from nullid, and don't need to care about
686 # any other roots.
684 # any other roots.
687 lowestrev = nullrev
685 lowestrev = nullrev
688 roots = [nullid]
686 roots = [nullid]
689 # Transform our roots list into a 'set' (i.e. a dictionary where the
687 # Transform our roots list into a 'set' (i.e. a dictionary where the
690 # values don't matter.
688 # values don't matter.
691 descendents = dict.fromkeys(roots, 1)
689 descendents = dict.fromkeys(roots, 1)
692 # Also, keep the original roots so we can filter out roots that aren't
690 # Also, keep the original roots so we can filter out roots that aren't
693 # 'real' roots (i.e. are descended from other roots).
691 # 'real' roots (i.e. are descended from other roots).
694 roots = descendents.copy()
692 roots = descendents.copy()
695 # Our topologically sorted list of output nodes.
693 # Our topologically sorted list of output nodes.
696 orderedout = []
694 orderedout = []
697 # Don't start at nullid since we don't want nullid in our output list,
695 # Don't start at nullid since we don't want nullid in our output list,
698 # and if nullid shows up in descedents, empty parents will look like
696 # and if nullid shows up in descedents, empty parents will look like
699 # they're descendents.
697 # they're descendents.
700 for r in xrange(max(lowestrev, 0), highestrev + 1):
698 for r in xrange(max(lowestrev, 0), highestrev + 1):
701 n = self.node(r)
699 n = self.node(r)
702 isdescendent = False
700 isdescendent = False
703 if lowestrev == nullrev: # Everybody is a descendent of nullid
701 if lowestrev == nullrev: # Everybody is a descendent of nullid
704 isdescendent = True
702 isdescendent = True
705 elif n in descendents:
703 elif n in descendents:
706 # n is already a descendent
704 # n is already a descendent
707 isdescendent = True
705 isdescendent = True
708 # This check only needs to be done here because all the roots
706 # This check only needs to be done here because all the roots
709 # will start being marked is descendents before the loop.
707 # will start being marked is descendents before the loop.
710 if n in roots:
708 if n in roots:
711 # If n was a root, check if it's a 'real' root.
709 # If n was a root, check if it's a 'real' root.
712 p = tuple(self.parents(n))
710 p = tuple(self.parents(n))
713 # If any of its parents are descendents, it's not a root.
711 # If any of its parents are descendents, it's not a root.
714 if (p[0] in descendents) or (p[1] in descendents):
712 if (p[0] in descendents) or (p[1] in descendents):
715 roots.pop(n)
713 roots.pop(n)
716 else:
714 else:
717 p = tuple(self.parents(n))
715 p = tuple(self.parents(n))
718 # A node is a descendent if either of its parents are
716 # A node is a descendent if either of its parents are
719 # descendents. (We seeded the dependents list with the roots
717 # descendents. (We seeded the dependents list with the roots
720 # up there, remember?)
718 # up there, remember?)
721 if (p[0] in descendents) or (p[1] in descendents):
719 if (p[0] in descendents) or (p[1] in descendents):
722 descendents[n] = 1
720 descendents[n] = 1
723 isdescendent = True
721 isdescendent = True
724 if isdescendent and ((ancestors is None) or (n in ancestors)):
722 if isdescendent and ((ancestors is None) or (n in ancestors)):
725 # Only include nodes that are both descendents and ancestors.
723 # Only include nodes that are both descendents and ancestors.
726 orderedout.append(n)
724 orderedout.append(n)
727 if (ancestors is not None) and (n in heads):
725 if (ancestors is not None) and (n in heads):
728 # We're trying to figure out which heads are reachable
726 # We're trying to figure out which heads are reachable
729 # from roots.
727 # from roots.
730 # Mark this head as having been reached
728 # Mark this head as having been reached
731 heads[n] = 1
729 heads[n] = 1
732 elif ancestors is None:
730 elif ancestors is None:
733 # Otherwise, we're trying to discover the heads.
731 # Otherwise, we're trying to discover the heads.
734 # Assume this is a head because if it isn't, the next step
732 # Assume this is a head because if it isn't, the next step
735 # will eventually remove it.
733 # will eventually remove it.
736 heads[n] = 1
734 heads[n] = 1
737 # But, obviously its parents aren't.
735 # But, obviously its parents aren't.
738 for p in self.parents(n):
736 for p in self.parents(n):
739 heads.pop(p, None)
737 heads.pop(p, None)
740 heads = [n for n in heads.iterkeys() if heads[n] != 0]
738 heads = [n for n in heads.iterkeys() if heads[n] != 0]
741 roots = roots.keys()
739 roots = roots.keys()
742 assert orderedout
740 assert orderedout
743 assert roots
741 assert roots
744 assert heads
742 assert heads
745 return (orderedout, roots, heads)
743 return (orderedout, roots, heads)
746
744
747 def heads(self, start=None, stop=None):
745 def heads(self, start=None, stop=None):
748 """return the list of all nodes that have no children
746 """return the list of all nodes that have no children
749
747
750 if start is specified, only heads that are descendants of
748 if start is specified, only heads that are descendants of
751 start will be returned
749 start will be returned
752 if stop is specified, it will consider all the revs from stop
750 if stop is specified, it will consider all the revs from stop
753 as if they had no children
751 as if they had no children
754 """
752 """
755 if start is None and stop is None:
753 if start is None and stop is None:
756 count = self.count()
754 count = self.count()
757 if not count:
755 if not count:
758 return [nullid]
756 return [nullid]
759 ishead = [1] * (count + 1)
757 ishead = [1] * (count + 1)
760 index = self.index
758 index = self.index
761 for r in xrange(count):
759 for r in xrange(count):
762 e = index[r]
760 e = index[r]
763 ishead[e[5]] = ishead[e[6]] = 0
761 ishead[e[5]] = ishead[e[6]] = 0
764 return [self.node(r) for r in xrange(count) if ishead[r]]
762 return [self.node(r) for r in xrange(count) if ishead[r]]
765
763
766 if start is None:
764 if start is None:
767 start = nullid
765 start = nullid
768 if stop is None:
766 if stop is None:
769 stop = []
767 stop = []
770 stoprevs = dict.fromkeys([self.rev(n) for n in stop])
768 stoprevs = dict.fromkeys([self.rev(n) for n in stop])
771 startrev = self.rev(start)
769 startrev = self.rev(start)
772 reachable = {startrev: 1}
770 reachable = {startrev: 1}
773 heads = {startrev: 1}
771 heads = {startrev: 1}
774
772
775 parentrevs = self.parentrevs
773 parentrevs = self.parentrevs
776 for r in xrange(startrev + 1, self.count()):
774 for r in xrange(startrev + 1, self.count()):
777 for p in parentrevs(r):
775 for p in parentrevs(r):
778 if p in reachable:
776 if p in reachable:
779 if r not in stoprevs:
777 if r not in stoprevs:
780 reachable[r] = 1
778 reachable[r] = 1
781 heads[r] = 1
779 heads[r] = 1
782 if p in heads and p not in stoprevs:
780 if p in heads and p not in stoprevs:
783 del heads[p]
781 del heads[p]
784
782
785 return [self.node(r) for r in heads]
783 return [self.node(r) for r in heads]
786
784
787 def children(self, node):
785 def children(self, node):
788 """find the children of a given node"""
786 """find the children of a given node"""
789 c = []
787 c = []
790 p = self.rev(node)
788 p = self.rev(node)
791 for r in range(p + 1, self.count()):
789 for r in range(p + 1, self.count()):
792 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
790 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
793 if prevs:
791 if prevs:
794 for pr in prevs:
792 for pr in prevs:
795 if pr == p:
793 if pr == p:
796 c.append(self.node(r))
794 c.append(self.node(r))
797 elif p == nullrev:
795 elif p == nullrev:
798 c.append(self.node(r))
796 c.append(self.node(r))
799 return c
797 return c
800
798
801 def _match(self, id):
799 def _match(self, id):
802 if isinstance(id, (long, int)):
800 if isinstance(id, (long, int)):
803 # rev
801 # rev
804 return self.node(id)
802 return self.node(id)
805 if len(id) == 20:
803 if len(id) == 20:
806 # possibly a binary node
804 # possibly a binary node
807 # odds of a binary node being all hex in ASCII are 1 in 10**25
805 # odds of a binary node being all hex in ASCII are 1 in 10**25
808 try:
806 try:
809 node = id
807 node = id
810 r = self.rev(node) # quick search the index
808 r = self.rev(node) # quick search the index
811 return node
809 return node
812 except LookupError:
810 except LookupError:
813 pass # may be partial hex id
811 pass # may be partial hex id
814 try:
812 try:
815 # str(rev)
813 # str(rev)
816 rev = int(id)
814 rev = int(id)
817 if str(rev) != id:
815 if str(rev) != id:
818 raise ValueError
816 raise ValueError
819 if rev < 0:
817 if rev < 0:
820 rev = self.count() + rev
818 rev = self.count() + rev
821 if rev < 0 or rev >= self.count():
819 if rev < 0 or rev >= self.count():
822 raise ValueError
820 raise ValueError
823 return self.node(rev)
821 return self.node(rev)
824 except (ValueError, OverflowError):
822 except (ValueError, OverflowError):
825 pass
823 pass
826 if len(id) == 40:
824 if len(id) == 40:
827 try:
825 try:
828 # a full hex nodeid?
826 # a full hex nodeid?
829 node = bin(id)
827 node = bin(id)
830 r = self.rev(node)
828 r = self.rev(node)
831 return node
829 return node
832 except TypeError:
830 except TypeError:
833 pass
831 pass
834
832
835 def _partialmatch(self, id):
833 def _partialmatch(self, id):
836 if len(id) < 40:
834 if len(id) < 40:
837 try:
835 try:
838 # hex(node)[:...]
836 # hex(node)[:...]
839 bin_id = bin(id[:len(id) & ~1]) # grab an even number of digits
837 bin_id = bin(id[:len(id) & ~1]) # grab an even number of digits
840 node = None
838 node = None
841 for n in self.nodemap:
839 for n in self.nodemap:
842 if n.startswith(bin_id) and hex(n).startswith(id):
840 if n.startswith(bin_id) and hex(n).startswith(id):
843 if node is not None:
841 if node is not None:
844 raise LookupError(hex(node),
842 raise LookupError(hex(node),
845 _("Ambiguous identifier"))
843 _("Ambiguous identifier"))
846 node = n
844 node = n
847 if node is not None:
845 if node is not None:
848 return node
846 return node
849 except TypeError:
847 except TypeError:
850 pass
848 pass
851
849
852 def lookup(self, id):
850 def lookup(self, id):
853 """locate a node based on:
851 """locate a node based on:
854 - revision number or str(revision number)
852 - revision number or str(revision number)
855 - nodeid or subset of hex nodeid
853 - nodeid or subset of hex nodeid
856 """
854 """
857 n = self._match(id)
855 n = self._match(id)
858 if n is not None:
856 if n is not None:
859 return n
857 return n
860 n = self._partialmatch(id)
858 n = self._partialmatch(id)
861 if n:
859 if n:
862 return n
860 return n
863
861
864 raise LookupError(id, _("No match found"))
862 raise LookupError(id, _("No match found"))
865
863
866 def cmp(self, node, text):
864 def cmp(self, node, text):
867 """compare text with a given file revision"""
865 """compare text with a given file revision"""
868 p1, p2 = self.parents(node)
866 p1, p2 = self.parents(node)
869 return hash(text, p1, p2) != node
867 return hash(text, p1, p2) != node
870
868
871 def chunk(self, rev, df=None):
869 def chunk(self, rev, df=None):
872 def loadcache(df):
870 def loadcache(df):
873 if not df:
871 if not df:
874 if self._inline:
872 if self._inline:
875 df = self.opener(self.indexfile)
873 df = self.opener(self.indexfile)
876 else:
874 else:
877 df = self.opener(self.datafile)
875 df = self.opener(self.datafile)
878 df.seek(start)
876 df.seek(start)
879 self._chunkcache = (start, df.read(cache_length))
877 self._chunkcache = (start, df.read(cache_length))
880
878
881 start, length = self.start(rev), self.length(rev)
879 start, length = self.start(rev), self.length(rev)
882 if self._inline:
880 if self._inline:
883 start += (rev + 1) * self._io.size
881 start += (rev + 1) * self._io.size
884 end = start + length
882 end = start + length
885
883
886 offset = 0
884 offset = 0
887 if not self._chunkcache:
885 if not self._chunkcache:
888 cache_length = max(65536, length)
886 cache_length = max(65536, length)
889 loadcache(df)
887 loadcache(df)
890 else:
888 else:
891 cache_start = self._chunkcache[0]
889 cache_start = self._chunkcache[0]
892 cache_length = len(self._chunkcache[1])
890 cache_length = len(self._chunkcache[1])
893 cache_end = cache_start + cache_length
891 cache_end = cache_start + cache_length
894 if start >= cache_start and end <= cache_end:
892 if start >= cache_start and end <= cache_end:
895 # it is cached
893 # it is cached
896 offset = start - cache_start
894 offset = start - cache_start
897 else:
895 else:
898 cache_length = max(65536, length)
896 cache_length = max(65536, length)
899 loadcache(df)
897 loadcache(df)
900
898
901 # avoid copying large chunks
899 # avoid copying large chunks
902 c = self._chunkcache[1]
900 c = self._chunkcache[1]
903 if cache_length != length:
901 if cache_length != length:
904 c = c[offset:offset + length]
902 c = c[offset:offset + length]
905
903
906 return decompress(c)
904 return decompress(c)
907
905
908 def delta(self, node):
906 def delta(self, node):
909 """return or calculate a delta between a node and its predecessor"""
907 """return or calculate a delta between a node and its predecessor"""
910 r = self.rev(node)
908 r = self.rev(node)
911 return self.revdiff(r - 1, r)
909 return self.revdiff(r - 1, r)
912
910
913 def revdiff(self, rev1, rev2):
911 def revdiff(self, rev1, rev2):
914 """return or calculate a delta between two revisions"""
912 """return or calculate a delta between two revisions"""
915 if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
913 if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
916 return self.chunk(rev2)
914 return self.chunk(rev2)
917
915
918 return mdiff.textdiff(self.revision(self.node(rev1)),
916 return mdiff.textdiff(self.revision(self.node(rev1)),
919 self.revision(self.node(rev2)))
917 self.revision(self.node(rev2)))
920
918
921 def revision(self, node):
919 def revision(self, node):
922 """return an uncompressed revision of a given"""
920 """return an uncompressed revision of a given"""
923 if node == nullid:
921 if node == nullid:
924 return ""
922 return ""
925 if self._cache and self._cache[0] == node:
923 if self._cache and self._cache[0] == node:
926 return str(self._cache[2])
924 return str(self._cache[2])
927
925
928 # look up what we need to read
926 # look up what we need to read
929 text = None
927 text = None
930 rev = self.rev(node)
928 rev = self.rev(node)
931 base = self.base(rev)
929 base = self.base(rev)
932
930
933 # check rev flags
931 # check rev flags
934 if self.index[rev][0] & 0xFFFF:
932 if self.index[rev][0] & 0xFFFF:
935 raise RevlogError(_('incompatible revision flag %x') %
933 raise RevlogError(_('incompatible revision flag %x') %
936 (self.index[rev][0] & 0xFFFF))
934 (self.index[rev][0] & 0xFFFF))
937
935
938 if self._inline:
936 if self._inline:
939 # we probably have the whole chunk cached
937 # we probably have the whole chunk cached
940 df = None
938 df = None
941 else:
939 else:
942 df = self.opener(self.datafile)
940 df = self.opener(self.datafile)
943
941
944 # do we have useful data cached?
942 # do we have useful data cached?
945 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
943 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
946 base = self._cache[1]
944 base = self._cache[1]
947 text = str(self._cache[2])
945 text = str(self._cache[2])
948 self._loadindex(base, rev + 1)
946 self._loadindex(base, rev + 1)
949 else:
947 else:
950 self._loadindex(base, rev + 1)
948 self._loadindex(base, rev + 1)
951 text = self.chunk(base, df=df)
949 text = self.chunk(base, df=df)
952
950
953 bins = [self.chunk(r, df) for r in xrange(base + 1, rev + 1)]
951 bins = [self.chunk(r, df) for r in xrange(base + 1, rev + 1)]
954 text = mdiff.patches(text, bins)
952 text = mdiff.patches(text, bins)
955 p1, p2 = self.parents(node)
953 p1, p2 = self.parents(node)
956 if node != hash(text, p1, p2):
954 if node != hash(text, p1, p2):
957 raise RevlogError(_("integrity check failed on %s:%d")
955 raise RevlogError(_("integrity check failed on %s:%d")
958 % (self.datafile, rev))
956 % (self.datafile, rev))
959
957
960 self._cache = (node, rev, text)
958 self._cache = (node, rev, text)
961 return text
959 return text
962
960
963 def checkinlinesize(self, tr, fp=None):
961 def checkinlinesize(self, tr, fp=None):
964 if not self._inline:
962 if not self._inline:
965 return
963 return
966 if not fp:
964 if not fp:
967 fp = self.opener(self.indexfile, 'r')
965 fp = self.opener(self.indexfile, 'r')
968 fp.seek(0, 2)
966 fp.seek(0, 2)
969 size = fp.tell()
967 size = fp.tell()
970 if size < 131072:
968 if size < 131072:
971 return
969 return
972 trinfo = tr.find(self.indexfile)
970 trinfo = tr.find(self.indexfile)
973 if trinfo == None:
971 if trinfo == None:
974 raise RevlogError(_("%s not found in the transaction")
972 raise RevlogError(_("%s not found in the transaction")
975 % self.indexfile)
973 % self.indexfile)
976
974
977 trindex = trinfo[2]
975 trindex = trinfo[2]
978 dataoff = self.start(trindex)
976 dataoff = self.start(trindex)
979
977
980 tr.add(self.datafile, dataoff)
978 tr.add(self.datafile, dataoff)
981 df = self.opener(self.datafile, 'w')
979 df = self.opener(self.datafile, 'w')
982 calc = self._io.size
980 calc = self._io.size
983 for r in xrange(self.count()):
981 for r in xrange(self.count()):
984 start = self.start(r) + (r + 1) * calc
982 start = self.start(r) + (r + 1) * calc
985 length = self.length(r)
983 length = self.length(r)
986 fp.seek(start)
984 fp.seek(start)
987 d = fp.read(length)
985 d = fp.read(length)
988 df.write(d)
986 df.write(d)
989 fp.close()
987 fp.close()
990 df.close()
988 df.close()
991 fp = self.opener(self.indexfile, 'w', atomictemp=True)
989 fp = self.opener(self.indexfile, 'w', atomictemp=True)
992 self.version &= ~(REVLOGNGINLINEDATA)
990 self.version &= ~(REVLOGNGINLINEDATA)
993 self._inline = False
991 self._inline = False
994 for i in xrange(self.count()):
992 for i in xrange(self.count()):
995 e = self._io.packentry(self.index[i], self.node, self.version, i)
993 e = self._io.packentry(self.index[i], self.node, self.version, i)
996 fp.write(e)
994 fp.write(e)
997
995
998 # if we don't call rename, the temp file will never replace the
996 # if we don't call rename, the temp file will never replace the
999 # real index
997 # real index
1000 fp.rename()
998 fp.rename()
1001
999
1002 tr.replace(self.indexfile, trindex * calc)
1000 tr.replace(self.indexfile, trindex * calc)
1003 self._chunkcache = None
1001 self._chunkcache = None
1004
1002
1005 def addrevision(self, text, transaction, link, p1, p2, d=None):
1003 def addrevision(self, text, transaction, link, p1, p2, d=None):
1006 """add a revision to the log
1004 """add a revision to the log
1007
1005
1008 text - the revision data to add
1006 text - the revision data to add
1009 transaction - the transaction object used for rollback
1007 transaction - the transaction object used for rollback
1010 link - the linkrev data to add
1008 link - the linkrev data to add
1011 p1, p2 - the parent nodeids of the revision
1009 p1, p2 - the parent nodeids of the revision
1012 d - an optional precomputed delta
1010 d - an optional precomputed delta
1013 """
1011 """
1014 dfh = None
1012 dfh = None
1015 if not self._inline:
1013 if not self._inline:
1016 dfh = self.opener(self.datafile, "a")
1014 dfh = self.opener(self.datafile, "a")
1017 ifh = self.opener(self.indexfile, "a+")
1015 ifh = self.opener(self.indexfile, "a+")
1018 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1016 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1019
1017
1020 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1018 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1021 node = hash(text, p1, p2)
1019 node = hash(text, p1, p2)
1022 if node in self.nodemap:
1020 if node in self.nodemap:
1023 return node
1021 return node
1024
1022
1025 curr = self.count()
1023 curr = self.count()
1026 prev = curr - 1
1024 prev = curr - 1
1027 base = self.base(prev)
1025 base = self.base(prev)
1028 offset = self.end(prev)
1026 offset = self.end(prev)
1029
1027
1030 if curr:
1028 if curr:
1031 if not d:
1029 if not d:
1032 ptext = self.revision(self.node(prev))
1030 ptext = self.revision(self.node(prev))
1033 d = mdiff.textdiff(ptext, text)
1031 d = mdiff.textdiff(ptext, text)
1034 data = compress(d)
1032 data = compress(d)
1035 l = len(data[1]) + len(data[0])
1033 l = len(data[1]) + len(data[0])
1036 dist = l + offset - self.start(base)
1034 dist = l + offset - self.start(base)
1037
1035
1038 # full versions are inserted when the needed deltas
1036 # full versions are inserted when the needed deltas
1039 # become comparable to the uncompressed text
1037 # become comparable to the uncompressed text
1040 if not curr or dist > len(text) * 2:
1038 if not curr or dist > len(text) * 2:
1041 data = compress(text)
1039 data = compress(text)
1042 l = len(data[1]) + len(data[0])
1040 l = len(data[1]) + len(data[0])
1043 base = curr
1041 base = curr
1044
1042
1045 e = (offset_type(offset, 0), l, len(text),
1043 e = (offset_type(offset, 0), l, len(text),
1046 base, link, self.rev(p1), self.rev(p2), node)
1044 base, link, self.rev(p1), self.rev(p2), node)
1047 self.index.insert(-1, e)
1045 self.index.insert(-1, e)
1048 self.nodemap[node] = curr
1046 self.nodemap[node] = curr
1049
1047
1050 entry = self._io.packentry(e, self.node, self.version, curr)
1048 entry = self._io.packentry(e, self.node, self.version, curr)
1051 if not self._inline:
1049 if not self._inline:
1052 transaction.add(self.datafile, offset)
1050 transaction.add(self.datafile, offset)
1053 transaction.add(self.indexfile, curr * len(entry))
1051 transaction.add(self.indexfile, curr * len(entry))
1054 if data[0]:
1052 if data[0]:
1055 dfh.write(data[0])
1053 dfh.write(data[0])
1056 dfh.write(data[1])
1054 dfh.write(data[1])
1057 dfh.flush()
1055 dfh.flush()
1058 ifh.write(entry)
1056 ifh.write(entry)
1059 else:
1057 else:
1060 offset += curr * self._io.size
1058 offset += curr * self._io.size
1061 transaction.add(self.indexfile, offset, curr)
1059 transaction.add(self.indexfile, offset, curr)
1062 ifh.write(entry)
1060 ifh.write(entry)
1063 ifh.write(data[0])
1061 ifh.write(data[0])
1064 ifh.write(data[1])
1062 ifh.write(data[1])
1065 self.checkinlinesize(transaction, ifh)
1063 self.checkinlinesize(transaction, ifh)
1066
1064
1067 self._cache = (node, curr, text)
1065 self._cache = (node, curr, text)
1068 return node
1066 return node
1069
1067
1070 def ancestor(self, a, b):
1068 def ancestor(self, a, b):
1071 """calculate the least common ancestor of nodes a and b"""
1069 """calculate the least common ancestor of nodes a and b"""
1072
1070
1073 def parents(rev):
1071 def parents(rev):
1074 return [p for p in self.parentrevs(rev) if p != nullrev]
1072 return [p for p in self.parentrevs(rev) if p != nullrev]
1075
1073
1076 c = ancestor.ancestor(self.rev(a), self.rev(b), parents)
1074 c = ancestor.ancestor(self.rev(a), self.rev(b), parents)
1077 if c is None:
1075 if c is None:
1078 return nullid
1076 return nullid
1079
1077
1080 return self.node(c)
1078 return self.node(c)
1081
1079
1082 def group(self, nodelist, lookup, infocollect=None):
1080 def group(self, nodelist, lookup, infocollect=None):
1083 """calculate a delta group
1081 """calculate a delta group
1084
1082
1085 Given a list of changeset revs, return a set of deltas and
1083 Given a list of changeset revs, return a set of deltas and
1086 metadata corresponding to nodes. the first delta is
1084 metadata corresponding to nodes. the first delta is
1087 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1085 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1088 have this parent as it has all history before these
1086 have this parent as it has all history before these
1089 changesets. parent is parent[0]
1087 changesets. parent is parent[0]
1090 """
1088 """
1091 revs = [self.rev(n) for n in nodelist]
1089 revs = [self.rev(n) for n in nodelist]
1092
1090
1093 # if we don't have any revisions touched by these changesets, bail
1091 # if we don't have any revisions touched by these changesets, bail
1094 if not revs:
1092 if not revs:
1095 yield changegroup.closechunk()
1093 yield changegroup.closechunk()
1096 return
1094 return
1097
1095
1098 # add the parent of the first rev
1096 # add the parent of the first rev
1099 p = self.parents(self.node(revs[0]))[0]
1097 p = self.parents(self.node(revs[0]))[0]
1100 revs.insert(0, self.rev(p))
1098 revs.insert(0, self.rev(p))
1101
1099
1102 # build deltas
1100 # build deltas
1103 for d in xrange(0, len(revs) - 1):
1101 for d in xrange(0, len(revs) - 1):
1104 a, b = revs[d], revs[d + 1]
1102 a, b = revs[d], revs[d + 1]
1105 nb = self.node(b)
1103 nb = self.node(b)
1106
1104
1107 if infocollect is not None:
1105 if infocollect is not None:
1108 infocollect(nb)
1106 infocollect(nb)
1109
1107
1110 p = self.parents(nb)
1108 p = self.parents(nb)
1111 meta = nb + p[0] + p[1] + lookup(nb)
1109 meta = nb + p[0] + p[1] + lookup(nb)
1112 if a == -1:
1110 if a == -1:
1113 d = self.revision(nb)
1111 d = self.revision(nb)
1114 meta += mdiff.trivialdiffheader(len(d))
1112 meta += mdiff.trivialdiffheader(len(d))
1115 else:
1113 else:
1116 d = self.revdiff(a, b)
1114 d = self.revdiff(a, b)
1117 yield changegroup.chunkheader(len(meta) + len(d))
1115 yield changegroup.chunkheader(len(meta) + len(d))
1118 yield meta
1116 yield meta
1119 if len(d) > 2**20:
1117 if len(d) > 2**20:
1120 pos = 0
1118 pos = 0
1121 while pos < len(d):
1119 while pos < len(d):
1122 pos2 = pos + 2 ** 18
1120 pos2 = pos + 2 ** 18
1123 yield d[pos:pos2]
1121 yield d[pos:pos2]
1124 pos = pos2
1122 pos = pos2
1125 else:
1123 else:
1126 yield d
1124 yield d
1127
1125
1128 yield changegroup.closechunk()
1126 yield changegroup.closechunk()
1129
1127
1130 def addgroup(self, revs, linkmapper, transaction, unique=0):
1128 def addgroup(self, revs, linkmapper, transaction, unique=0):
1131 """
1129 """
1132 add a delta group
1130 add a delta group
1133
1131
1134 given a set of deltas, add them to the revision log. the
1132 given a set of deltas, add them to the revision log. the
1135 first delta is against its parent, which should be in our
1133 first delta is against its parent, which should be in our
1136 log, the rest are against the previous delta.
1134 log, the rest are against the previous delta.
1137 """
1135 """
1138
1136
1139 #track the base of the current delta log
1137 #track the base of the current delta log
1140 r = self.count()
1138 r = self.count()
1141 t = r - 1
1139 t = r - 1
1142 node = None
1140 node = None
1143
1141
1144 base = prev = nullrev
1142 base = prev = nullrev
1145 start = end = textlen = 0
1143 start = end = textlen = 0
1146 if r:
1144 if r:
1147 end = self.end(t)
1145 end = self.end(t)
1148
1146
1149 ifh = self.opener(self.indexfile, "a+")
1147 ifh = self.opener(self.indexfile, "a+")
1150 isize = r * self._io.size
1148 isize = r * self._io.size
1151 if self._inline:
1149 if self._inline:
1152 transaction.add(self.indexfile, end + isize, r)
1150 transaction.add(self.indexfile, end + isize, r)
1153 dfh = None
1151 dfh = None
1154 else:
1152 else:
1155 transaction.add(self.indexfile, isize, r)
1153 transaction.add(self.indexfile, isize, r)
1156 transaction.add(self.datafile, end)
1154 transaction.add(self.datafile, end)
1157 dfh = self.opener(self.datafile, "a")
1155 dfh = self.opener(self.datafile, "a")
1158
1156
1159 # loop through our set of deltas
1157 # loop through our set of deltas
1160 chain = None
1158 chain = None
1161 for chunk in revs:
1159 for chunk in revs:
1162 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1160 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1163 link = linkmapper(cs)
1161 link = linkmapper(cs)
1164 if node in self.nodemap:
1162 if node in self.nodemap:
1165 # this can happen if two branches make the same change
1163 # this can happen if two branches make the same change
1166 # if unique:
1164 # if unique:
1167 # raise RevlogError(_("already have %s") % hex(node[:4]))
1165 # raise RevlogError(_("already have %s") % hex(node[:4]))
1168 chain = node
1166 chain = node
1169 continue
1167 continue
1170 delta = buffer(chunk, 80)
1168 delta = buffer(chunk, 80)
1171 del chunk
1169 del chunk
1172
1170
1173 for p in (p1, p2):
1171 for p in (p1, p2):
1174 if not p in self.nodemap:
1172 if not p in self.nodemap:
1175 raise LookupError(hex(p), _("unknown parent %s") % short(p))
1173 raise LookupError(hex(p), _("unknown parent %s") % short(p))
1176
1174
1177 if not chain:
1175 if not chain:
1178 # retrieve the parent revision of the delta chain
1176 # retrieve the parent revision of the delta chain
1179 chain = p1
1177 chain = p1
1180 if not chain in self.nodemap:
1178 if not chain in self.nodemap:
1181 raise LookupError(hex(chain), _("unknown base %s") % short(chain[:4]))
1179 raise LookupError(hex(chain), _("unknown base %s") % short(chain[:4]))
1182
1180
1183 # full versions are inserted when the needed deltas become
1181 # full versions are inserted when the needed deltas become
1184 # comparable to the uncompressed text or when the previous
1182 # comparable to the uncompressed text or when the previous
1185 # version is not the one we have a delta against. We use
1183 # version is not the one we have a delta against. We use
1186 # the size of the previous full rev as a proxy for the
1184 # the size of the previous full rev as a proxy for the
1187 # current size.
1185 # current size.
1188
1186
1189 if chain == prev:
1187 if chain == prev:
1190 cdelta = compress(delta)
1188 cdelta = compress(delta)
1191 cdeltalen = len(cdelta[0]) + len(cdelta[1])
1189 cdeltalen = len(cdelta[0]) + len(cdelta[1])
1192 textlen = mdiff.patchedsize(textlen, delta)
1190 textlen = mdiff.patchedsize(textlen, delta)
1193
1191
1194 if chain != prev or (end - start + cdeltalen) > textlen * 2:
1192 if chain != prev or (end - start + cdeltalen) > textlen * 2:
1195 # flush our writes here so we can read it in revision
1193 # flush our writes here so we can read it in revision
1196 if dfh:
1194 if dfh:
1197 dfh.flush()
1195 dfh.flush()
1198 ifh.flush()
1196 ifh.flush()
1199 text = self.revision(chain)
1197 text = self.revision(chain)
1200 if len(text) == 0:
1198 if len(text) == 0:
1201 # skip over trivial delta header
1199 # skip over trivial delta header
1202 text = buffer(delta, 12)
1200 text = buffer(delta, 12)
1203 else:
1201 else:
1204 text = mdiff.patches(text, [delta])
1202 text = mdiff.patches(text, [delta])
1205 del delta
1203 del delta
1206 chk = self._addrevision(text, transaction, link, p1, p2, None,
1204 chk = self._addrevision(text, transaction, link, p1, p2, None,
1207 ifh, dfh)
1205 ifh, dfh)
1208 if not dfh and not self._inline:
1206 if not dfh and not self._inline:
1209 # addrevision switched from inline to conventional
1207 # addrevision switched from inline to conventional
1210 # reopen the index
1208 # reopen the index
1211 dfh = self.opener(self.datafile, "a")
1209 dfh = self.opener(self.datafile, "a")
1212 ifh = self.opener(self.indexfile, "a")
1210 ifh = self.opener(self.indexfile, "a")
1213 if chk != node:
1211 if chk != node:
1214 raise RevlogError(_("consistency error adding group"))
1212 raise RevlogError(_("consistency error adding group"))
1215 textlen = len(text)
1213 textlen = len(text)
1216 else:
1214 else:
1217 e = (offset_type(end, 0), cdeltalen, textlen, base,
1215 e = (offset_type(end, 0), cdeltalen, textlen, base,
1218 link, self.rev(p1), self.rev(p2), node)
1216 link, self.rev(p1), self.rev(p2), node)
1219 self.index.insert(-1, e)
1217 self.index.insert(-1, e)
1220 self.nodemap[node] = r
1218 self.nodemap[node] = r
1221 entry = self._io.packentry(e, self.node, self.version, r)
1219 entry = self._io.packentry(e, self.node, self.version, r)
1222 if self._inline:
1220 if self._inline:
1223 ifh.write(entry)
1221 ifh.write(entry)
1224 ifh.write(cdelta[0])
1222 ifh.write(cdelta[0])
1225 ifh.write(cdelta[1])
1223 ifh.write(cdelta[1])
1226 self.checkinlinesize(transaction, ifh)
1224 self.checkinlinesize(transaction, ifh)
1227 if not self._inline:
1225 if not self._inline:
1228 dfh = self.opener(self.datafile, "a")
1226 dfh = self.opener(self.datafile, "a")
1229 ifh = self.opener(self.indexfile, "a")
1227 ifh = self.opener(self.indexfile, "a")
1230 else:
1228 else:
1231 dfh.write(cdelta[0])
1229 dfh.write(cdelta[0])
1232 dfh.write(cdelta[1])
1230 dfh.write(cdelta[1])
1233 ifh.write(entry)
1231 ifh.write(entry)
1234
1232
1235 t, r, chain, prev = r, r + 1, node, node
1233 t, r, chain, prev = r, r + 1, node, node
1236 base = self.base(t)
1234 base = self.base(t)
1237 start = self.start(base)
1235 start = self.start(base)
1238 end = self.end(t)
1236 end = self.end(t)
1239
1237
1240 return node
1238 return node
1241
1239
1242 def strip(self, rev, minlink):
1240 def strip(self, rev, minlink):
1243 if self.count() == 0 or rev >= self.count():
1241 if self.count() == 0 or rev >= self.count():
1244 return
1242 return
1245
1243
1246 if isinstance(self.index, lazyindex):
1244 if isinstance(self.index, lazyindex):
1247 self._loadindexmap()
1245 self._loadindexmap()
1248
1246
1249 # When stripping away a revision, we need to make sure it
1247 # When stripping away a revision, we need to make sure it
1250 # does not actually belong to an older changeset.
1248 # does not actually belong to an older changeset.
1251 # The minlink parameter defines the oldest revision
1249 # The minlink parameter defines the oldest revision
1252 # we're allowed to strip away.
1250 # we're allowed to strip away.
1253 while minlink > self.index[rev][4]:
1251 while minlink > self.index[rev][4]:
1254 rev += 1
1252 rev += 1
1255 if rev >= self.count():
1253 if rev >= self.count():
1256 return
1254 return
1257
1255
1258 # first truncate the files on disk
1256 # first truncate the files on disk
1259 end = self.start(rev)
1257 end = self.start(rev)
1260 if not self._inline:
1258 if not self._inline:
1261 df = self.opener(self.datafile, "a")
1259 df = self.opener(self.datafile, "a")
1262 df.truncate(end)
1260 df.truncate(end)
1263 end = rev * self._io.size
1261 end = rev * self._io.size
1264 else:
1262 else:
1265 end += rev * self._io.size
1263 end += rev * self._io.size
1266
1264
1267 indexf = self.opener(self.indexfile, "a")
1265 indexf = self.opener(self.indexfile, "a")
1268 indexf.truncate(end)
1266 indexf.truncate(end)
1269
1267
1270 # then reset internal state in memory to forget those revisions
1268 # then reset internal state in memory to forget those revisions
1271 self._cache = None
1269 self._cache = None
1272 self._chunkcache = None
1270 self._chunkcache = None
1273 for x in xrange(rev, self.count()):
1271 for x in xrange(rev, self.count()):
1274 del self.nodemap[self.node(x)]
1272 del self.nodemap[self.node(x)]
1275
1273
1276 del self.index[rev:-1]
1274 del self.index[rev:-1]
1277
1275
1278 def checksize(self):
1276 def checksize(self):
1279 expected = 0
1277 expected = 0
1280 if self.count():
1278 if self.count():
1281 expected = max(0, self.end(self.count() - 1))
1279 expected = max(0, self.end(self.count() - 1))
1282
1280
1283 try:
1281 try:
1284 f = self.opener(self.datafile)
1282 f = self.opener(self.datafile)
1285 f.seek(0, 2)
1283 f.seek(0, 2)
1286 actual = f.tell()
1284 actual = f.tell()
1287 dd = actual - expected
1285 dd = actual - expected
1288 except IOError, inst:
1286 except IOError, inst:
1289 if inst.errno != errno.ENOENT:
1287 if inst.errno != errno.ENOENT:
1290 raise
1288 raise
1291 dd = 0
1289 dd = 0
1292
1290
1293 try:
1291 try:
1294 f = self.opener(self.indexfile)
1292 f = self.opener(self.indexfile)
1295 f.seek(0, 2)
1293 f.seek(0, 2)
1296 actual = f.tell()
1294 actual = f.tell()
1297 s = self._io.size
1295 s = self._io.size
1298 i = max(0, actual / s)
1296 i = max(0, actual / s)
1299 di = actual - (i * s)
1297 di = actual - (i * s)
1300 if self._inline:
1298 if self._inline:
1301 databytes = 0
1299 databytes = 0
1302 for r in xrange(self.count()):
1300 for r in xrange(self.count()):
1303 databytes += max(0, self.length(r))
1301 databytes += max(0, self.length(r))
1304 dd = 0
1302 dd = 0
1305 di = actual - self.count() * s - databytes
1303 di = actual - self.count() * s - databytes
1306 except IOError, inst:
1304 except IOError, inst:
1307 if inst.errno != errno.ENOENT:
1305 if inst.errno != errno.ENOENT:
1308 raise
1306 raise
1309 di = 0
1307 di = 0
1310
1308
1311 return (dd, di)
1309 return (dd, di)
@@ -1,1750 +1,1757 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import _
15 from i18n import _
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile, strutil
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile, strutil
17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
18 import re, urlparse
18 import re, urlparse
19
19
20 try:
20 try:
21 set = set
21 set = set
22 frozenset = frozenset
22 frozenset = frozenset
23 except NameError:
23 except NameError:
24 from sets import Set as set, ImmutableSet as frozenset
24 from sets import Set as set, ImmutableSet as frozenset
25
25
26 try:
26 try:
27 _encoding = os.environ.get("HGENCODING")
27 _encoding = os.environ.get("HGENCODING")
28 if sys.platform == 'darwin' and not _encoding:
28 if sys.platform == 'darwin' and not _encoding:
29 # On darwin, getpreferredencoding ignores the locale environment and
29 # On darwin, getpreferredencoding ignores the locale environment and
30 # always returns mac-roman. We override this if the environment is
30 # always returns mac-roman. We override this if the environment is
31 # not C (has been customized by the user).
31 # not C (has been customized by the user).
32 locale.setlocale(locale.LC_CTYPE, '')
32 locale.setlocale(locale.LC_CTYPE, '')
33 _encoding = locale.getlocale()[1]
33 _encoding = locale.getlocale()[1]
34 if not _encoding:
34 if not _encoding:
35 _encoding = locale.getpreferredencoding() or 'ascii'
35 _encoding = locale.getpreferredencoding() or 'ascii'
36 except locale.Error:
36 except locale.Error:
37 _encoding = 'ascii'
37 _encoding = 'ascii'
38 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
38 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
39 _fallbackencoding = 'ISO-8859-1'
39 _fallbackencoding = 'ISO-8859-1'
40
40
41 def tolocal(s):
41 def tolocal(s):
42 """
42 """
43 Convert a string from internal UTF-8 to local encoding
43 Convert a string from internal UTF-8 to local encoding
44
44
45 All internal strings should be UTF-8 but some repos before the
45 All internal strings should be UTF-8 but some repos before the
46 implementation of locale support may contain latin1 or possibly
46 implementation of locale support may contain latin1 or possibly
47 other character sets. We attempt to decode everything strictly
47 other character sets. We attempt to decode everything strictly
48 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
48 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
49 replace unknown characters.
49 replace unknown characters.
50 """
50 """
51 for e in ('UTF-8', _fallbackencoding):
51 for e in ('UTF-8', _fallbackencoding):
52 try:
52 try:
53 u = s.decode(e) # attempt strict decoding
53 u = s.decode(e) # attempt strict decoding
54 return u.encode(_encoding, "replace")
54 return u.encode(_encoding, "replace")
55 except LookupError, k:
55 except LookupError, k:
56 raise Abort(_("%s, please check your locale settings") % k)
56 raise Abort(_("%s, please check your locale settings") % k)
57 except UnicodeDecodeError:
57 except UnicodeDecodeError:
58 pass
58 pass
59 u = s.decode("utf-8", "replace") # last ditch
59 u = s.decode("utf-8", "replace") # last ditch
60 return u.encode(_encoding, "replace")
60 return u.encode(_encoding, "replace")
61
61
62 def fromlocal(s):
62 def fromlocal(s):
63 """
63 """
64 Convert a string from the local character encoding to UTF-8
64 Convert a string from the local character encoding to UTF-8
65
65
66 We attempt to decode strings using the encoding mode set by
66 We attempt to decode strings using the encoding mode set by
67 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
67 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
68 characters will cause an error message. Other modes include
68 characters will cause an error message. Other modes include
69 'replace', which replaces unknown characters with a special
69 'replace', which replaces unknown characters with a special
70 Unicode character, and 'ignore', which drops the character.
70 Unicode character, and 'ignore', which drops the character.
71 """
71 """
72 try:
72 try:
73 return s.decode(_encoding, _encodingmode).encode("utf-8")
73 return s.decode(_encoding, _encodingmode).encode("utf-8")
74 except UnicodeDecodeError, inst:
74 except UnicodeDecodeError, inst:
75 sub = s[max(0, inst.start-10):inst.start+10]
75 sub = s[max(0, inst.start-10):inst.start+10]
76 raise Abort("decoding near '%s': %s!" % (sub, inst))
76 raise Abort("decoding near '%s': %s!" % (sub, inst))
77 except LookupError, k:
77 except LookupError, k:
78 raise Abort(_("%s, please check your locale settings") % k)
78 raise Abort(_("%s, please check your locale settings") % k)
79
79
80 def locallen(s):
80 def locallen(s):
81 """Find the length in characters of a local string"""
81 """Find the length in characters of a local string"""
82 return len(s.decode(_encoding, "replace"))
82 return len(s.decode(_encoding, "replace"))
83
83
84 def localsub(s, a, b=None):
84 def localsub(s, a, b=None):
85 try:
85 try:
86 u = s.decode(_encoding, _encodingmode)
86 u = s.decode(_encoding, _encodingmode)
87 if b is not None:
87 if b is not None:
88 u = u[a:b]
88 u = u[a:b]
89 else:
89 else:
90 u = u[:a]
90 u = u[:a]
91 return u.encode(_encoding, _encodingmode)
91 return u.encode(_encoding, _encodingmode)
92 except UnicodeDecodeError, inst:
92 except UnicodeDecodeError, inst:
93 sub = s[max(0, inst.start-10), inst.start+10]
93 sub = s[max(0, inst.start-10), inst.start+10]
94 raise Abort(_("decoding near '%s': %s!") % (sub, inst))
94 raise Abort(_("decoding near '%s': %s!") % (sub, inst))
95
95
96 # used by parsedate
96 # used by parsedate
97 defaultdateformats = (
97 defaultdateformats = (
98 '%Y-%m-%d %H:%M:%S',
98 '%Y-%m-%d %H:%M:%S',
99 '%Y-%m-%d %I:%M:%S%p',
99 '%Y-%m-%d %I:%M:%S%p',
100 '%Y-%m-%d %H:%M',
100 '%Y-%m-%d %H:%M',
101 '%Y-%m-%d %I:%M%p',
101 '%Y-%m-%d %I:%M%p',
102 '%Y-%m-%d',
102 '%Y-%m-%d',
103 '%m-%d',
103 '%m-%d',
104 '%m/%d',
104 '%m/%d',
105 '%m/%d/%y',
105 '%m/%d/%y',
106 '%m/%d/%Y',
106 '%m/%d/%Y',
107 '%a %b %d %H:%M:%S %Y',
107 '%a %b %d %H:%M:%S %Y',
108 '%a %b %d %I:%M:%S%p %Y',
108 '%a %b %d %I:%M:%S%p %Y',
109 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
109 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
110 '%b %d %H:%M:%S %Y',
110 '%b %d %H:%M:%S %Y',
111 '%b %d %I:%M:%S%p %Y',
111 '%b %d %I:%M:%S%p %Y',
112 '%b %d %H:%M:%S',
112 '%b %d %H:%M:%S',
113 '%b %d %I:%M:%S%p',
113 '%b %d %I:%M:%S%p',
114 '%b %d %H:%M',
114 '%b %d %H:%M',
115 '%b %d %I:%M%p',
115 '%b %d %I:%M%p',
116 '%b %d %Y',
116 '%b %d %Y',
117 '%b %d',
117 '%b %d',
118 '%H:%M:%S',
118 '%H:%M:%S',
119 '%I:%M:%SP',
119 '%I:%M:%SP',
120 '%H:%M',
120 '%H:%M',
121 '%I:%M%p',
121 '%I:%M%p',
122 )
122 )
123
123
124 extendeddateformats = defaultdateformats + (
124 extendeddateformats = defaultdateformats + (
125 "%Y",
125 "%Y",
126 "%Y-%m",
126 "%Y-%m",
127 "%b",
127 "%b",
128 "%b %Y",
128 "%b %Y",
129 )
129 )
130
130
131 class SignalInterrupt(Exception):
131 class SignalInterrupt(Exception):
132 """Exception raised on SIGTERM and SIGHUP."""
132 """Exception raised on SIGTERM and SIGHUP."""
133
133
134 # differences from SafeConfigParser:
134 # differences from SafeConfigParser:
135 # - case-sensitive keys
135 # - case-sensitive keys
136 # - allows values that are not strings (this means that you may not
136 # - allows values that are not strings (this means that you may not
137 # be able to save the configuration to a file)
137 # be able to save the configuration to a file)
138 class configparser(ConfigParser.SafeConfigParser):
138 class configparser(ConfigParser.SafeConfigParser):
139 def optionxform(self, optionstr):
139 def optionxform(self, optionstr):
140 return optionstr
140 return optionstr
141
141
142 def set(self, section, option, value):
142 def set(self, section, option, value):
143 return ConfigParser.ConfigParser.set(self, section, option, value)
143 return ConfigParser.ConfigParser.set(self, section, option, value)
144
144
145 def _interpolate(self, section, option, rawval, vars):
145 def _interpolate(self, section, option, rawval, vars):
146 if not isinstance(rawval, basestring):
146 if not isinstance(rawval, basestring):
147 return rawval
147 return rawval
148 return ConfigParser.SafeConfigParser._interpolate(self, section,
148 return ConfigParser.SafeConfigParser._interpolate(self, section,
149 option, rawval, vars)
149 option, rawval, vars)
150
150
151 def cachefunc(func):
151 def cachefunc(func):
152 '''cache the result of function calls'''
152 '''cache the result of function calls'''
153 # XXX doesn't handle keywords args
153 # XXX doesn't handle keywords args
154 cache = {}
154 cache = {}
155 if func.func_code.co_argcount == 1:
155 if func.func_code.co_argcount == 1:
156 # we gain a small amount of time because
156 # we gain a small amount of time because
157 # we don't need to pack/unpack the list
157 # we don't need to pack/unpack the list
158 def f(arg):
158 def f(arg):
159 if arg not in cache:
159 if arg not in cache:
160 cache[arg] = func(arg)
160 cache[arg] = func(arg)
161 return cache[arg]
161 return cache[arg]
162 else:
162 else:
163 def f(*args):
163 def f(*args):
164 if args not in cache:
164 if args not in cache:
165 cache[args] = func(*args)
165 cache[args] = func(*args)
166 return cache[args]
166 return cache[args]
167
167
168 return f
168 return f
169
169
170 def pipefilter(s, cmd):
170 def pipefilter(s, cmd):
171 '''filter string S through command CMD, returning its output'''
171 '''filter string S through command CMD, returning its output'''
172 (pin, pout) = os.popen2(cmd, 'b')
172 (pin, pout) = os.popen2(cmd, 'b')
173 def writer():
173 def writer():
174 try:
174 try:
175 pin.write(s)
175 pin.write(s)
176 pin.close()
176 pin.close()
177 except IOError, inst:
177 except IOError, inst:
178 if inst.errno != errno.EPIPE:
178 if inst.errno != errno.EPIPE:
179 raise
179 raise
180
180
181 # we should use select instead on UNIX, but this will work on most
181 # we should use select instead on UNIX, but this will work on most
182 # systems, including Windows
182 # systems, including Windows
183 w = threading.Thread(target=writer)
183 w = threading.Thread(target=writer)
184 w.start()
184 w.start()
185 f = pout.read()
185 f = pout.read()
186 pout.close()
186 pout.close()
187 w.join()
187 w.join()
188 return f
188 return f
189
189
190 def tempfilter(s, cmd):
190 def tempfilter(s, cmd):
191 '''filter string S through a pair of temporary files with CMD.
191 '''filter string S through a pair of temporary files with CMD.
192 CMD is used as a template to create the real command to be run,
192 CMD is used as a template to create the real command to be run,
193 with the strings INFILE and OUTFILE replaced by the real names of
193 with the strings INFILE and OUTFILE replaced by the real names of
194 the temporary files generated.'''
194 the temporary files generated.'''
195 inname, outname = None, None
195 inname, outname = None, None
196 try:
196 try:
197 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
197 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
198 fp = os.fdopen(infd, 'wb')
198 fp = os.fdopen(infd, 'wb')
199 fp.write(s)
199 fp.write(s)
200 fp.close()
200 fp.close()
201 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
201 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
202 os.close(outfd)
202 os.close(outfd)
203 cmd = cmd.replace('INFILE', inname)
203 cmd = cmd.replace('INFILE', inname)
204 cmd = cmd.replace('OUTFILE', outname)
204 cmd = cmd.replace('OUTFILE', outname)
205 code = os.system(cmd)
205 code = os.system(cmd)
206 if sys.platform == 'OpenVMS' and code & 1:
206 if sys.platform == 'OpenVMS' and code & 1:
207 code = 0
207 code = 0
208 if code: raise Abort(_("command '%s' failed: %s") %
208 if code: raise Abort(_("command '%s' failed: %s") %
209 (cmd, explain_exit(code)))
209 (cmd, explain_exit(code)))
210 return open(outname, 'rb').read()
210 return open(outname, 'rb').read()
211 finally:
211 finally:
212 try:
212 try:
213 if inname: os.unlink(inname)
213 if inname: os.unlink(inname)
214 except: pass
214 except: pass
215 try:
215 try:
216 if outname: os.unlink(outname)
216 if outname: os.unlink(outname)
217 except: pass
217 except: pass
218
218
219 filtertable = {
219 filtertable = {
220 'tempfile:': tempfilter,
220 'tempfile:': tempfilter,
221 'pipe:': pipefilter,
221 'pipe:': pipefilter,
222 }
222 }
223
223
224 def filter(s, cmd):
224 def filter(s, cmd):
225 "filter a string through a command that transforms its input to its output"
225 "filter a string through a command that transforms its input to its output"
226 for name, fn in filtertable.iteritems():
226 for name, fn in filtertable.iteritems():
227 if cmd.startswith(name):
227 if cmd.startswith(name):
228 return fn(s, cmd[len(name):].lstrip())
228 return fn(s, cmd[len(name):].lstrip())
229 return pipefilter(s, cmd)
229 return pipefilter(s, cmd)
230
230
231 def binary(s):
231 def binary(s):
232 """return true if a string is binary data using diff's heuristic"""
232 """return true if a string is binary data using diff's heuristic"""
233 if s and '\0' in s[:4096]:
233 if s and '\0' in s[:4096]:
234 return True
234 return True
235 return False
235 return False
236
236
237 def unique(g):
237 def unique(g):
238 """return the uniq elements of iterable g"""
238 """return the uniq elements of iterable g"""
239 seen = {}
239 seen = {}
240 l = []
240 l = []
241 for f in g:
241 for f in g:
242 if f not in seen:
242 if f not in seen:
243 seen[f] = 1
243 seen[f] = 1
244 l.append(f)
244 l.append(f)
245 return l
245 return l
246
246
247 class Abort(Exception):
247 class Abort(Exception):
248 """Raised if a command needs to print an error and exit."""
248 """Raised if a command needs to print an error and exit."""
249
249
250 class UnexpectedOutput(Abort):
250 class UnexpectedOutput(Abort):
251 """Raised to print an error with part of output and exit."""
251 """Raised to print an error with part of output and exit."""
252
252
253 def always(fn): return True
253 def always(fn): return True
254 def never(fn): return False
254 def never(fn): return False
255
255
256 def expand_glob(pats):
256 def expand_glob(pats):
257 '''On Windows, expand the implicit globs in a list of patterns'''
257 '''On Windows, expand the implicit globs in a list of patterns'''
258 if os.name != 'nt':
258 if os.name != 'nt':
259 return list(pats)
259 return list(pats)
260 ret = []
260 ret = []
261 for p in pats:
261 for p in pats:
262 kind, name = patkind(p, None)
262 kind, name = patkind(p, None)
263 if kind is None:
263 if kind is None:
264 globbed = glob.glob(name)
264 globbed = glob.glob(name)
265 if globbed:
265 if globbed:
266 ret.extend(globbed)
266 ret.extend(globbed)
267 continue
267 continue
268 # if we couldn't expand the glob, just keep it around
268 # if we couldn't expand the glob, just keep it around
269 ret.append(p)
269 ret.append(p)
270 return ret
270 return ret
271
271
272 def patkind(name, dflt_pat='glob'):
272 def patkind(name, dflt_pat='glob'):
273 """Split a string into an optional pattern kind prefix and the
273 """Split a string into an optional pattern kind prefix and the
274 actual pattern."""
274 actual pattern."""
275 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
275 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
276 if name.startswith(prefix + ':'): return name.split(':', 1)
276 if name.startswith(prefix + ':'): return name.split(':', 1)
277 return dflt_pat, name
277 return dflt_pat, name
278
278
279 def globre(pat, head='^', tail='$'):
279 def globre(pat, head='^', tail='$'):
280 "convert a glob pattern into a regexp"
280 "convert a glob pattern into a regexp"
281 i, n = 0, len(pat)
281 i, n = 0, len(pat)
282 res = ''
282 res = ''
283 group = False
283 group = False
284 def peek(): return i < n and pat[i]
284 def peek(): return i < n and pat[i]
285 while i < n:
285 while i < n:
286 c = pat[i]
286 c = pat[i]
287 i = i+1
287 i = i+1
288 if c == '*':
288 if c == '*':
289 if peek() == '*':
289 if peek() == '*':
290 i += 1
290 i += 1
291 res += '.*'
291 res += '.*'
292 else:
292 else:
293 res += '[^/]*'
293 res += '[^/]*'
294 elif c == '?':
294 elif c == '?':
295 res += '.'
295 res += '.'
296 elif c == '[':
296 elif c == '[':
297 j = i
297 j = i
298 if j < n and pat[j] in '!]':
298 if j < n and pat[j] in '!]':
299 j += 1
299 j += 1
300 while j < n and pat[j] != ']':
300 while j < n and pat[j] != ']':
301 j += 1
301 j += 1
302 if j >= n:
302 if j >= n:
303 res += '\\['
303 res += '\\['
304 else:
304 else:
305 stuff = pat[i:j].replace('\\','\\\\')
305 stuff = pat[i:j].replace('\\','\\\\')
306 i = j + 1
306 i = j + 1
307 if stuff[0] == '!':
307 if stuff[0] == '!':
308 stuff = '^' + stuff[1:]
308 stuff = '^' + stuff[1:]
309 elif stuff[0] == '^':
309 elif stuff[0] == '^':
310 stuff = '\\' + stuff
310 stuff = '\\' + stuff
311 res = '%s[%s]' % (res, stuff)
311 res = '%s[%s]' % (res, stuff)
312 elif c == '{':
312 elif c == '{':
313 group = True
313 group = True
314 res += '(?:'
314 res += '(?:'
315 elif c == '}' and group:
315 elif c == '}' and group:
316 res += ')'
316 res += ')'
317 group = False
317 group = False
318 elif c == ',' and group:
318 elif c == ',' and group:
319 res += '|'
319 res += '|'
320 elif c == '\\':
320 elif c == '\\':
321 p = peek()
321 p = peek()
322 if p:
322 if p:
323 i += 1
323 i += 1
324 res += re.escape(p)
324 res += re.escape(p)
325 else:
325 else:
326 res += re.escape(c)
326 res += re.escape(c)
327 else:
327 else:
328 res += re.escape(c)
328 res += re.escape(c)
329 return head + res + tail
329 return head + res + tail
330
330
331 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
331 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
332
332
333 def pathto(root, n1, n2):
333 def pathto(root, n1, n2):
334 '''return the relative path from one place to another.
334 '''return the relative path from one place to another.
335 root should use os.sep to separate directories
335 root should use os.sep to separate directories
336 n1 should use os.sep to separate directories
336 n1 should use os.sep to separate directories
337 n2 should use "/" to separate directories
337 n2 should use "/" to separate directories
338 returns an os.sep-separated path.
338 returns an os.sep-separated path.
339
339
340 If n1 is a relative path, it's assumed it's
340 If n1 is a relative path, it's assumed it's
341 relative to root.
341 relative to root.
342 n2 should always be relative to root.
342 n2 should always be relative to root.
343 '''
343 '''
344 if not n1: return localpath(n2)
344 if not n1: return localpath(n2)
345 if os.path.isabs(n1):
345 if os.path.isabs(n1):
346 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
346 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
347 return os.path.join(root, localpath(n2))
347 return os.path.join(root, localpath(n2))
348 n2 = '/'.join((pconvert(root), n2))
348 n2 = '/'.join((pconvert(root), n2))
349 a, b = n1.split(os.sep), n2.split('/')
349 a, b = n1.split(os.sep), n2.split('/')
350 a.reverse()
350 a.reverse()
351 b.reverse()
351 b.reverse()
352 while a and b and a[-1] == b[-1]:
352 while a and b and a[-1] == b[-1]:
353 a.pop()
353 a.pop()
354 b.pop()
354 b.pop()
355 b.reverse()
355 b.reverse()
356 return os.sep.join((['..'] * len(a)) + b)
356 return os.sep.join((['..'] * len(a)) + b)
357
357
358 def canonpath(root, cwd, myname):
358 def canonpath(root, cwd, myname):
359 """return the canonical path of myname, given cwd and root"""
359 """return the canonical path of myname, given cwd and root"""
360 if root == os.sep:
360 if root == os.sep:
361 rootsep = os.sep
361 rootsep = os.sep
362 elif root.endswith(os.sep):
362 elif root.endswith(os.sep):
363 rootsep = root
363 rootsep = root
364 else:
364 else:
365 rootsep = root + os.sep
365 rootsep = root + os.sep
366 name = myname
366 name = myname
367 if not os.path.isabs(name):
367 if not os.path.isabs(name):
368 name = os.path.join(root, cwd, name)
368 name = os.path.join(root, cwd, name)
369 name = os.path.normpath(name)
369 name = os.path.normpath(name)
370 audit_path = path_auditor(root)
370 audit_path = path_auditor(root)
371 if name != rootsep and name.startswith(rootsep):
371 if name != rootsep and name.startswith(rootsep):
372 name = name[len(rootsep):]
372 name = name[len(rootsep):]
373 audit_path(name)
373 audit_path(name)
374 return pconvert(name)
374 return pconvert(name)
375 elif name == root:
375 elif name == root:
376 return ''
376 return ''
377 else:
377 else:
378 # Determine whether `name' is in the hierarchy at or beneath `root',
378 # Determine whether `name' is in the hierarchy at or beneath `root',
379 # by iterating name=dirname(name) until that causes no change (can't
379 # by iterating name=dirname(name) until that causes no change (can't
380 # check name == '/', because that doesn't work on windows). For each
380 # check name == '/', because that doesn't work on windows). For each
381 # `name', compare dev/inode numbers. If they match, the list `rel'
381 # `name', compare dev/inode numbers. If they match, the list `rel'
382 # holds the reversed list of components making up the relative file
382 # holds the reversed list of components making up the relative file
383 # name we want.
383 # name we want.
384 root_st = os.stat(root)
384 root_st = os.stat(root)
385 rel = []
385 rel = []
386 while True:
386 while True:
387 try:
387 try:
388 name_st = os.stat(name)
388 name_st = os.stat(name)
389 except OSError:
389 except OSError:
390 break
390 break
391 if samestat(name_st, root_st):
391 if samestat(name_st, root_st):
392 if not rel:
392 if not rel:
393 # name was actually the same as root (maybe a symlink)
393 # name was actually the same as root (maybe a symlink)
394 return ''
394 return ''
395 rel.reverse()
395 rel.reverse()
396 name = os.path.join(*rel)
396 name = os.path.join(*rel)
397 audit_path(name)
397 audit_path(name)
398 return pconvert(name)
398 return pconvert(name)
399 dirname, basename = os.path.split(name)
399 dirname, basename = os.path.split(name)
400 rel.append(basename)
400 rel.append(basename)
401 if dirname == name:
401 if dirname == name:
402 break
402 break
403 name = dirname
403 name = dirname
404
404
405 raise Abort('%s not under root' % myname)
405 raise Abort('%s not under root' % myname)
406
406
407 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
407 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
408 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
408 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
409
409
410 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
410 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
411 globbed=False, default=None):
411 globbed=False, default=None):
412 default = default or 'relpath'
412 default = default or 'relpath'
413 if default == 'relpath' and not globbed:
413 if default == 'relpath' and not globbed:
414 names = expand_glob(names)
414 names = expand_glob(names)
415 return _matcher(canonroot, cwd, names, inc, exc, default, src)
415 return _matcher(canonroot, cwd, names, inc, exc, default, src)
416
416
417 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
417 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
418 """build a function to match a set of file patterns
418 """build a function to match a set of file patterns
419
419
420 arguments:
420 arguments:
421 canonroot - the canonical root of the tree you're matching against
421 canonroot - the canonical root of the tree you're matching against
422 cwd - the current working directory, if relevant
422 cwd - the current working directory, if relevant
423 names - patterns to find
423 names - patterns to find
424 inc - patterns to include
424 inc - patterns to include
425 exc - patterns to exclude
425 exc - patterns to exclude
426 dflt_pat - if a pattern in names has no explicit type, assume this one
426 dflt_pat - if a pattern in names has no explicit type, assume this one
427 src - where these patterns came from (e.g. .hgignore)
427 src - where these patterns came from (e.g. .hgignore)
428
428
429 a pattern is one of:
429 a pattern is one of:
430 'glob:<glob>' - a glob relative to cwd
430 'glob:<glob>' - a glob relative to cwd
431 're:<regexp>' - a regular expression
431 're:<regexp>' - a regular expression
432 'path:<path>' - a path relative to canonroot
432 'path:<path>' - a path relative to canonroot
433 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
433 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
434 'relpath:<path>' - a path relative to cwd
434 'relpath:<path>' - a path relative to cwd
435 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
435 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
436 '<something>' - one of the cases above, selected by the dflt_pat argument
436 '<something>' - one of the cases above, selected by the dflt_pat argument
437
437
438 returns:
438 returns:
439 a 3-tuple containing
439 a 3-tuple containing
440 - list of roots (places where one should start a recursive walk of the fs);
440 - list of roots (places where one should start a recursive walk of the fs);
441 this often matches the explicit non-pattern names passed in, but also
441 this often matches the explicit non-pattern names passed in, but also
442 includes the initial part of glob: patterns that has no glob characters
442 includes the initial part of glob: patterns that has no glob characters
443 - a bool match(filename) function
443 - a bool match(filename) function
444 - a bool indicating if any patterns were passed in
444 - a bool indicating if any patterns were passed in
445 """
445 """
446
446
447 # a common case: no patterns at all
447 # a common case: no patterns at all
448 if not names and not inc and not exc:
448 if not names and not inc and not exc:
449 return [], always, False
449 return [], always, False
450
450
451 def contains_glob(name):
451 def contains_glob(name):
452 for c in name:
452 for c in name:
453 if c in _globchars: return True
453 if c in _globchars: return True
454 return False
454 return False
455
455
456 def regex(kind, name, tail):
456 def regex(kind, name, tail):
457 '''convert a pattern into a regular expression'''
457 '''convert a pattern into a regular expression'''
458 if not name:
458 if not name:
459 return ''
459 return ''
460 if kind == 're':
460 if kind == 're':
461 return name
461 return name
462 elif kind == 'path':
462 elif kind == 'path':
463 return '^' + re.escape(name) + '(?:/|$)'
463 return '^' + re.escape(name) + '(?:/|$)'
464 elif kind == 'relglob':
464 elif kind == 'relglob':
465 return globre(name, '(?:|.*/)', tail)
465 return globre(name, '(?:|.*/)', tail)
466 elif kind == 'relpath':
466 elif kind == 'relpath':
467 return re.escape(name) + '(?:/|$)'
467 return re.escape(name) + '(?:/|$)'
468 elif kind == 'relre':
468 elif kind == 'relre':
469 if name.startswith('^'):
469 if name.startswith('^'):
470 return name
470 return name
471 return '.*' + name
471 return '.*' + name
472 return globre(name, '', tail)
472 return globre(name, '', tail)
473
473
474 def matchfn(pats, tail):
474 def matchfn(pats, tail):
475 """build a matching function from a set of patterns"""
475 """build a matching function from a set of patterns"""
476 if not pats:
476 if not pats:
477 return
477 return
478 try:
478 try:
479 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
479 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
480 return re.compile(pat).match
480 return re.compile(pat).match
481 except OverflowError:
481 except OverflowError:
482 # We're using a Python with a tiny regex engine and we
482 # We're using a Python with a tiny regex engine and we
483 # made it explode, so we'll divide the pattern list in two
483 # made it explode, so we'll divide the pattern list in two
484 # until it works
484 # until it works
485 l = len(pats)
485 l = len(pats)
486 if l < 2:
486 if l < 2:
487 raise
487 raise
488 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
488 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
489 return lambda s: a(s) or b(s)
489 return lambda s: a(s) or b(s)
490 except re.error:
490 except re.error:
491 for k, p in pats:
491 for k, p in pats:
492 try:
492 try:
493 re.compile('(?:%s)' % regex(k, p, tail))
493 re.compile('(?:%s)' % regex(k, p, tail))
494 except re.error:
494 except re.error:
495 if src:
495 if src:
496 raise Abort("%s: invalid pattern (%s): %s" %
496 raise Abort("%s: invalid pattern (%s): %s" %
497 (src, k, p))
497 (src, k, p))
498 else:
498 else:
499 raise Abort("invalid pattern (%s): %s" % (k, p))
499 raise Abort("invalid pattern (%s): %s" % (k, p))
500 raise Abort("invalid pattern")
500 raise Abort("invalid pattern")
501
501
502 def globprefix(pat):
502 def globprefix(pat):
503 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
503 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
504 root = []
504 root = []
505 for p in pat.split('/'):
505 for p in pat.split('/'):
506 if contains_glob(p): break
506 if contains_glob(p): break
507 root.append(p)
507 root.append(p)
508 return '/'.join(root) or '.'
508 return '/'.join(root) or '.'
509
509
510 def normalizepats(names, default):
510 def normalizepats(names, default):
511 pats = []
511 pats = []
512 roots = []
512 roots = []
513 anypats = False
513 anypats = False
514 for kind, name in [patkind(p, default) for p in names]:
514 for kind, name in [patkind(p, default) for p in names]:
515 if kind in ('glob', 'relpath'):
515 if kind in ('glob', 'relpath'):
516 name = canonpath(canonroot, cwd, name)
516 name = canonpath(canonroot, cwd, name)
517 elif kind in ('relglob', 'path'):
517 elif kind in ('relglob', 'path'):
518 name = normpath(name)
518 name = normpath(name)
519
519
520 pats.append((kind, name))
520 pats.append((kind, name))
521
521
522 if kind in ('glob', 're', 'relglob', 'relre'):
522 if kind in ('glob', 're', 'relglob', 'relre'):
523 anypats = True
523 anypats = True
524
524
525 if kind == 'glob':
525 if kind == 'glob':
526 root = globprefix(name)
526 root = globprefix(name)
527 roots.append(root)
527 roots.append(root)
528 elif kind in ('relpath', 'path'):
528 elif kind in ('relpath', 'path'):
529 roots.append(name or '.')
529 roots.append(name or '.')
530 elif kind == 'relglob':
530 elif kind == 'relglob':
531 roots.append('.')
531 roots.append('.')
532 return roots, pats, anypats
532 return roots, pats, anypats
533
533
534 roots, pats, anypats = normalizepats(names, dflt_pat)
534 roots, pats, anypats = normalizepats(names, dflt_pat)
535
535
536 patmatch = matchfn(pats, '$') or always
536 patmatch = matchfn(pats, '$') or always
537 incmatch = always
537 incmatch = always
538 if inc:
538 if inc:
539 dummy, inckinds, dummy = normalizepats(inc, 'glob')
539 dummy, inckinds, dummy = normalizepats(inc, 'glob')
540 incmatch = matchfn(inckinds, '(?:/|$)')
540 incmatch = matchfn(inckinds, '(?:/|$)')
541 excmatch = lambda fn: False
541 excmatch = lambda fn: False
542 if exc:
542 if exc:
543 dummy, exckinds, dummy = normalizepats(exc, 'glob')
543 dummy, exckinds, dummy = normalizepats(exc, 'glob')
544 excmatch = matchfn(exckinds, '(?:/|$)')
544 excmatch = matchfn(exckinds, '(?:/|$)')
545
545
546 if not names and inc and not exc:
546 if not names and inc and not exc:
547 # common case: hgignore patterns
547 # common case: hgignore patterns
548 match = incmatch
548 match = incmatch
549 else:
549 else:
550 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
550 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
551
551
552 return (roots, match, (inc or exc or anypats) and True)
552 return (roots, match, (inc or exc or anypats) and True)
553
553
554 _hgexecutable = None
554 _hgexecutable = None
555
555
556 def hgexecutable():
556 def hgexecutable():
557 """return location of the 'hg' executable.
557 """return location of the 'hg' executable.
558
558
559 Defaults to $HG or 'hg' in the search path.
559 Defaults to $HG or 'hg' in the search path.
560 """
560 """
561 if _hgexecutable is None:
561 if _hgexecutable is None:
562 set_hgexecutable(os.environ.get('HG') or find_exe('hg', 'hg'))
562 set_hgexecutable(os.environ.get('HG') or find_exe('hg', 'hg'))
563 return _hgexecutable
563 return _hgexecutable
564
564
565 def set_hgexecutable(path):
565 def set_hgexecutable(path):
566 """set location of the 'hg' executable"""
566 """set location of the 'hg' executable"""
567 global _hgexecutable
567 global _hgexecutable
568 _hgexecutable = path
568 _hgexecutable = path
569
569
570 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
570 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
571 '''enhanced shell command execution.
571 '''enhanced shell command execution.
572 run with environment maybe modified, maybe in different dir.
572 run with environment maybe modified, maybe in different dir.
573
573
574 if command fails and onerr is None, return status. if ui object,
574 if command fails and onerr is None, return status. if ui object,
575 print error message and return status, else raise onerr object as
575 print error message and return status, else raise onerr object as
576 exception.'''
576 exception.'''
577 def py2shell(val):
577 def py2shell(val):
578 'convert python object into string that is useful to shell'
578 'convert python object into string that is useful to shell'
579 if val in (None, False):
579 if val in (None, False):
580 return '0'
580 return '0'
581 if val == True:
581 if val == True:
582 return '1'
582 return '1'
583 return str(val)
583 return str(val)
584 oldenv = {}
584 oldenv = {}
585 for k in environ:
585 for k in environ:
586 oldenv[k] = os.environ.get(k)
586 oldenv[k] = os.environ.get(k)
587 if cwd is not None:
587 if cwd is not None:
588 oldcwd = os.getcwd()
588 oldcwd = os.getcwd()
589 origcmd = cmd
589 origcmd = cmd
590 if os.name == 'nt':
590 if os.name == 'nt':
591 cmd = '"%s"' % cmd
591 cmd = '"%s"' % cmd
592 try:
592 try:
593 for k, v in environ.iteritems():
593 for k, v in environ.iteritems():
594 os.environ[k] = py2shell(v)
594 os.environ[k] = py2shell(v)
595 os.environ['HG'] = hgexecutable()
595 os.environ['HG'] = hgexecutable()
596 if cwd is not None and oldcwd != cwd:
596 if cwd is not None and oldcwd != cwd:
597 os.chdir(cwd)
597 os.chdir(cwd)
598 rc = os.system(cmd)
598 rc = os.system(cmd)
599 if sys.platform == 'OpenVMS' and rc & 1:
599 if sys.platform == 'OpenVMS' and rc & 1:
600 rc = 0
600 rc = 0
601 if rc and onerr:
601 if rc and onerr:
602 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
602 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
603 explain_exit(rc)[0])
603 explain_exit(rc)[0])
604 if errprefix:
604 if errprefix:
605 errmsg = '%s: %s' % (errprefix, errmsg)
605 errmsg = '%s: %s' % (errprefix, errmsg)
606 try:
606 try:
607 onerr.warn(errmsg + '\n')
607 onerr.warn(errmsg + '\n')
608 except AttributeError:
608 except AttributeError:
609 raise onerr(errmsg)
609 raise onerr(errmsg)
610 return rc
610 return rc
611 finally:
611 finally:
612 for k, v in oldenv.iteritems():
612 for k, v in oldenv.iteritems():
613 if v is None:
613 if v is None:
614 del os.environ[k]
614 del os.environ[k]
615 else:
615 else:
616 os.environ[k] = v
616 os.environ[k] = v
617 if cwd is not None and oldcwd != cwd:
617 if cwd is not None and oldcwd != cwd:
618 os.chdir(oldcwd)
618 os.chdir(oldcwd)
619
619
620 # os.path.lexists is not available on python2.3
620 # os.path.lexists is not available on python2.3
621 def lexists(filename):
621 def lexists(filename):
622 "test whether a file with this name exists. does not follow symlinks"
622 "test whether a file with this name exists. does not follow symlinks"
623 try:
623 try:
624 os.lstat(filename)
624 os.lstat(filename)
625 except:
625 except:
626 return False
626 return False
627 return True
627 return True
628
628
629 def rename(src, dst):
629 def rename(src, dst):
630 """forcibly rename a file"""
630 """forcibly rename a file"""
631 try:
631 try:
632 os.rename(src, dst)
632 os.rename(src, dst)
633 except OSError, err: # FIXME: check err (EEXIST ?)
633 except OSError, err: # FIXME: check err (EEXIST ?)
634 # on windows, rename to existing file is not allowed, so we
634 # on windows, rename to existing file is not allowed, so we
635 # must delete destination first. but if file is open, unlink
635 # must delete destination first. but if file is open, unlink
636 # schedules it for delete but does not delete it. rename
636 # schedules it for delete but does not delete it. rename
637 # happens immediately even for open files, so we create
637 # happens immediately even for open files, so we create
638 # temporary file, delete it, rename destination to that name,
638 # temporary file, delete it, rename destination to that name,
639 # then delete that. then rename is safe to do.
639 # then delete that. then rename is safe to do.
640 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
640 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
641 os.close(fd)
641 os.close(fd)
642 os.unlink(temp)
642 os.unlink(temp)
643 os.rename(dst, temp)
643 os.rename(dst, temp)
644 os.unlink(temp)
644 os.unlink(temp)
645 os.rename(src, dst)
645 os.rename(src, dst)
646
646
647 def unlink(f):
647 def unlink(f):
648 """unlink and remove the directory if it is empty"""
648 """unlink and remove the directory if it is empty"""
649 os.unlink(f)
649 os.unlink(f)
650 # try removing directories that might now be empty
650 # try removing directories that might now be empty
651 try:
651 try:
652 os.removedirs(os.path.dirname(f))
652 os.removedirs(os.path.dirname(f))
653 except OSError:
653 except OSError:
654 pass
654 pass
655
655
656 def copyfile(src, dest):
656 def copyfile(src, dest):
657 "copy a file, preserving mode"
657 "copy a file, preserving mode"
658 if os.path.islink(src):
658 if os.path.islink(src):
659 try:
659 try:
660 os.unlink(dest)
660 os.unlink(dest)
661 except:
661 except:
662 pass
662 pass
663 os.symlink(os.readlink(src), dest)
663 os.symlink(os.readlink(src), dest)
664 else:
664 else:
665 try:
665 try:
666 shutil.copyfile(src, dest)
666 shutil.copyfile(src, dest)
667 shutil.copymode(src, dest)
667 shutil.copymode(src, dest)
668 except shutil.Error, inst:
668 except shutil.Error, inst:
669 raise Abort(str(inst))
669 raise Abort(str(inst))
670
670
671 def copyfiles(src, dst, hardlink=None):
671 def copyfiles(src, dst, hardlink=None):
672 """Copy a directory tree using hardlinks if possible"""
672 """Copy a directory tree using hardlinks if possible"""
673
673
674 if hardlink is None:
674 if hardlink is None:
675 hardlink = (os.stat(src).st_dev ==
675 hardlink = (os.stat(src).st_dev ==
676 os.stat(os.path.dirname(dst)).st_dev)
676 os.stat(os.path.dirname(dst)).st_dev)
677
677
678 if os.path.isdir(src):
678 if os.path.isdir(src):
679 os.mkdir(dst)
679 os.mkdir(dst)
680 for name, kind in osutil.listdir(src):
680 for name, kind in osutil.listdir(src):
681 srcname = os.path.join(src, name)
681 srcname = os.path.join(src, name)
682 dstname = os.path.join(dst, name)
682 dstname = os.path.join(dst, name)
683 copyfiles(srcname, dstname, hardlink)
683 copyfiles(srcname, dstname, hardlink)
684 else:
684 else:
685 if hardlink:
685 if hardlink:
686 try:
686 try:
687 os_link(src, dst)
687 os_link(src, dst)
688 except (IOError, OSError):
688 except (IOError, OSError):
689 hardlink = False
689 hardlink = False
690 shutil.copy(src, dst)
690 shutil.copy(src, dst)
691 else:
691 else:
692 shutil.copy(src, dst)
692 shutil.copy(src, dst)
693
693
694 class path_auditor(object):
694 class path_auditor(object):
695 '''ensure that a filesystem path contains no banned components.
695 '''ensure that a filesystem path contains no banned components.
696 the following properties of a path are checked:
696 the following properties of a path are checked:
697
697
698 - under top-level .hg
698 - under top-level .hg
699 - starts at the root of a windows drive
699 - starts at the root of a windows drive
700 - contains ".."
700 - contains ".."
701 - traverses a symlink (e.g. a/symlink_here/b)
701 - traverses a symlink (e.g. a/symlink_here/b)
702 - inside a nested repository'''
702 - inside a nested repository'''
703
703
704 def __init__(self, root):
704 def __init__(self, root):
705 self.audited = set()
705 self.audited = set()
706 self.auditeddir = set()
706 self.auditeddir = set()
707 self.root = root
707 self.root = root
708
708
709 def __call__(self, path):
709 def __call__(self, path):
710 if path in self.audited:
710 if path in self.audited:
711 return
711 return
712 normpath = os.path.normcase(path)
712 normpath = os.path.normcase(path)
713 parts = normpath.split(os.sep)
713 parts = normpath.split(os.sep)
714 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
714 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
715 or os.pardir in parts):
715 or os.pardir in parts):
716 raise Abort(_("path contains illegal component: %s") % path)
716 raise Abort(_("path contains illegal component: %s") % path)
717 def check(prefix):
717 def check(prefix):
718 curpath = os.path.join(self.root, prefix)
718 curpath = os.path.join(self.root, prefix)
719 try:
719 try:
720 st = os.lstat(curpath)
720 st = os.lstat(curpath)
721 except OSError, err:
721 except OSError, err:
722 # EINVAL can be raised as invalid path syntax under win32.
722 # EINVAL can be raised as invalid path syntax under win32.
723 # They must be ignored for patterns can be checked too.
723 # They must be ignored for patterns can be checked too.
724 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
724 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
725 raise
725 raise
726 else:
726 else:
727 if stat.S_ISLNK(st.st_mode):
727 if stat.S_ISLNK(st.st_mode):
728 raise Abort(_('path %r traverses symbolic link %r') %
728 raise Abort(_('path %r traverses symbolic link %r') %
729 (path, prefix))
729 (path, prefix))
730 elif (stat.S_ISDIR(st.st_mode) and
730 elif (stat.S_ISDIR(st.st_mode) and
731 os.path.isdir(os.path.join(curpath, '.hg'))):
731 os.path.isdir(os.path.join(curpath, '.hg'))):
732 raise Abort(_('path %r is inside repo %r') %
732 raise Abort(_('path %r is inside repo %r') %
733 (path, prefix))
733 (path, prefix))
734
734
735 prefixes = []
735 prefixes = []
736 for c in strutil.rfindall(normpath, os.sep):
736 for c in strutil.rfindall(normpath, os.sep):
737 prefix = normpath[:c]
737 prefix = normpath[:c]
738 if prefix in self.auditeddir:
738 if prefix in self.auditeddir:
739 break
739 break
740 check(prefix)
740 check(prefix)
741 prefixes.append(prefix)
741 prefixes.append(prefix)
742
742
743 self.audited.add(path)
743 self.audited.add(path)
744 # only add prefixes to the cache after checking everything: we don't
744 # only add prefixes to the cache after checking everything: we don't
745 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
745 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
746 self.auditeddir.update(prefixes)
746 self.auditeddir.update(prefixes)
747
747
748 def _makelock_file(info, pathname):
748 def _makelock_file(info, pathname):
749 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
749 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
750 os.write(ld, info)
750 os.write(ld, info)
751 os.close(ld)
751 os.close(ld)
752
752
753 def _readlock_file(pathname):
753 def _readlock_file(pathname):
754 return posixfile(pathname).read()
754 return posixfile(pathname).read()
755
755
756 def nlinks(pathname):
756 def nlinks(pathname):
757 """Return number of hardlinks for the given file."""
757 """Return number of hardlinks for the given file."""
758 return os.lstat(pathname).st_nlink
758 return os.lstat(pathname).st_nlink
759
759
760 if hasattr(os, 'link'):
760 if hasattr(os, 'link'):
761 os_link = os.link
761 os_link = os.link
762 else:
762 else:
763 def os_link(src, dst):
763 def os_link(src, dst):
764 raise OSError(0, _("Hardlinks not supported"))
764 raise OSError(0, _("Hardlinks not supported"))
765
765
766 def fstat(fp):
766 def fstat(fp):
767 '''stat file object that may not have fileno method.'''
767 '''stat file object that may not have fileno method.'''
768 try:
768 try:
769 return os.fstat(fp.fileno())
769 return os.fstat(fp.fileno())
770 except AttributeError:
770 except AttributeError:
771 return os.stat(fp.name)
771 return os.stat(fp.name)
772
772
773 posixfile = file
773 posixfile = file
774
774
775 def is_win_9x():
775 def openhardlinks():
776 '''return true if run on windows 95, 98 or me.'''
776 '''return true if it is safe to hold open file handles to hardlinks'''
777 try:
777 return True
778 return sys.getwindowsversion()[3] == 1
779 except AttributeError:
780 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
781
778
782 getuser_fallback = None
779 getuser_fallback = None
783
780
784 def getuser():
781 def getuser():
785 '''return name of current user'''
782 '''return name of current user'''
786 try:
783 try:
787 return getpass.getuser()
784 return getpass.getuser()
788 except ImportError:
785 except ImportError:
789 # import of pwd will fail on windows - try fallback
786 # import of pwd will fail on windows - try fallback
790 if getuser_fallback:
787 if getuser_fallback:
791 return getuser_fallback()
788 return getuser_fallback()
792 # raised if win32api not available
789 # raised if win32api not available
793 raise Abort(_('user name not available - set USERNAME '
790 raise Abort(_('user name not available - set USERNAME '
794 'environment variable'))
791 'environment variable'))
795
792
796 def username(uid=None):
793 def username(uid=None):
797 """Return the name of the user with the given uid.
794 """Return the name of the user with the given uid.
798
795
799 If uid is None, return the name of the current user."""
796 If uid is None, return the name of the current user."""
800 try:
797 try:
801 import pwd
798 import pwd
802 if uid is None:
799 if uid is None:
803 uid = os.getuid()
800 uid = os.getuid()
804 try:
801 try:
805 return pwd.getpwuid(uid)[0]
802 return pwd.getpwuid(uid)[0]
806 except KeyError:
803 except KeyError:
807 return str(uid)
804 return str(uid)
808 except ImportError:
805 except ImportError:
809 return None
806 return None
810
807
811 def groupname(gid=None):
808 def groupname(gid=None):
812 """Return the name of the group with the given gid.
809 """Return the name of the group with the given gid.
813
810
814 If gid is None, return the name of the current group."""
811 If gid is None, return the name of the current group."""
815 try:
812 try:
816 import grp
813 import grp
817 if gid is None:
814 if gid is None:
818 gid = os.getgid()
815 gid = os.getgid()
819 try:
816 try:
820 return grp.getgrgid(gid)[0]
817 return grp.getgrgid(gid)[0]
821 except KeyError:
818 except KeyError:
822 return str(gid)
819 return str(gid)
823 except ImportError:
820 except ImportError:
824 return None
821 return None
825
822
826 # File system features
823 # File system features
827
824
828 def checkfolding(path):
825 def checkfolding(path):
829 """
826 """
830 Check whether the given path is on a case-sensitive filesystem
827 Check whether the given path is on a case-sensitive filesystem
831
828
832 Requires a path (like /foo/.hg) ending with a foldable final
829 Requires a path (like /foo/.hg) ending with a foldable final
833 directory component.
830 directory component.
834 """
831 """
835 s1 = os.stat(path)
832 s1 = os.stat(path)
836 d, b = os.path.split(path)
833 d, b = os.path.split(path)
837 p2 = os.path.join(d, b.upper())
834 p2 = os.path.join(d, b.upper())
838 if path == p2:
835 if path == p2:
839 p2 = os.path.join(d, b.lower())
836 p2 = os.path.join(d, b.lower())
840 try:
837 try:
841 s2 = os.stat(p2)
838 s2 = os.stat(p2)
842 if s2 == s1:
839 if s2 == s1:
843 return False
840 return False
844 return True
841 return True
845 except:
842 except:
846 return True
843 return True
847
844
848 def checkexec(path):
845 def checkexec(path):
849 """
846 """
850 Check whether the given path is on a filesystem with UNIX-like exec flags
847 Check whether the given path is on a filesystem with UNIX-like exec flags
851
848
852 Requires a directory (like /foo/.hg)
849 Requires a directory (like /foo/.hg)
853 """
850 """
854 try:
851 try:
855 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
852 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
856 fh, fn = tempfile.mkstemp("", "", path)
853 fh, fn = tempfile.mkstemp("", "", path)
857 os.close(fh)
854 os.close(fh)
858 m = os.stat(fn).st_mode
855 m = os.stat(fn).st_mode
859 # VFAT on Linux can flip mode but it doesn't persist a FS remount.
856 # VFAT on Linux can flip mode but it doesn't persist a FS remount.
860 # frequently we can detect it if files are created with exec bit on.
857 # frequently we can detect it if files are created with exec bit on.
861 new_file_has_exec = m & EXECFLAGS
858 new_file_has_exec = m & EXECFLAGS
862 os.chmod(fn, m ^ EXECFLAGS)
859 os.chmod(fn, m ^ EXECFLAGS)
863 exec_flags_cannot_flip = (os.stat(fn).st_mode == m)
860 exec_flags_cannot_flip = (os.stat(fn).st_mode == m)
864 os.unlink(fn)
861 os.unlink(fn)
865 except (IOError,OSError):
862 except (IOError,OSError):
866 # we don't care, the user probably won't be able to commit anyway
863 # we don't care, the user probably won't be able to commit anyway
867 return False
864 return False
868 return not (new_file_has_exec or exec_flags_cannot_flip)
865 return not (new_file_has_exec or exec_flags_cannot_flip)
869
866
870 def execfunc(path, fallback):
867 def execfunc(path, fallback):
871 '''return an is_exec() function with default to fallback'''
868 '''return an is_exec() function with default to fallback'''
872 if checkexec(path):
869 if checkexec(path):
873 return lambda x: is_exec(os.path.join(path, x))
870 return lambda x: is_exec(os.path.join(path, x))
874 return fallback
871 return fallback
875
872
876 def checklink(path):
873 def checklink(path):
877 """check whether the given path is on a symlink-capable filesystem"""
874 """check whether the given path is on a symlink-capable filesystem"""
878 # mktemp is not racy because symlink creation will fail if the
875 # mktemp is not racy because symlink creation will fail if the
879 # file already exists
876 # file already exists
880 name = tempfile.mktemp(dir=path)
877 name = tempfile.mktemp(dir=path)
881 try:
878 try:
882 os.symlink(".", name)
879 os.symlink(".", name)
883 os.unlink(name)
880 os.unlink(name)
884 return True
881 return True
885 except (OSError, AttributeError):
882 except (OSError, AttributeError):
886 return False
883 return False
887
884
888 def linkfunc(path, fallback):
885 def linkfunc(path, fallback):
889 '''return an is_link() function with default to fallback'''
886 '''return an is_link() function with default to fallback'''
890 if checklink(path):
887 if checklink(path):
891 return lambda x: os.path.islink(os.path.join(path, x))
888 return lambda x: os.path.islink(os.path.join(path, x))
892 return fallback
889 return fallback
893
890
894 _umask = os.umask(0)
891 _umask = os.umask(0)
895 os.umask(_umask)
892 os.umask(_umask)
896
893
897 def needbinarypatch():
894 def needbinarypatch():
898 """return True if patches should be applied in binary mode by default."""
895 """return True if patches should be applied in binary mode by default."""
899 return os.name == 'nt'
896 return os.name == 'nt'
900
897
901 # Platform specific variants
898 # Platform specific variants
902 if os.name == 'nt':
899 if os.name == 'nt':
903 import msvcrt
900 import msvcrt
904 nulldev = 'NUL:'
901 nulldev = 'NUL:'
905
902
906 class winstdout:
903 class winstdout:
907 '''stdout on windows misbehaves if sent through a pipe'''
904 '''stdout on windows misbehaves if sent through a pipe'''
908
905
909 def __init__(self, fp):
906 def __init__(self, fp):
910 self.fp = fp
907 self.fp = fp
911
908
912 def __getattr__(self, key):
909 def __getattr__(self, key):
913 return getattr(self.fp, key)
910 return getattr(self.fp, key)
914
911
915 def close(self):
912 def close(self):
916 try:
913 try:
917 self.fp.close()
914 self.fp.close()
918 except: pass
915 except: pass
919
916
920 def write(self, s):
917 def write(self, s):
921 try:
918 try:
922 # This is workaround for "Not enough space" error on
919 # This is workaround for "Not enough space" error on
923 # writing large size of data to console.
920 # writing large size of data to console.
924 limit = 16000
921 limit = 16000
925 l = len(s)
922 l = len(s)
926 start = 0
923 start = 0
927 while start < l:
924 while start < l:
928 end = start + limit
925 end = start + limit
929 self.fp.write(s[start:end])
926 self.fp.write(s[start:end])
930 start = end
927 start = end
931 except IOError, inst:
928 except IOError, inst:
932 if inst.errno != 0: raise
929 if inst.errno != 0: raise
933 self.close()
930 self.close()
934 raise IOError(errno.EPIPE, 'Broken pipe')
931 raise IOError(errno.EPIPE, 'Broken pipe')
935
932
936 def flush(self):
933 def flush(self):
937 try:
934 try:
938 return self.fp.flush()
935 return self.fp.flush()
939 except IOError, inst:
936 except IOError, inst:
940 if inst.errno != errno.EINVAL: raise
937 if inst.errno != errno.EINVAL: raise
941 self.close()
938 self.close()
942 raise IOError(errno.EPIPE, 'Broken pipe')
939 raise IOError(errno.EPIPE, 'Broken pipe')
943
940
944 sys.stdout = winstdout(sys.stdout)
941 sys.stdout = winstdout(sys.stdout)
945
942
943 def _is_win_9x():
944 '''return true if run on windows 95, 98 or me.'''
945 try:
946 return sys.getwindowsversion()[3] == 1
947 except AttributeError:
948 return 'command' in os.environ.get('comspec', '')
949
950 def openhardlinks():
951 return not _is_win_9x and "win32api" in locals()
952
946 def system_rcpath():
953 def system_rcpath():
947 try:
954 try:
948 return system_rcpath_win32()
955 return system_rcpath_win32()
949 except:
956 except:
950 return [r'c:\mercurial\mercurial.ini']
957 return [r'c:\mercurial\mercurial.ini']
951
958
952 def user_rcpath():
959 def user_rcpath():
953 '''return os-specific hgrc search path to the user dir'''
960 '''return os-specific hgrc search path to the user dir'''
954 try:
961 try:
955 userrc = user_rcpath_win32()
962 userrc = user_rcpath_win32()
956 except:
963 except:
957 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
964 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
958 path = [userrc]
965 path = [userrc]
959 userprofile = os.environ.get('USERPROFILE')
966 userprofile = os.environ.get('USERPROFILE')
960 if userprofile:
967 if userprofile:
961 path.append(os.path.join(userprofile, 'mercurial.ini'))
968 path.append(os.path.join(userprofile, 'mercurial.ini'))
962 return path
969 return path
963
970
964 def parse_patch_output(output_line):
971 def parse_patch_output(output_line):
965 """parses the output produced by patch and returns the file name"""
972 """parses the output produced by patch and returns the file name"""
966 pf = output_line[14:]
973 pf = output_line[14:]
967 if pf[0] == '`':
974 if pf[0] == '`':
968 pf = pf[1:-1] # Remove the quotes
975 pf = pf[1:-1] # Remove the quotes
969 return pf
976 return pf
970
977
971 def sshargs(sshcmd, host, user, port):
978 def sshargs(sshcmd, host, user, port):
972 '''Build argument list for ssh or Plink'''
979 '''Build argument list for ssh or Plink'''
973 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
980 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
974 args = user and ("%s@%s" % (user, host)) or host
981 args = user and ("%s@%s" % (user, host)) or host
975 return port and ("%s %s %s" % (args, pflag, port)) or args
982 return port and ("%s %s %s" % (args, pflag, port)) or args
976
983
977 def testpid(pid):
984 def testpid(pid):
978 '''return False if pid dead, True if running or not known'''
985 '''return False if pid dead, True if running or not known'''
979 return True
986 return True
980
987
981 def set_exec(f, mode):
988 def set_exec(f, mode):
982 pass
989 pass
983
990
984 def set_link(f, mode):
991 def set_link(f, mode):
985 pass
992 pass
986
993
987 def set_binary(fd):
994 def set_binary(fd):
988 msvcrt.setmode(fd.fileno(), os.O_BINARY)
995 msvcrt.setmode(fd.fileno(), os.O_BINARY)
989
996
990 def pconvert(path):
997 def pconvert(path):
991 return path.replace("\\", "/")
998 return path.replace("\\", "/")
992
999
993 def localpath(path):
1000 def localpath(path):
994 return path.replace('/', '\\')
1001 return path.replace('/', '\\')
995
1002
996 def normpath(path):
1003 def normpath(path):
997 return pconvert(os.path.normpath(path))
1004 return pconvert(os.path.normpath(path))
998
1005
999 makelock = _makelock_file
1006 makelock = _makelock_file
1000 readlock = _readlock_file
1007 readlock = _readlock_file
1001
1008
1002 def samestat(s1, s2):
1009 def samestat(s1, s2):
1003 return False
1010 return False
1004
1011
1005 # A sequence of backslashes is special iff it precedes a double quote:
1012 # A sequence of backslashes is special iff it precedes a double quote:
1006 # - if there's an even number of backslashes, the double quote is not
1013 # - if there's an even number of backslashes, the double quote is not
1007 # quoted (i.e. it ends the quoted region)
1014 # quoted (i.e. it ends the quoted region)
1008 # - if there's an odd number of backslashes, the double quote is quoted
1015 # - if there's an odd number of backslashes, the double quote is quoted
1009 # - in both cases, every pair of backslashes is unquoted into a single
1016 # - in both cases, every pair of backslashes is unquoted into a single
1010 # backslash
1017 # backslash
1011 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1018 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1012 # So, to quote a string, we must surround it in double quotes, double
1019 # So, to quote a string, we must surround it in double quotes, double
1013 # the number of backslashes that preceed double quotes and add another
1020 # the number of backslashes that preceed double quotes and add another
1014 # backslash before every double quote (being careful with the double
1021 # backslash before every double quote (being careful with the double
1015 # quote we've appended to the end)
1022 # quote we've appended to the end)
1016 _quotere = None
1023 _quotere = None
1017 def shellquote(s):
1024 def shellquote(s):
1018 global _quotere
1025 global _quotere
1019 if _quotere is None:
1026 if _quotere is None:
1020 _quotere = re.compile(r'(\\*)("|\\$)')
1027 _quotere = re.compile(r'(\\*)("|\\$)')
1021 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1028 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1022
1029
1023 def quotecommand(cmd):
1030 def quotecommand(cmd):
1024 """Build a command string suitable for os.popen* calls."""
1031 """Build a command string suitable for os.popen* calls."""
1025 # The extra quotes are needed because popen* runs the command
1032 # The extra quotes are needed because popen* runs the command
1026 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1033 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1027 return '"' + cmd + '"'
1034 return '"' + cmd + '"'
1028
1035
1029 def popen(command):
1036 def popen(command):
1030 # Work around "popen spawned process may not write to stdout
1037 # Work around "popen spawned process may not write to stdout
1031 # under windows"
1038 # under windows"
1032 # http://bugs.python.org/issue1366
1039 # http://bugs.python.org/issue1366
1033 command += " 2> %s" % nulldev
1040 command += " 2> %s" % nulldev
1034 return os.popen(quotecommand(command))
1041 return os.popen(quotecommand(command))
1035
1042
1036 def explain_exit(code):
1043 def explain_exit(code):
1037 return _("exited with status %d") % code, code
1044 return _("exited with status %d") % code, code
1038
1045
1039 # if you change this stub into a real check, please try to implement the
1046 # if you change this stub into a real check, please try to implement the
1040 # username and groupname functions above, too.
1047 # username and groupname functions above, too.
1041 def isowner(fp, st=None):
1048 def isowner(fp, st=None):
1042 return True
1049 return True
1043
1050
1044 def find_in_path(name, path, default=None):
1051 def find_in_path(name, path, default=None):
1045 '''find name in search path. path can be string (will be split
1052 '''find name in search path. path can be string (will be split
1046 with os.pathsep), or iterable thing that returns strings. if name
1053 with os.pathsep), or iterable thing that returns strings. if name
1047 found, return path to name. else return default. name is looked up
1054 found, return path to name. else return default. name is looked up
1048 using cmd.exe rules, using PATHEXT.'''
1055 using cmd.exe rules, using PATHEXT.'''
1049 if isinstance(path, str):
1056 if isinstance(path, str):
1050 path = path.split(os.pathsep)
1057 path = path.split(os.pathsep)
1051
1058
1052 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1059 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1053 pathext = pathext.lower().split(os.pathsep)
1060 pathext = pathext.lower().split(os.pathsep)
1054 isexec = os.path.splitext(name)[1].lower() in pathext
1061 isexec = os.path.splitext(name)[1].lower() in pathext
1055
1062
1056 for p in path:
1063 for p in path:
1057 p_name = os.path.join(p, name)
1064 p_name = os.path.join(p, name)
1058
1065
1059 if isexec and os.path.exists(p_name):
1066 if isexec and os.path.exists(p_name):
1060 return p_name
1067 return p_name
1061
1068
1062 for ext in pathext:
1069 for ext in pathext:
1063 p_name_ext = p_name + ext
1070 p_name_ext = p_name + ext
1064 if os.path.exists(p_name_ext):
1071 if os.path.exists(p_name_ext):
1065 return p_name_ext
1072 return p_name_ext
1066 return default
1073 return default
1067
1074
1068 def set_signal_handler():
1075 def set_signal_handler():
1069 try:
1076 try:
1070 set_signal_handler_win32()
1077 set_signal_handler_win32()
1071 except NameError:
1078 except NameError:
1072 pass
1079 pass
1073
1080
1074 try:
1081 try:
1075 # override functions with win32 versions if possible
1082 # override functions with win32 versions if possible
1076 from util_win32 import *
1083 from util_win32 import *
1077 if not is_win_9x():
1084 if not _is_win_9x():
1078 posixfile = posixfile_nt
1085 posixfile = posixfile_nt
1079 except ImportError:
1086 except ImportError:
1080 pass
1087 pass
1081
1088
1082 else:
1089 else:
1083 nulldev = '/dev/null'
1090 nulldev = '/dev/null'
1084
1091
1085 def rcfiles(path):
1092 def rcfiles(path):
1086 rcs = [os.path.join(path, 'hgrc')]
1093 rcs = [os.path.join(path, 'hgrc')]
1087 rcdir = os.path.join(path, 'hgrc.d')
1094 rcdir = os.path.join(path, 'hgrc.d')
1088 try:
1095 try:
1089 rcs.extend([os.path.join(rcdir, f)
1096 rcs.extend([os.path.join(rcdir, f)
1090 for f, kind in osutil.listdir(rcdir)
1097 for f, kind in osutil.listdir(rcdir)
1091 if f.endswith(".rc")])
1098 if f.endswith(".rc")])
1092 except OSError:
1099 except OSError:
1093 pass
1100 pass
1094 return rcs
1101 return rcs
1095
1102
1096 def system_rcpath():
1103 def system_rcpath():
1097 path = []
1104 path = []
1098 # old mod_python does not set sys.argv
1105 # old mod_python does not set sys.argv
1099 if len(getattr(sys, 'argv', [])) > 0:
1106 if len(getattr(sys, 'argv', [])) > 0:
1100 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1107 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1101 '/../etc/mercurial'))
1108 '/../etc/mercurial'))
1102 path.extend(rcfiles('/etc/mercurial'))
1109 path.extend(rcfiles('/etc/mercurial'))
1103 return path
1110 return path
1104
1111
1105 def user_rcpath():
1112 def user_rcpath():
1106 return [os.path.expanduser('~/.hgrc')]
1113 return [os.path.expanduser('~/.hgrc')]
1107
1114
1108 def parse_patch_output(output_line):
1115 def parse_patch_output(output_line):
1109 """parses the output produced by patch and returns the file name"""
1116 """parses the output produced by patch and returns the file name"""
1110 pf = output_line[14:]
1117 pf = output_line[14:]
1111 if os.sys.platform == 'OpenVMS':
1118 if os.sys.platform == 'OpenVMS':
1112 if pf[0] == '`':
1119 if pf[0] == '`':
1113 pf = pf[1:-1] # Remove the quotes
1120 pf = pf[1:-1] # Remove the quotes
1114 else:
1121 else:
1115 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1122 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1116 pf = pf[1:-1] # Remove the quotes
1123 pf = pf[1:-1] # Remove the quotes
1117 return pf
1124 return pf
1118
1125
1119 def sshargs(sshcmd, host, user, port):
1126 def sshargs(sshcmd, host, user, port):
1120 '''Build argument list for ssh'''
1127 '''Build argument list for ssh'''
1121 args = user and ("%s@%s" % (user, host)) or host
1128 args = user and ("%s@%s" % (user, host)) or host
1122 return port and ("%s -p %s" % (args, port)) or args
1129 return port and ("%s -p %s" % (args, port)) or args
1123
1130
1124 def is_exec(f):
1131 def is_exec(f):
1125 """check whether a file is executable"""
1132 """check whether a file is executable"""
1126 return (os.lstat(f).st_mode & 0100 != 0)
1133 return (os.lstat(f).st_mode & 0100 != 0)
1127
1134
1128 def set_exec(f, mode):
1135 def set_exec(f, mode):
1129 s = os.lstat(f).st_mode
1136 s = os.lstat(f).st_mode
1130 if stat.S_ISLNK(s) or (s & 0100 != 0) == mode:
1137 if stat.S_ISLNK(s) or (s & 0100 != 0) == mode:
1131 return
1138 return
1132 if mode:
1139 if mode:
1133 # Turn on +x for every +r bit when making a file executable
1140 # Turn on +x for every +r bit when making a file executable
1134 # and obey umask.
1141 # and obey umask.
1135 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1142 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1136 else:
1143 else:
1137 os.chmod(f, s & 0666)
1144 os.chmod(f, s & 0666)
1138
1145
1139 def set_link(f, mode):
1146 def set_link(f, mode):
1140 """make a file a symbolic link/regular file
1147 """make a file a symbolic link/regular file
1141
1148
1142 if a file is changed to a link, its contents become the link data
1149 if a file is changed to a link, its contents become the link data
1143 if a link is changed to a file, its link data become its contents
1150 if a link is changed to a file, its link data become its contents
1144 """
1151 """
1145
1152
1146 m = os.path.islink(f)
1153 m = os.path.islink(f)
1147 if m == bool(mode):
1154 if m == bool(mode):
1148 return
1155 return
1149
1156
1150 if mode: # switch file to link
1157 if mode: # switch file to link
1151 data = file(f).read()
1158 data = file(f).read()
1152 os.unlink(f)
1159 os.unlink(f)
1153 os.symlink(data, f)
1160 os.symlink(data, f)
1154 else:
1161 else:
1155 data = os.readlink(f)
1162 data = os.readlink(f)
1156 os.unlink(f)
1163 os.unlink(f)
1157 file(f, "w").write(data)
1164 file(f, "w").write(data)
1158
1165
1159 def set_binary(fd):
1166 def set_binary(fd):
1160 pass
1167 pass
1161
1168
1162 def pconvert(path):
1169 def pconvert(path):
1163 return path
1170 return path
1164
1171
1165 def localpath(path):
1172 def localpath(path):
1166 return path
1173 return path
1167
1174
1168 normpath = os.path.normpath
1175 normpath = os.path.normpath
1169 samestat = os.path.samestat
1176 samestat = os.path.samestat
1170
1177
1171 def makelock(info, pathname):
1178 def makelock(info, pathname):
1172 try:
1179 try:
1173 os.symlink(info, pathname)
1180 os.symlink(info, pathname)
1174 except OSError, why:
1181 except OSError, why:
1175 if why.errno == errno.EEXIST:
1182 if why.errno == errno.EEXIST:
1176 raise
1183 raise
1177 else:
1184 else:
1178 _makelock_file(info, pathname)
1185 _makelock_file(info, pathname)
1179
1186
1180 def readlock(pathname):
1187 def readlock(pathname):
1181 try:
1188 try:
1182 return os.readlink(pathname)
1189 return os.readlink(pathname)
1183 except OSError, why:
1190 except OSError, why:
1184 if why.errno in (errno.EINVAL, errno.ENOSYS):
1191 if why.errno in (errno.EINVAL, errno.ENOSYS):
1185 return _readlock_file(pathname)
1192 return _readlock_file(pathname)
1186 else:
1193 else:
1187 raise
1194 raise
1188
1195
1189 def shellquote(s):
1196 def shellquote(s):
1190 if os.sys.platform == 'OpenVMS':
1197 if os.sys.platform == 'OpenVMS':
1191 return '"%s"' % s
1198 return '"%s"' % s
1192 else:
1199 else:
1193 return "'%s'" % s.replace("'", "'\\''")
1200 return "'%s'" % s.replace("'", "'\\''")
1194
1201
1195 def quotecommand(cmd):
1202 def quotecommand(cmd):
1196 return cmd
1203 return cmd
1197
1204
1198 def popen(command):
1205 def popen(command):
1199 return os.popen(command)
1206 return os.popen(command)
1200
1207
1201 def testpid(pid):
1208 def testpid(pid):
1202 '''return False if pid dead, True if running or not sure'''
1209 '''return False if pid dead, True if running or not sure'''
1203 if os.sys.platform == 'OpenVMS':
1210 if os.sys.platform == 'OpenVMS':
1204 return True
1211 return True
1205 try:
1212 try:
1206 os.kill(pid, 0)
1213 os.kill(pid, 0)
1207 return True
1214 return True
1208 except OSError, inst:
1215 except OSError, inst:
1209 return inst.errno != errno.ESRCH
1216 return inst.errno != errno.ESRCH
1210
1217
1211 def explain_exit(code):
1218 def explain_exit(code):
1212 """return a 2-tuple (desc, code) describing a process's status"""
1219 """return a 2-tuple (desc, code) describing a process's status"""
1213 if os.WIFEXITED(code):
1220 if os.WIFEXITED(code):
1214 val = os.WEXITSTATUS(code)
1221 val = os.WEXITSTATUS(code)
1215 return _("exited with status %d") % val, val
1222 return _("exited with status %d") % val, val
1216 elif os.WIFSIGNALED(code):
1223 elif os.WIFSIGNALED(code):
1217 val = os.WTERMSIG(code)
1224 val = os.WTERMSIG(code)
1218 return _("killed by signal %d") % val, val
1225 return _("killed by signal %d") % val, val
1219 elif os.WIFSTOPPED(code):
1226 elif os.WIFSTOPPED(code):
1220 val = os.WSTOPSIG(code)
1227 val = os.WSTOPSIG(code)
1221 return _("stopped by signal %d") % val, val
1228 return _("stopped by signal %d") % val, val
1222 raise ValueError(_("invalid exit code"))
1229 raise ValueError(_("invalid exit code"))
1223
1230
1224 def isowner(fp, st=None):
1231 def isowner(fp, st=None):
1225 """Return True if the file object f belongs to the current user.
1232 """Return True if the file object f belongs to the current user.
1226
1233
1227 The return value of a util.fstat(f) may be passed as the st argument.
1234 The return value of a util.fstat(f) may be passed as the st argument.
1228 """
1235 """
1229 if st is None:
1236 if st is None:
1230 st = fstat(fp)
1237 st = fstat(fp)
1231 return st.st_uid == os.getuid()
1238 return st.st_uid == os.getuid()
1232
1239
1233 def find_in_path(name, path, default=None):
1240 def find_in_path(name, path, default=None):
1234 '''find name in search path. path can be string (will be split
1241 '''find name in search path. path can be string (will be split
1235 with os.pathsep), or iterable thing that returns strings. if name
1242 with os.pathsep), or iterable thing that returns strings. if name
1236 found, return path to name. else return default.'''
1243 found, return path to name. else return default.'''
1237 if isinstance(path, str):
1244 if isinstance(path, str):
1238 path = path.split(os.pathsep)
1245 path = path.split(os.pathsep)
1239 for p in path:
1246 for p in path:
1240 p_name = os.path.join(p, name)
1247 p_name = os.path.join(p, name)
1241 if os.path.exists(p_name):
1248 if os.path.exists(p_name):
1242 return p_name
1249 return p_name
1243 return default
1250 return default
1244
1251
1245 def set_signal_handler():
1252 def set_signal_handler():
1246 pass
1253 pass
1247
1254
1248 def find_exe(name, default=None):
1255 def find_exe(name, default=None):
1249 '''find path of an executable.
1256 '''find path of an executable.
1250 if name contains a path component, return it as is. otherwise,
1257 if name contains a path component, return it as is. otherwise,
1251 use normal executable search path.'''
1258 use normal executable search path.'''
1252
1259
1253 if os.sep in name or sys.platform == 'OpenVMS':
1260 if os.sep in name or sys.platform == 'OpenVMS':
1254 # don't check the executable bit. if the file isn't
1261 # don't check the executable bit. if the file isn't
1255 # executable, whoever tries to actually run it will give a
1262 # executable, whoever tries to actually run it will give a
1256 # much more useful error message.
1263 # much more useful error message.
1257 return name
1264 return name
1258 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1265 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1259
1266
1260 def _buildencodefun():
1267 def _buildencodefun():
1261 e = '_'
1268 e = '_'
1262 win_reserved = [ord(x) for x in '\\:*?"<>|']
1269 win_reserved = [ord(x) for x in '\\:*?"<>|']
1263 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1270 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1264 for x in (range(32) + range(126, 256) + win_reserved):
1271 for x in (range(32) + range(126, 256) + win_reserved):
1265 cmap[chr(x)] = "~%02x" % x
1272 cmap[chr(x)] = "~%02x" % x
1266 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1273 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1267 cmap[chr(x)] = e + chr(x).lower()
1274 cmap[chr(x)] = e + chr(x).lower()
1268 dmap = {}
1275 dmap = {}
1269 for k, v in cmap.iteritems():
1276 for k, v in cmap.iteritems():
1270 dmap[v] = k
1277 dmap[v] = k
1271 def decode(s):
1278 def decode(s):
1272 i = 0
1279 i = 0
1273 while i < len(s):
1280 while i < len(s):
1274 for l in xrange(1, 4):
1281 for l in xrange(1, 4):
1275 try:
1282 try:
1276 yield dmap[s[i:i+l]]
1283 yield dmap[s[i:i+l]]
1277 i += l
1284 i += l
1278 break
1285 break
1279 except KeyError:
1286 except KeyError:
1280 pass
1287 pass
1281 else:
1288 else:
1282 raise KeyError
1289 raise KeyError
1283 return (lambda s: "".join([cmap[c] for c in s]),
1290 return (lambda s: "".join([cmap[c] for c in s]),
1284 lambda s: "".join(list(decode(s))))
1291 lambda s: "".join(list(decode(s))))
1285
1292
1286 encodefilename, decodefilename = _buildencodefun()
1293 encodefilename, decodefilename = _buildencodefun()
1287
1294
1288 def encodedopener(openerfn, fn):
1295 def encodedopener(openerfn, fn):
1289 def o(path, *args, **kw):
1296 def o(path, *args, **kw):
1290 return openerfn(fn(path), *args, **kw)
1297 return openerfn(fn(path), *args, **kw)
1291 return o
1298 return o
1292
1299
1293 def mktempcopy(name, emptyok=False):
1300 def mktempcopy(name, emptyok=False):
1294 """Create a temporary file with the same contents from name
1301 """Create a temporary file with the same contents from name
1295
1302
1296 The permission bits are copied from the original file.
1303 The permission bits are copied from the original file.
1297
1304
1298 If the temporary file is going to be truncated immediately, you
1305 If the temporary file is going to be truncated immediately, you
1299 can use emptyok=True as an optimization.
1306 can use emptyok=True as an optimization.
1300
1307
1301 Returns the name of the temporary file.
1308 Returns the name of the temporary file.
1302 """
1309 """
1303 d, fn = os.path.split(name)
1310 d, fn = os.path.split(name)
1304 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1311 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1305 os.close(fd)
1312 os.close(fd)
1306 # Temporary files are created with mode 0600, which is usually not
1313 # Temporary files are created with mode 0600, which is usually not
1307 # what we want. If the original file already exists, just copy
1314 # what we want. If the original file already exists, just copy
1308 # its mode. Otherwise, manually obey umask.
1315 # its mode. Otherwise, manually obey umask.
1309 try:
1316 try:
1310 st_mode = os.lstat(name).st_mode
1317 st_mode = os.lstat(name).st_mode
1311 except OSError, inst:
1318 except OSError, inst:
1312 if inst.errno != errno.ENOENT:
1319 if inst.errno != errno.ENOENT:
1313 raise
1320 raise
1314 st_mode = 0666 & ~_umask
1321 st_mode = 0666 & ~_umask
1315 os.chmod(temp, st_mode)
1322 os.chmod(temp, st_mode)
1316 if emptyok:
1323 if emptyok:
1317 return temp
1324 return temp
1318 try:
1325 try:
1319 try:
1326 try:
1320 ifp = posixfile(name, "rb")
1327 ifp = posixfile(name, "rb")
1321 except IOError, inst:
1328 except IOError, inst:
1322 if inst.errno == errno.ENOENT:
1329 if inst.errno == errno.ENOENT:
1323 return temp
1330 return temp
1324 if not getattr(inst, 'filename', None):
1331 if not getattr(inst, 'filename', None):
1325 inst.filename = name
1332 inst.filename = name
1326 raise
1333 raise
1327 ofp = posixfile(temp, "wb")
1334 ofp = posixfile(temp, "wb")
1328 for chunk in filechunkiter(ifp):
1335 for chunk in filechunkiter(ifp):
1329 ofp.write(chunk)
1336 ofp.write(chunk)
1330 ifp.close()
1337 ifp.close()
1331 ofp.close()
1338 ofp.close()
1332 except:
1339 except:
1333 try: os.unlink(temp)
1340 try: os.unlink(temp)
1334 except: pass
1341 except: pass
1335 raise
1342 raise
1336 return temp
1343 return temp
1337
1344
1338 class atomictempfile(posixfile):
1345 class atomictempfile(posixfile):
1339 """file-like object that atomically updates a file
1346 """file-like object that atomically updates a file
1340
1347
1341 All writes will be redirected to a temporary copy of the original
1348 All writes will be redirected to a temporary copy of the original
1342 file. When rename is called, the copy is renamed to the original
1349 file. When rename is called, the copy is renamed to the original
1343 name, making the changes visible.
1350 name, making the changes visible.
1344 """
1351 """
1345 def __init__(self, name, mode):
1352 def __init__(self, name, mode):
1346 self.__name = name
1353 self.__name = name
1347 self.temp = mktempcopy(name, emptyok=('w' in mode))
1354 self.temp = mktempcopy(name, emptyok=('w' in mode))
1348 posixfile.__init__(self, self.temp, mode)
1355 posixfile.__init__(self, self.temp, mode)
1349
1356
1350 def rename(self):
1357 def rename(self):
1351 if not self.closed:
1358 if not self.closed:
1352 posixfile.close(self)
1359 posixfile.close(self)
1353 rename(self.temp, localpath(self.__name))
1360 rename(self.temp, localpath(self.__name))
1354
1361
1355 def __del__(self):
1362 def __del__(self):
1356 if not self.closed:
1363 if not self.closed:
1357 try:
1364 try:
1358 os.unlink(self.temp)
1365 os.unlink(self.temp)
1359 except: pass
1366 except: pass
1360 posixfile.close(self)
1367 posixfile.close(self)
1361
1368
1362 class opener(object):
1369 class opener(object):
1363 """Open files relative to a base directory
1370 """Open files relative to a base directory
1364
1371
1365 This class is used to hide the details of COW semantics and
1372 This class is used to hide the details of COW semantics and
1366 remote file access from higher level code.
1373 remote file access from higher level code.
1367 """
1374 """
1368 def __init__(self, base, audit=True):
1375 def __init__(self, base, audit=True):
1369 self.base = base
1376 self.base = base
1370 if audit:
1377 if audit:
1371 self.audit_path = path_auditor(base)
1378 self.audit_path = path_auditor(base)
1372 else:
1379 else:
1373 self.audit_path = always
1380 self.audit_path = always
1374
1381
1375 def __getattr__(self, name):
1382 def __getattr__(self, name):
1376 if name == '_can_symlink':
1383 if name == '_can_symlink':
1377 self._can_symlink = checklink(self.base)
1384 self._can_symlink = checklink(self.base)
1378 return self._can_symlink
1385 return self._can_symlink
1379 raise AttributeError(name)
1386 raise AttributeError(name)
1380
1387
1381 def __call__(self, path, mode="r", text=False, atomictemp=False):
1388 def __call__(self, path, mode="r", text=False, atomictemp=False):
1382 self.audit_path(path)
1389 self.audit_path(path)
1383 f = os.path.join(self.base, path)
1390 f = os.path.join(self.base, path)
1384
1391
1385 if not text and "b" not in mode:
1392 if not text and "b" not in mode:
1386 mode += "b" # for that other OS
1393 mode += "b" # for that other OS
1387
1394
1388 if mode[0] != "r":
1395 if mode[0] != "r":
1389 try:
1396 try:
1390 nlink = nlinks(f)
1397 nlink = nlinks(f)
1391 except OSError:
1398 except OSError:
1392 nlink = 0
1399 nlink = 0
1393 d = os.path.dirname(f)
1400 d = os.path.dirname(f)
1394 if not os.path.isdir(d):
1401 if not os.path.isdir(d):
1395 os.makedirs(d)
1402 os.makedirs(d)
1396 if atomictemp:
1403 if atomictemp:
1397 return atomictempfile(f, mode)
1404 return atomictempfile(f, mode)
1398 if nlink > 1:
1405 if nlink > 1:
1399 rename(mktempcopy(f), f)
1406 rename(mktempcopy(f), f)
1400 return posixfile(f, mode)
1407 return posixfile(f, mode)
1401
1408
1402 def symlink(self, src, dst):
1409 def symlink(self, src, dst):
1403 self.audit_path(dst)
1410 self.audit_path(dst)
1404 linkname = os.path.join(self.base, dst)
1411 linkname = os.path.join(self.base, dst)
1405 try:
1412 try:
1406 os.unlink(linkname)
1413 os.unlink(linkname)
1407 except OSError:
1414 except OSError:
1408 pass
1415 pass
1409
1416
1410 dirname = os.path.dirname(linkname)
1417 dirname = os.path.dirname(linkname)
1411 if not os.path.exists(dirname):
1418 if not os.path.exists(dirname):
1412 os.makedirs(dirname)
1419 os.makedirs(dirname)
1413
1420
1414 if self._can_symlink:
1421 if self._can_symlink:
1415 try:
1422 try:
1416 os.symlink(src, linkname)
1423 os.symlink(src, linkname)
1417 except OSError, err:
1424 except OSError, err:
1418 raise OSError(err.errno, _('could not symlink to %r: %s') %
1425 raise OSError(err.errno, _('could not symlink to %r: %s') %
1419 (src, err.strerror), linkname)
1426 (src, err.strerror), linkname)
1420 else:
1427 else:
1421 f = self(dst, "w")
1428 f = self(dst, "w")
1422 f.write(src)
1429 f.write(src)
1423 f.close()
1430 f.close()
1424
1431
1425 class chunkbuffer(object):
1432 class chunkbuffer(object):
1426 """Allow arbitrary sized chunks of data to be efficiently read from an
1433 """Allow arbitrary sized chunks of data to be efficiently read from an
1427 iterator over chunks of arbitrary size."""
1434 iterator over chunks of arbitrary size."""
1428
1435
1429 def __init__(self, in_iter):
1436 def __init__(self, in_iter):
1430 """in_iter is the iterator that's iterating over the input chunks.
1437 """in_iter is the iterator that's iterating over the input chunks.
1431 targetsize is how big a buffer to try to maintain."""
1438 targetsize is how big a buffer to try to maintain."""
1432 self.iter = iter(in_iter)
1439 self.iter = iter(in_iter)
1433 self.buf = ''
1440 self.buf = ''
1434 self.targetsize = 2**16
1441 self.targetsize = 2**16
1435
1442
1436 def read(self, l):
1443 def read(self, l):
1437 """Read L bytes of data from the iterator of chunks of data.
1444 """Read L bytes of data from the iterator of chunks of data.
1438 Returns less than L bytes if the iterator runs dry."""
1445 Returns less than L bytes if the iterator runs dry."""
1439 if l > len(self.buf) and self.iter:
1446 if l > len(self.buf) and self.iter:
1440 # Clamp to a multiple of self.targetsize
1447 # Clamp to a multiple of self.targetsize
1441 targetsize = max(l, self.targetsize)
1448 targetsize = max(l, self.targetsize)
1442 collector = cStringIO.StringIO()
1449 collector = cStringIO.StringIO()
1443 collector.write(self.buf)
1450 collector.write(self.buf)
1444 collected = len(self.buf)
1451 collected = len(self.buf)
1445 for chunk in self.iter:
1452 for chunk in self.iter:
1446 collector.write(chunk)
1453 collector.write(chunk)
1447 collected += len(chunk)
1454 collected += len(chunk)
1448 if collected >= targetsize:
1455 if collected >= targetsize:
1449 break
1456 break
1450 if collected < targetsize:
1457 if collected < targetsize:
1451 self.iter = False
1458 self.iter = False
1452 self.buf = collector.getvalue()
1459 self.buf = collector.getvalue()
1453 if len(self.buf) == l:
1460 if len(self.buf) == l:
1454 s, self.buf = str(self.buf), ''
1461 s, self.buf = str(self.buf), ''
1455 else:
1462 else:
1456 s, self.buf = self.buf[:l], buffer(self.buf, l)
1463 s, self.buf = self.buf[:l], buffer(self.buf, l)
1457 return s
1464 return s
1458
1465
1459 def filechunkiter(f, size=65536, limit=None):
1466 def filechunkiter(f, size=65536, limit=None):
1460 """Create a generator that produces the data in the file size
1467 """Create a generator that produces the data in the file size
1461 (default 65536) bytes at a time, up to optional limit (default is
1468 (default 65536) bytes at a time, up to optional limit (default is
1462 to read all data). Chunks may be less than size bytes if the
1469 to read all data). Chunks may be less than size bytes if the
1463 chunk is the last chunk in the file, or the file is a socket or
1470 chunk is the last chunk in the file, or the file is a socket or
1464 some other type of file that sometimes reads less data than is
1471 some other type of file that sometimes reads less data than is
1465 requested."""
1472 requested."""
1466 assert size >= 0
1473 assert size >= 0
1467 assert limit is None or limit >= 0
1474 assert limit is None or limit >= 0
1468 while True:
1475 while True:
1469 if limit is None: nbytes = size
1476 if limit is None: nbytes = size
1470 else: nbytes = min(limit, size)
1477 else: nbytes = min(limit, size)
1471 s = nbytes and f.read(nbytes)
1478 s = nbytes and f.read(nbytes)
1472 if not s: break
1479 if not s: break
1473 if limit: limit -= len(s)
1480 if limit: limit -= len(s)
1474 yield s
1481 yield s
1475
1482
1476 def makedate():
1483 def makedate():
1477 lt = time.localtime()
1484 lt = time.localtime()
1478 if lt[8] == 1 and time.daylight:
1485 if lt[8] == 1 and time.daylight:
1479 tz = time.altzone
1486 tz = time.altzone
1480 else:
1487 else:
1481 tz = time.timezone
1488 tz = time.timezone
1482 return time.mktime(lt), tz
1489 return time.mktime(lt), tz
1483
1490
1484 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True, timezone_format=" %+03d%02d"):
1491 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True, timezone_format=" %+03d%02d"):
1485 """represent a (unixtime, offset) tuple as a localized time.
1492 """represent a (unixtime, offset) tuple as a localized time.
1486 unixtime is seconds since the epoch, and offset is the time zone's
1493 unixtime is seconds since the epoch, and offset is the time zone's
1487 number of seconds away from UTC. if timezone is false, do not
1494 number of seconds away from UTC. if timezone is false, do not
1488 append time zone to string."""
1495 append time zone to string."""
1489 t, tz = date or makedate()
1496 t, tz = date or makedate()
1490 s = time.strftime(format, time.gmtime(float(t) - tz))
1497 s = time.strftime(format, time.gmtime(float(t) - tz))
1491 if timezone:
1498 if timezone:
1492 s += timezone_format % (-tz / 3600, ((-tz % 3600) / 60))
1499 s += timezone_format % (-tz / 3600, ((-tz % 3600) / 60))
1493 return s
1500 return s
1494
1501
1495 def strdate(string, format, defaults=[]):
1502 def strdate(string, format, defaults=[]):
1496 """parse a localized time string and return a (unixtime, offset) tuple.
1503 """parse a localized time string and return a (unixtime, offset) tuple.
1497 if the string cannot be parsed, ValueError is raised."""
1504 if the string cannot be parsed, ValueError is raised."""
1498 def timezone(string):
1505 def timezone(string):
1499 tz = string.split()[-1]
1506 tz = string.split()[-1]
1500 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1507 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1501 tz = int(tz)
1508 tz = int(tz)
1502 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1509 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1503 return offset
1510 return offset
1504 if tz == "GMT" or tz == "UTC":
1511 if tz == "GMT" or tz == "UTC":
1505 return 0
1512 return 0
1506 return None
1513 return None
1507
1514
1508 # NOTE: unixtime = localunixtime + offset
1515 # NOTE: unixtime = localunixtime + offset
1509 offset, date = timezone(string), string
1516 offset, date = timezone(string), string
1510 if offset != None:
1517 if offset != None:
1511 date = " ".join(string.split()[:-1])
1518 date = " ".join(string.split()[:-1])
1512
1519
1513 # add missing elements from defaults
1520 # add missing elements from defaults
1514 for part in defaults:
1521 for part in defaults:
1515 found = [True for p in part if ("%"+p) in format]
1522 found = [True for p in part if ("%"+p) in format]
1516 if not found:
1523 if not found:
1517 date += "@" + defaults[part]
1524 date += "@" + defaults[part]
1518 format += "@%" + part[0]
1525 format += "@%" + part[0]
1519
1526
1520 timetuple = time.strptime(date, format)
1527 timetuple = time.strptime(date, format)
1521 localunixtime = int(calendar.timegm(timetuple))
1528 localunixtime = int(calendar.timegm(timetuple))
1522 if offset is None:
1529 if offset is None:
1523 # local timezone
1530 # local timezone
1524 unixtime = int(time.mktime(timetuple))
1531 unixtime = int(time.mktime(timetuple))
1525 offset = unixtime - localunixtime
1532 offset = unixtime - localunixtime
1526 else:
1533 else:
1527 unixtime = localunixtime + offset
1534 unixtime = localunixtime + offset
1528 return unixtime, offset
1535 return unixtime, offset
1529
1536
1530 def parsedate(string, formats=None, defaults=None):
1537 def parsedate(string, formats=None, defaults=None):
1531 """parse a localized time string and return a (unixtime, offset) tuple.
1538 """parse a localized time string and return a (unixtime, offset) tuple.
1532 The date may be a "unixtime offset" string or in one of the specified
1539 The date may be a "unixtime offset" string or in one of the specified
1533 formats."""
1540 formats."""
1534 if not string:
1541 if not string:
1535 return 0, 0
1542 return 0, 0
1536 if not formats:
1543 if not formats:
1537 formats = defaultdateformats
1544 formats = defaultdateformats
1538 string = string.strip()
1545 string = string.strip()
1539 try:
1546 try:
1540 when, offset = map(int, string.split(' '))
1547 when, offset = map(int, string.split(' '))
1541 except ValueError:
1548 except ValueError:
1542 # fill out defaults
1549 # fill out defaults
1543 if not defaults:
1550 if not defaults:
1544 defaults = {}
1551 defaults = {}
1545 now = makedate()
1552 now = makedate()
1546 for part in "d mb yY HI M S".split():
1553 for part in "d mb yY HI M S".split():
1547 if part not in defaults:
1554 if part not in defaults:
1548 if part[0] in "HMS":
1555 if part[0] in "HMS":
1549 defaults[part] = "00"
1556 defaults[part] = "00"
1550 elif part[0] in "dm":
1557 elif part[0] in "dm":
1551 defaults[part] = "1"
1558 defaults[part] = "1"
1552 else:
1559 else:
1553 defaults[part] = datestr(now, "%" + part[0], False)
1560 defaults[part] = datestr(now, "%" + part[0], False)
1554
1561
1555 for format in formats:
1562 for format in formats:
1556 try:
1563 try:
1557 when, offset = strdate(string, format, defaults)
1564 when, offset = strdate(string, format, defaults)
1558 except ValueError:
1565 except ValueError:
1559 pass
1566 pass
1560 else:
1567 else:
1561 break
1568 break
1562 else:
1569 else:
1563 raise Abort(_('invalid date: %r ') % string)
1570 raise Abort(_('invalid date: %r ') % string)
1564 # validate explicit (probably user-specified) date and
1571 # validate explicit (probably user-specified) date and
1565 # time zone offset. values must fit in signed 32 bits for
1572 # time zone offset. values must fit in signed 32 bits for
1566 # current 32-bit linux runtimes. timezones go from UTC-12
1573 # current 32-bit linux runtimes. timezones go from UTC-12
1567 # to UTC+14
1574 # to UTC+14
1568 if abs(when) > 0x7fffffff:
1575 if abs(when) > 0x7fffffff:
1569 raise Abort(_('date exceeds 32 bits: %d') % when)
1576 raise Abort(_('date exceeds 32 bits: %d') % when)
1570 if offset < -50400 or offset > 43200:
1577 if offset < -50400 or offset > 43200:
1571 raise Abort(_('impossible time zone offset: %d') % offset)
1578 raise Abort(_('impossible time zone offset: %d') % offset)
1572 return when, offset
1579 return when, offset
1573
1580
1574 def matchdate(date):
1581 def matchdate(date):
1575 """Return a function that matches a given date match specifier
1582 """Return a function that matches a given date match specifier
1576
1583
1577 Formats include:
1584 Formats include:
1578
1585
1579 '{date}' match a given date to the accuracy provided
1586 '{date}' match a given date to the accuracy provided
1580
1587
1581 '<{date}' on or before a given date
1588 '<{date}' on or before a given date
1582
1589
1583 '>{date}' on or after a given date
1590 '>{date}' on or after a given date
1584
1591
1585 """
1592 """
1586
1593
1587 def lower(date):
1594 def lower(date):
1588 return parsedate(date, extendeddateformats)[0]
1595 return parsedate(date, extendeddateformats)[0]
1589
1596
1590 def upper(date):
1597 def upper(date):
1591 d = dict(mb="12", HI="23", M="59", S="59")
1598 d = dict(mb="12", HI="23", M="59", S="59")
1592 for days in "31 30 29".split():
1599 for days in "31 30 29".split():
1593 try:
1600 try:
1594 d["d"] = days
1601 d["d"] = days
1595 return parsedate(date, extendeddateformats, d)[0]
1602 return parsedate(date, extendeddateformats, d)[0]
1596 except:
1603 except:
1597 pass
1604 pass
1598 d["d"] = "28"
1605 d["d"] = "28"
1599 return parsedate(date, extendeddateformats, d)[0]
1606 return parsedate(date, extendeddateformats, d)[0]
1600
1607
1601 if date[0] == "<":
1608 if date[0] == "<":
1602 when = upper(date[1:])
1609 when = upper(date[1:])
1603 return lambda x: x <= when
1610 return lambda x: x <= when
1604 elif date[0] == ">":
1611 elif date[0] == ">":
1605 when = lower(date[1:])
1612 when = lower(date[1:])
1606 return lambda x: x >= when
1613 return lambda x: x >= when
1607 elif date[0] == "-":
1614 elif date[0] == "-":
1608 try:
1615 try:
1609 days = int(date[1:])
1616 days = int(date[1:])
1610 except ValueError:
1617 except ValueError:
1611 raise Abort(_("invalid day spec: %s") % date[1:])
1618 raise Abort(_("invalid day spec: %s") % date[1:])
1612 when = makedate()[0] - days * 3600 * 24
1619 when = makedate()[0] - days * 3600 * 24
1613 return lambda x: x >= when
1620 return lambda x: x >= when
1614 elif " to " in date:
1621 elif " to " in date:
1615 a, b = date.split(" to ")
1622 a, b = date.split(" to ")
1616 start, stop = lower(a), upper(b)
1623 start, stop = lower(a), upper(b)
1617 return lambda x: x >= start and x <= stop
1624 return lambda x: x >= start and x <= stop
1618 else:
1625 else:
1619 start, stop = lower(date), upper(date)
1626 start, stop = lower(date), upper(date)
1620 return lambda x: x >= start and x <= stop
1627 return lambda x: x >= start and x <= stop
1621
1628
1622 def shortuser(user):
1629 def shortuser(user):
1623 """Return a short representation of a user name or email address."""
1630 """Return a short representation of a user name or email address."""
1624 f = user.find('@')
1631 f = user.find('@')
1625 if f >= 0:
1632 if f >= 0:
1626 user = user[:f]
1633 user = user[:f]
1627 f = user.find('<')
1634 f = user.find('<')
1628 if f >= 0:
1635 if f >= 0:
1629 user = user[f+1:]
1636 user = user[f+1:]
1630 f = user.find(' ')
1637 f = user.find(' ')
1631 if f >= 0:
1638 if f >= 0:
1632 user = user[:f]
1639 user = user[:f]
1633 f = user.find('.')
1640 f = user.find('.')
1634 if f >= 0:
1641 if f >= 0:
1635 user = user[:f]
1642 user = user[:f]
1636 return user
1643 return user
1637
1644
1638 def ellipsis(text, maxlength=400):
1645 def ellipsis(text, maxlength=400):
1639 """Trim string to at most maxlength (default: 400) characters."""
1646 """Trim string to at most maxlength (default: 400) characters."""
1640 if len(text) <= maxlength:
1647 if len(text) <= maxlength:
1641 return text
1648 return text
1642 else:
1649 else:
1643 return "%s..." % (text[:maxlength-3])
1650 return "%s..." % (text[:maxlength-3])
1644
1651
1645 def walkrepos(path):
1652 def walkrepos(path):
1646 '''yield every hg repository under path, recursively.'''
1653 '''yield every hg repository under path, recursively.'''
1647 def errhandler(err):
1654 def errhandler(err):
1648 if err.filename == path:
1655 if err.filename == path:
1649 raise err
1656 raise err
1650
1657
1651 for root, dirs, files in os.walk(path, onerror=errhandler):
1658 for root, dirs, files in os.walk(path, onerror=errhandler):
1652 for d in dirs:
1659 for d in dirs:
1653 if d == '.hg':
1660 if d == '.hg':
1654 yield root
1661 yield root
1655 dirs[:] = []
1662 dirs[:] = []
1656 break
1663 break
1657
1664
1658 _rcpath = None
1665 _rcpath = None
1659
1666
1660 def os_rcpath():
1667 def os_rcpath():
1661 '''return default os-specific hgrc search path'''
1668 '''return default os-specific hgrc search path'''
1662 path = system_rcpath()
1669 path = system_rcpath()
1663 path.extend(user_rcpath())
1670 path.extend(user_rcpath())
1664 path = [os.path.normpath(f) for f in path]
1671 path = [os.path.normpath(f) for f in path]
1665 return path
1672 return path
1666
1673
1667 def rcpath():
1674 def rcpath():
1668 '''return hgrc search path. if env var HGRCPATH is set, use it.
1675 '''return hgrc search path. if env var HGRCPATH is set, use it.
1669 for each item in path, if directory, use files ending in .rc,
1676 for each item in path, if directory, use files ending in .rc,
1670 else use item.
1677 else use item.
1671 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1678 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1672 if no HGRCPATH, use default os-specific path.'''
1679 if no HGRCPATH, use default os-specific path.'''
1673 global _rcpath
1680 global _rcpath
1674 if _rcpath is None:
1681 if _rcpath is None:
1675 if 'HGRCPATH' in os.environ:
1682 if 'HGRCPATH' in os.environ:
1676 _rcpath = []
1683 _rcpath = []
1677 for p in os.environ['HGRCPATH'].split(os.pathsep):
1684 for p in os.environ['HGRCPATH'].split(os.pathsep):
1678 if not p: continue
1685 if not p: continue
1679 if os.path.isdir(p):
1686 if os.path.isdir(p):
1680 for f, kind in osutil.listdir(p):
1687 for f, kind in osutil.listdir(p):
1681 if f.endswith('.rc'):
1688 if f.endswith('.rc'):
1682 _rcpath.append(os.path.join(p, f))
1689 _rcpath.append(os.path.join(p, f))
1683 else:
1690 else:
1684 _rcpath.append(p)
1691 _rcpath.append(p)
1685 else:
1692 else:
1686 _rcpath = os_rcpath()
1693 _rcpath = os_rcpath()
1687 return _rcpath
1694 return _rcpath
1688
1695
1689 def bytecount(nbytes):
1696 def bytecount(nbytes):
1690 '''return byte count formatted as readable string, with units'''
1697 '''return byte count formatted as readable string, with units'''
1691
1698
1692 units = (
1699 units = (
1693 (100, 1<<30, _('%.0f GB')),
1700 (100, 1<<30, _('%.0f GB')),
1694 (10, 1<<30, _('%.1f GB')),
1701 (10, 1<<30, _('%.1f GB')),
1695 (1, 1<<30, _('%.2f GB')),
1702 (1, 1<<30, _('%.2f GB')),
1696 (100, 1<<20, _('%.0f MB')),
1703 (100, 1<<20, _('%.0f MB')),
1697 (10, 1<<20, _('%.1f MB')),
1704 (10, 1<<20, _('%.1f MB')),
1698 (1, 1<<20, _('%.2f MB')),
1705 (1, 1<<20, _('%.2f MB')),
1699 (100, 1<<10, _('%.0f KB')),
1706 (100, 1<<10, _('%.0f KB')),
1700 (10, 1<<10, _('%.1f KB')),
1707 (10, 1<<10, _('%.1f KB')),
1701 (1, 1<<10, _('%.2f KB')),
1708 (1, 1<<10, _('%.2f KB')),
1702 (1, 1, _('%.0f bytes')),
1709 (1, 1, _('%.0f bytes')),
1703 )
1710 )
1704
1711
1705 for multiplier, divisor, format in units:
1712 for multiplier, divisor, format in units:
1706 if nbytes >= divisor * multiplier:
1713 if nbytes >= divisor * multiplier:
1707 return format % (nbytes / float(divisor))
1714 return format % (nbytes / float(divisor))
1708 return units[-1][2] % nbytes
1715 return units[-1][2] % nbytes
1709
1716
1710 def drop_scheme(scheme, path):
1717 def drop_scheme(scheme, path):
1711 sc = scheme + ':'
1718 sc = scheme + ':'
1712 if path.startswith(sc):
1719 if path.startswith(sc):
1713 path = path[len(sc):]
1720 path = path[len(sc):]
1714 if path.startswith('//'):
1721 if path.startswith('//'):
1715 path = path[2:]
1722 path = path[2:]
1716 return path
1723 return path
1717
1724
1718 def uirepr(s):
1725 def uirepr(s):
1719 # Avoid double backslash in Windows path repr()
1726 # Avoid double backslash in Windows path repr()
1720 return repr(s).replace('\\\\', '\\')
1727 return repr(s).replace('\\\\', '\\')
1721
1728
1722 def hidepassword(url):
1729 def hidepassword(url):
1723 '''replaces the password in the url string by three asterisks (***)
1730 '''replaces the password in the url string by three asterisks (***)
1724
1731
1725 >>> hidepassword('http://www.example.com/some/path#fragment')
1732 >>> hidepassword('http://www.example.com/some/path#fragment')
1726 'http://www.example.com/some/path#fragment'
1733 'http://www.example.com/some/path#fragment'
1727 >>> hidepassword('http://me@www.example.com/some/path#fragment')
1734 >>> hidepassword('http://me@www.example.com/some/path#fragment')
1728 'http://me@www.example.com/some/path#fragment'
1735 'http://me@www.example.com/some/path#fragment'
1729 >>> hidepassword('http://me:simplepw@www.example.com/path#frag')
1736 >>> hidepassword('http://me:simplepw@www.example.com/path#frag')
1730 'http://me:***@www.example.com/path#frag'
1737 'http://me:***@www.example.com/path#frag'
1731 >>> hidepassword('http://me:complex:pw@www.example.com/path#frag')
1738 >>> hidepassword('http://me:complex:pw@www.example.com/path#frag')
1732 'http://me:***@www.example.com/path#frag'
1739 'http://me:***@www.example.com/path#frag'
1733 >>> hidepassword('/path/to/repo')
1740 >>> hidepassword('/path/to/repo')
1734 '/path/to/repo'
1741 '/path/to/repo'
1735 >>> hidepassword('relative/path/to/repo')
1742 >>> hidepassword('relative/path/to/repo')
1736 'relative/path/to/repo'
1743 'relative/path/to/repo'
1737 >>> hidepassword('c:\\\\path\\\\to\\\\repo')
1744 >>> hidepassword('c:\\\\path\\\\to\\\\repo')
1738 'c:\\\\path\\\\to\\\\repo'
1745 'c:\\\\path\\\\to\\\\repo'
1739 >>> hidepassword('c:/path/to/repo')
1746 >>> hidepassword('c:/path/to/repo')
1740 'c:/path/to/repo'
1747 'c:/path/to/repo'
1741 >>> hidepassword('bundle://path/to/bundle')
1748 >>> hidepassword('bundle://path/to/bundle')
1742 'bundle://path/to/bundle'
1749 'bundle://path/to/bundle'
1743 '''
1750 '''
1744 url_parts = list(urlparse.urlparse(url))
1751 url_parts = list(urlparse.urlparse(url))
1745 host_with_pw_pattern = re.compile('^([^:]*):([^@]*)@(.*)$')
1752 host_with_pw_pattern = re.compile('^([^:]*):([^@]*)@(.*)$')
1746 if host_with_pw_pattern.match(url_parts[1]):
1753 if host_with_pw_pattern.match(url_parts[1]):
1747 url_parts[1] = re.sub(host_with_pw_pattern, r'\1:***@\3',
1754 url_parts[1] = re.sub(host_with_pw_pattern, r'\1:***@\3',
1748 url_parts[1])
1755 url_parts[1])
1749 return urlparse.urlunparse(url_parts)
1756 return urlparse.urlunparse(url_parts)
1750
1757
General Comments 0
You need to be logged in to leave comments. Login now