##// END OF EJS Templates
chunkbuffer: split big strings directly in chunkbuffer
Benoit Boissinot -
r11670:1b3b843e default
parent child Browse files
Show More
@@ -1,1405 +1,1398 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 # import stuff from node for others to import from revlog
14 # import stuff from node for others to import from revlog
15 from node import bin, hex, nullid, nullrev, short #@UnusedImport
15 from node import bin, hex, nullid, nullrev, short #@UnusedImport
16 from i18n import _
16 from i18n import _
17 import changegroup, ancestor, mdiff, parsers, error, util
17 import changegroup, ancestor, mdiff, parsers, error, util
18 import struct, zlib, errno
18 import struct, zlib, errno
19
19
20 _pack = struct.pack
20 _pack = struct.pack
21 _unpack = struct.unpack
21 _unpack = struct.unpack
22 _compress = zlib.compress
22 _compress = zlib.compress
23 _decompress = zlib.decompress
23 _decompress = zlib.decompress
24 _sha = util.sha1
24 _sha = util.sha1
25
25
26 # revlog flags
26 # revlog flags
27 REVLOGV0 = 0
27 REVLOGV0 = 0
28 REVLOGNG = 1
28 REVLOGNG = 1
29 REVLOGNGINLINEDATA = (1 << 16)
29 REVLOGNGINLINEDATA = (1 << 16)
30 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
30 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
31 REVLOG_DEFAULT_FORMAT = REVLOGNG
31 REVLOG_DEFAULT_FORMAT = REVLOGNG
32 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
32 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
33
33
34 # amount of data read unconditionally, should be >= 4
34 # amount of data read unconditionally, should be >= 4
35 # when not inline: threshold for using lazy index
35 # when not inline: threshold for using lazy index
36 _prereadsize = 1048576
36 _prereadsize = 1048576
37 # max size of revlog with inline data
37 # max size of revlog with inline data
38 _maxinline = 131072
38 _maxinline = 131072
39
39
40 RevlogError = error.RevlogError
40 RevlogError = error.RevlogError
41 LookupError = error.LookupError
41 LookupError = error.LookupError
42
42
43 def getoffset(q):
43 def getoffset(q):
44 return int(q >> 16)
44 return int(q >> 16)
45
45
46 def gettype(q):
46 def gettype(q):
47 return int(q & 0xFFFF)
47 return int(q & 0xFFFF)
48
48
49 def offset_type(offset, type):
49 def offset_type(offset, type):
50 return long(long(offset) << 16 | type)
50 return long(long(offset) << 16 | type)
51
51
52 nullhash = _sha(nullid)
52 nullhash = _sha(nullid)
53
53
54 def hash(text, p1, p2):
54 def hash(text, p1, p2):
55 """generate a hash from the given text and its parent hashes
55 """generate a hash from the given text and its parent hashes
56
56
57 This hash combines both the current file contents and its history
57 This hash combines both the current file contents and its history
58 in a manner that makes it easy to distinguish nodes with the same
58 in a manner that makes it easy to distinguish nodes with the same
59 content in the revision graph.
59 content in the revision graph.
60 """
60 """
61 # As of now, if one of the parent node is null, p2 is null
61 # As of now, if one of the parent node is null, p2 is null
62 if p2 == nullid:
62 if p2 == nullid:
63 # deep copy of a hash is faster than creating one
63 # deep copy of a hash is faster than creating one
64 s = nullhash.copy()
64 s = nullhash.copy()
65 s.update(p1)
65 s.update(p1)
66 else:
66 else:
67 # none of the parent nodes are nullid
67 # none of the parent nodes are nullid
68 l = [p1, p2]
68 l = [p1, p2]
69 l.sort()
69 l.sort()
70 s = _sha(l[0])
70 s = _sha(l[0])
71 s.update(l[1])
71 s.update(l[1])
72 s.update(text)
72 s.update(text)
73 return s.digest()
73 return s.digest()
74
74
75 def compress(text):
75 def compress(text):
76 """ generate a possibly-compressed representation of text """
76 """ generate a possibly-compressed representation of text """
77 if not text:
77 if not text:
78 return ("", text)
78 return ("", text)
79 l = len(text)
79 l = len(text)
80 bin = None
80 bin = None
81 if l < 44:
81 if l < 44:
82 pass
82 pass
83 elif l > 1000000:
83 elif l > 1000000:
84 # zlib makes an internal copy, thus doubling memory usage for
84 # zlib makes an internal copy, thus doubling memory usage for
85 # large files, so lets do this in pieces
85 # large files, so lets do this in pieces
86 z = zlib.compressobj()
86 z = zlib.compressobj()
87 p = []
87 p = []
88 pos = 0
88 pos = 0
89 while pos < l:
89 while pos < l:
90 pos2 = pos + 2**20
90 pos2 = pos + 2**20
91 p.append(z.compress(text[pos:pos2]))
91 p.append(z.compress(text[pos:pos2]))
92 pos = pos2
92 pos = pos2
93 p.append(z.flush())
93 p.append(z.flush())
94 if sum(map(len, p)) < l:
94 if sum(map(len, p)) < l:
95 bin = "".join(p)
95 bin = "".join(p)
96 else:
96 else:
97 bin = _compress(text)
97 bin = _compress(text)
98 if bin is None or len(bin) > l:
98 if bin is None or len(bin) > l:
99 if text[0] == '\0':
99 if text[0] == '\0':
100 return ("", text)
100 return ("", text)
101 return ('u', text)
101 return ('u', text)
102 return ("", bin)
102 return ("", bin)
103
103
104 def decompress(bin):
104 def decompress(bin):
105 """ decompress the given input """
105 """ decompress the given input """
106 if not bin:
106 if not bin:
107 return bin
107 return bin
108 t = bin[0]
108 t = bin[0]
109 if t == '\0':
109 if t == '\0':
110 return bin
110 return bin
111 if t == 'x':
111 if t == 'x':
112 return _decompress(bin)
112 return _decompress(bin)
113 if t == 'u':
113 if t == 'u':
114 return bin[1:]
114 return bin[1:]
115 raise RevlogError(_("unknown compression type %r") % t)
115 raise RevlogError(_("unknown compression type %r") % t)
116
116
117 class lazyparser(object):
117 class lazyparser(object):
118 """
118 """
119 this class avoids the need to parse the entirety of large indices
119 this class avoids the need to parse the entirety of large indices
120 """
120 """
121
121
122 # lazyparser is not safe to use on windows if win32 extensions not
122 # lazyparser is not safe to use on windows if win32 extensions not
123 # available. it keeps file handle open, which make it not possible
123 # available. it keeps file handle open, which make it not possible
124 # to break hardlinks on local cloned repos.
124 # to break hardlinks on local cloned repos.
125
125
126 def __init__(self, dataf):
126 def __init__(self, dataf):
127 try:
127 try:
128 size = util.fstat(dataf).st_size
128 size = util.fstat(dataf).st_size
129 except AttributeError:
129 except AttributeError:
130 size = 0
130 size = 0
131 self.dataf = dataf
131 self.dataf = dataf
132 self.s = struct.calcsize(indexformatng)
132 self.s = struct.calcsize(indexformatng)
133 self.datasize = size
133 self.datasize = size
134 self.l = size // self.s
134 self.l = size // self.s
135 self.index = [None] * self.l
135 self.index = [None] * self.l
136 self.map = {nullid: nullrev}
136 self.map = {nullid: nullrev}
137 self.allmap = 0
137 self.allmap = 0
138 self.all = 0
138 self.all = 0
139 self.mapfind_count = 0
139 self.mapfind_count = 0
140
140
141 def loadmap(self):
141 def loadmap(self):
142 """
142 """
143 during a commit, we need to make sure the rev being added is
143 during a commit, we need to make sure the rev being added is
144 not a duplicate. This requires loading the entire index,
144 not a duplicate. This requires loading the entire index,
145 which is fairly slow. loadmap can load up just the node map,
145 which is fairly slow. loadmap can load up just the node map,
146 which takes much less time.
146 which takes much less time.
147 """
147 """
148 if self.allmap:
148 if self.allmap:
149 return
149 return
150 end = self.datasize
150 end = self.datasize
151 self.allmap = 1
151 self.allmap = 1
152 cur = 0
152 cur = 0
153 count = 0
153 count = 0
154 blocksize = self.s * 256
154 blocksize = self.s * 256
155 self.dataf.seek(0)
155 self.dataf.seek(0)
156 while cur < end:
156 while cur < end:
157 data = self.dataf.read(blocksize)
157 data = self.dataf.read(blocksize)
158 off = 0
158 off = 0
159 for x in xrange(256):
159 for x in xrange(256):
160 n = data[off + ngshaoffset:off + ngshaoffset + 20]
160 n = data[off + ngshaoffset:off + ngshaoffset + 20]
161 self.map[n] = count
161 self.map[n] = count
162 count += 1
162 count += 1
163 if count >= self.l:
163 if count >= self.l:
164 break
164 break
165 off += self.s
165 off += self.s
166 cur += blocksize
166 cur += blocksize
167
167
168 def loadblock(self, blockstart, blocksize, data=None):
168 def loadblock(self, blockstart, blocksize, data=None):
169 if self.all:
169 if self.all:
170 return
170 return
171 if data is None:
171 if data is None:
172 self.dataf.seek(blockstart)
172 self.dataf.seek(blockstart)
173 if blockstart + blocksize > self.datasize:
173 if blockstart + blocksize > self.datasize:
174 # the revlog may have grown since we've started running,
174 # the revlog may have grown since we've started running,
175 # but we don't have space in self.index for more entries.
175 # but we don't have space in self.index for more entries.
176 # limit blocksize so that we don't get too much data.
176 # limit blocksize so that we don't get too much data.
177 blocksize = max(self.datasize - blockstart, 0)
177 blocksize = max(self.datasize - blockstart, 0)
178 data = self.dataf.read(blocksize)
178 data = self.dataf.read(blocksize)
179 lend = len(data) // self.s
179 lend = len(data) // self.s
180 i = blockstart // self.s
180 i = blockstart // self.s
181 off = 0
181 off = 0
182 # lazyindex supports __delitem__
182 # lazyindex supports __delitem__
183 if lend > len(self.index) - i:
183 if lend > len(self.index) - i:
184 lend = len(self.index) - i
184 lend = len(self.index) - i
185 for x in xrange(lend):
185 for x in xrange(lend):
186 if self.index[i + x] is None:
186 if self.index[i + x] is None:
187 b = data[off : off + self.s]
187 b = data[off : off + self.s]
188 self.index[i + x] = b
188 self.index[i + x] = b
189 n = b[ngshaoffset:ngshaoffset + 20]
189 n = b[ngshaoffset:ngshaoffset + 20]
190 self.map[n] = i + x
190 self.map[n] = i + x
191 off += self.s
191 off += self.s
192
192
193 def findnode(self, node):
193 def findnode(self, node):
194 """search backwards through the index file for a specific node"""
194 """search backwards through the index file for a specific node"""
195 if self.allmap:
195 if self.allmap:
196 return None
196 return None
197
197
198 # hg log will cause many many searches for the manifest
198 # hg log will cause many many searches for the manifest
199 # nodes. After we get called a few times, just load the whole
199 # nodes. After we get called a few times, just load the whole
200 # thing.
200 # thing.
201 if self.mapfind_count > 8:
201 if self.mapfind_count > 8:
202 self.loadmap()
202 self.loadmap()
203 if node in self.map:
203 if node in self.map:
204 return node
204 return node
205 return None
205 return None
206 self.mapfind_count += 1
206 self.mapfind_count += 1
207 last = self.l - 1
207 last = self.l - 1
208 while self.index[last] != None:
208 while self.index[last] != None:
209 if last == 0:
209 if last == 0:
210 self.all = 1
210 self.all = 1
211 self.allmap = 1
211 self.allmap = 1
212 return None
212 return None
213 last -= 1
213 last -= 1
214 end = (last + 1) * self.s
214 end = (last + 1) * self.s
215 blocksize = self.s * 256
215 blocksize = self.s * 256
216 while end >= 0:
216 while end >= 0:
217 start = max(end - blocksize, 0)
217 start = max(end - blocksize, 0)
218 self.dataf.seek(start)
218 self.dataf.seek(start)
219 data = self.dataf.read(end - start)
219 data = self.dataf.read(end - start)
220 findend = end - start
220 findend = end - start
221 while True:
221 while True:
222 # we're searching backwards, so we have to make sure
222 # we're searching backwards, so we have to make sure
223 # we don't find a changeset where this node is a parent
223 # we don't find a changeset where this node is a parent
224 off = data.find(node, 0, findend)
224 off = data.find(node, 0, findend)
225 findend = off
225 findend = off
226 if off >= 0:
226 if off >= 0:
227 i = off / self.s
227 i = off / self.s
228 off = i * self.s
228 off = i * self.s
229 n = data[off + ngshaoffset:off + ngshaoffset + 20]
229 n = data[off + ngshaoffset:off + ngshaoffset + 20]
230 if n == node:
230 if n == node:
231 self.map[n] = i + start / self.s
231 self.map[n] = i + start / self.s
232 return node
232 return node
233 else:
233 else:
234 break
234 break
235 end -= blocksize
235 end -= blocksize
236 return None
236 return None
237
237
238 def loadindex(self, i=None, end=None):
238 def loadindex(self, i=None, end=None):
239 if self.all:
239 if self.all:
240 return
240 return
241 all = False
241 all = False
242 if i is None:
242 if i is None:
243 blockstart = 0
243 blockstart = 0
244 blocksize = (65536 / self.s) * self.s
244 blocksize = (65536 / self.s) * self.s
245 end = self.datasize
245 end = self.datasize
246 all = True
246 all = True
247 else:
247 else:
248 if end:
248 if end:
249 blockstart = i * self.s
249 blockstart = i * self.s
250 end = end * self.s
250 end = end * self.s
251 blocksize = end - blockstart
251 blocksize = end - blockstart
252 else:
252 else:
253 blockstart = (i & ~1023) * self.s
253 blockstart = (i & ~1023) * self.s
254 blocksize = self.s * 1024
254 blocksize = self.s * 1024
255 end = blockstart + blocksize
255 end = blockstart + blocksize
256 while blockstart < end:
256 while blockstart < end:
257 self.loadblock(blockstart, blocksize)
257 self.loadblock(blockstart, blocksize)
258 blockstart += blocksize
258 blockstart += blocksize
259 if all:
259 if all:
260 self.all = True
260 self.all = True
261
261
262 class lazyindex(object):
262 class lazyindex(object):
263 """a lazy version of the index array"""
263 """a lazy version of the index array"""
264 def __init__(self, parser):
264 def __init__(self, parser):
265 self.p = parser
265 self.p = parser
266 def __len__(self):
266 def __len__(self):
267 return len(self.p.index)
267 return len(self.p.index)
268 def load(self, pos):
268 def load(self, pos):
269 if pos < 0:
269 if pos < 0:
270 pos += len(self.p.index)
270 pos += len(self.p.index)
271 self.p.loadindex(pos)
271 self.p.loadindex(pos)
272 return self.p.index[pos]
272 return self.p.index[pos]
273 def __getitem__(self, pos):
273 def __getitem__(self, pos):
274 return _unpack(indexformatng, self.p.index[pos] or self.load(pos))
274 return _unpack(indexformatng, self.p.index[pos] or self.load(pos))
275 def __setitem__(self, pos, item):
275 def __setitem__(self, pos, item):
276 self.p.index[pos] = _pack(indexformatng, *item)
276 self.p.index[pos] = _pack(indexformatng, *item)
277 def __delitem__(self, pos):
277 def __delitem__(self, pos):
278 del self.p.index[pos]
278 del self.p.index[pos]
279 def insert(self, pos, e):
279 def insert(self, pos, e):
280 self.p.index.insert(pos, _pack(indexformatng, *e))
280 self.p.index.insert(pos, _pack(indexformatng, *e))
281 def append(self, e):
281 def append(self, e):
282 self.p.index.append(_pack(indexformatng, *e))
282 self.p.index.append(_pack(indexformatng, *e))
283
283
284 class lazymap(object):
284 class lazymap(object):
285 """a lazy version of the node map"""
285 """a lazy version of the node map"""
286 def __init__(self, parser):
286 def __init__(self, parser):
287 self.p = parser
287 self.p = parser
288 def load(self, key):
288 def load(self, key):
289 n = self.p.findnode(key)
289 n = self.p.findnode(key)
290 if n is None:
290 if n is None:
291 raise KeyError(key)
291 raise KeyError(key)
292 def __contains__(self, key):
292 def __contains__(self, key):
293 if key in self.p.map:
293 if key in self.p.map:
294 return True
294 return True
295 self.p.loadmap()
295 self.p.loadmap()
296 return key in self.p.map
296 return key in self.p.map
297 def __iter__(self):
297 def __iter__(self):
298 yield nullid
298 yield nullid
299 for i, ret in enumerate(self.p.index):
299 for i, ret in enumerate(self.p.index):
300 if not ret:
300 if not ret:
301 self.p.loadindex(i)
301 self.p.loadindex(i)
302 ret = self.p.index[i]
302 ret = self.p.index[i]
303 if isinstance(ret, str):
303 if isinstance(ret, str):
304 ret = _unpack(indexformatng, ret)
304 ret = _unpack(indexformatng, ret)
305 yield ret[7]
305 yield ret[7]
306 def __getitem__(self, key):
306 def __getitem__(self, key):
307 try:
307 try:
308 return self.p.map[key]
308 return self.p.map[key]
309 except KeyError:
309 except KeyError:
310 try:
310 try:
311 self.load(key)
311 self.load(key)
312 return self.p.map[key]
312 return self.p.map[key]
313 except KeyError:
313 except KeyError:
314 raise KeyError("node " + hex(key))
314 raise KeyError("node " + hex(key))
315 def __setitem__(self, key, val):
315 def __setitem__(self, key, val):
316 self.p.map[key] = val
316 self.p.map[key] = val
317 def __delitem__(self, key):
317 def __delitem__(self, key):
318 del self.p.map[key]
318 del self.p.map[key]
319
319
320 indexformatv0 = ">4l20s20s20s"
320 indexformatv0 = ">4l20s20s20s"
321 v0shaoffset = 56
321 v0shaoffset = 56
322
322
323 class revlogoldio(object):
323 class revlogoldio(object):
324 def __init__(self):
324 def __init__(self):
325 self.size = struct.calcsize(indexformatv0)
325 self.size = struct.calcsize(indexformatv0)
326
326
327 def parseindex(self, fp, data, inline):
327 def parseindex(self, fp, data, inline):
328 s = self.size
328 s = self.size
329 index = []
329 index = []
330 nodemap = {nullid: nullrev}
330 nodemap = {nullid: nullrev}
331 n = off = 0
331 n = off = 0
332 if len(data) == _prereadsize:
332 if len(data) == _prereadsize:
333 data += fp.read() # read the rest
333 data += fp.read() # read the rest
334 l = len(data)
334 l = len(data)
335 while off + s <= l:
335 while off + s <= l:
336 cur = data[off:off + s]
336 cur = data[off:off + s]
337 off += s
337 off += s
338 e = _unpack(indexformatv0, cur)
338 e = _unpack(indexformatv0, cur)
339 # transform to revlogv1 format
339 # transform to revlogv1 format
340 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
340 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
341 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
341 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
342 index.append(e2)
342 index.append(e2)
343 nodemap[e[6]] = n
343 nodemap[e[6]] = n
344 n += 1
344 n += 1
345
345
346 return index, nodemap, None
346 return index, nodemap, None
347
347
348 def packentry(self, entry, node, version, rev):
348 def packentry(self, entry, node, version, rev):
349 if gettype(entry[0]):
349 if gettype(entry[0]):
350 raise RevlogError(_("index entry flags need RevlogNG"))
350 raise RevlogError(_("index entry flags need RevlogNG"))
351 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
351 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
352 node(entry[5]), node(entry[6]), entry[7])
352 node(entry[5]), node(entry[6]), entry[7])
353 return _pack(indexformatv0, *e2)
353 return _pack(indexformatv0, *e2)
354
354
355 # index ng:
355 # index ng:
356 # 6 bytes: offset
356 # 6 bytes: offset
357 # 2 bytes: flags
357 # 2 bytes: flags
358 # 4 bytes: compressed length
358 # 4 bytes: compressed length
359 # 4 bytes: uncompressed length
359 # 4 bytes: uncompressed length
360 # 4 bytes: base rev
360 # 4 bytes: base rev
361 # 4 bytes: link rev
361 # 4 bytes: link rev
362 # 4 bytes: parent 1 rev
362 # 4 bytes: parent 1 rev
363 # 4 bytes: parent 2 rev
363 # 4 bytes: parent 2 rev
364 # 32 bytes: nodeid
364 # 32 bytes: nodeid
365 indexformatng = ">Qiiiiii20s12x"
365 indexformatng = ">Qiiiiii20s12x"
366 ngshaoffset = 32
366 ngshaoffset = 32
367 versionformat = ">I"
367 versionformat = ">I"
368
368
369 class revlogio(object):
369 class revlogio(object):
370 def __init__(self):
370 def __init__(self):
371 self.size = struct.calcsize(indexformatng)
371 self.size = struct.calcsize(indexformatng)
372
372
373 def parseindex(self, fp, data, inline):
373 def parseindex(self, fp, data, inline):
374 if len(data) == _prereadsize:
374 if len(data) == _prereadsize:
375 if util.openhardlinks() and not inline:
375 if util.openhardlinks() and not inline:
376 # big index, let's parse it on demand
376 # big index, let's parse it on demand
377 parser = lazyparser(fp)
377 parser = lazyparser(fp)
378 index = lazyindex(parser)
378 index = lazyindex(parser)
379 nodemap = lazymap(parser)
379 nodemap = lazymap(parser)
380 e = list(index[0])
380 e = list(index[0])
381 type = gettype(e[0])
381 type = gettype(e[0])
382 e[0] = offset_type(0, type)
382 e[0] = offset_type(0, type)
383 index[0] = e
383 index[0] = e
384 return index, nodemap, None
384 return index, nodemap, None
385 else:
385 else:
386 data += fp.read()
386 data += fp.read()
387
387
388 # call the C implementation to parse the index data
388 # call the C implementation to parse the index data
389 index, nodemap, cache = parsers.parse_index(data, inline)
389 index, nodemap, cache = parsers.parse_index(data, inline)
390 return index, nodemap, cache
390 return index, nodemap, cache
391
391
392 def packentry(self, entry, node, version, rev):
392 def packentry(self, entry, node, version, rev):
393 p = _pack(indexformatng, *entry)
393 p = _pack(indexformatng, *entry)
394 if rev == 0:
394 if rev == 0:
395 p = _pack(versionformat, version) + p[4:]
395 p = _pack(versionformat, version) + p[4:]
396 return p
396 return p
397
397
398 class revlog(object):
398 class revlog(object):
399 """
399 """
400 the underlying revision storage object
400 the underlying revision storage object
401
401
402 A revlog consists of two parts, an index and the revision data.
402 A revlog consists of two parts, an index and the revision data.
403
403
404 The index is a file with a fixed record size containing
404 The index is a file with a fixed record size containing
405 information on each revision, including its nodeid (hash), the
405 information on each revision, including its nodeid (hash), the
406 nodeids of its parents, the position and offset of its data within
406 nodeids of its parents, the position and offset of its data within
407 the data file, and the revision it's based on. Finally, each entry
407 the data file, and the revision it's based on. Finally, each entry
408 contains a linkrev entry that can serve as a pointer to external
408 contains a linkrev entry that can serve as a pointer to external
409 data.
409 data.
410
410
411 The revision data itself is a linear collection of data chunks.
411 The revision data itself is a linear collection of data chunks.
412 Each chunk represents a revision and is usually represented as a
412 Each chunk represents a revision and is usually represented as a
413 delta against the previous chunk. To bound lookup time, runs of
413 delta against the previous chunk. To bound lookup time, runs of
414 deltas are limited to about 2 times the length of the original
414 deltas are limited to about 2 times the length of the original
415 version data. This makes retrieval of a version proportional to
415 version data. This makes retrieval of a version proportional to
416 its size, or O(1) relative to the number of revisions.
416 its size, or O(1) relative to the number of revisions.
417
417
418 Both pieces of the revlog are written to in an append-only
418 Both pieces of the revlog are written to in an append-only
419 fashion, which means we never need to rewrite a file to insert or
419 fashion, which means we never need to rewrite a file to insert or
420 remove data, and can use some simple techniques to avoid the need
420 remove data, and can use some simple techniques to avoid the need
421 for locking while reading.
421 for locking while reading.
422 """
422 """
423 def __init__(self, opener, indexfile):
423 def __init__(self, opener, indexfile):
424 """
424 """
425 create a revlog object
425 create a revlog object
426
426
427 opener is a function that abstracts the file opening operation
427 opener is a function that abstracts the file opening operation
428 and can be used to implement COW semantics or the like.
428 and can be used to implement COW semantics or the like.
429 """
429 """
430 self.indexfile = indexfile
430 self.indexfile = indexfile
431 self.datafile = indexfile[:-2] + ".d"
431 self.datafile = indexfile[:-2] + ".d"
432 self.opener = opener
432 self.opener = opener
433 self._cache = None
433 self._cache = None
434 self._chunkcache = (0, '')
434 self._chunkcache = (0, '')
435 self.nodemap = {nullid: nullrev}
435 self.nodemap = {nullid: nullrev}
436 self.index = []
436 self.index = []
437
437
438 v = REVLOG_DEFAULT_VERSION
438 v = REVLOG_DEFAULT_VERSION
439 if hasattr(opener, 'options') and 'defversion' in opener.options:
439 if hasattr(opener, 'options') and 'defversion' in opener.options:
440 v = opener.options['defversion']
440 v = opener.options['defversion']
441 if v & REVLOGNG:
441 if v & REVLOGNG:
442 v |= REVLOGNGINLINEDATA
442 v |= REVLOGNGINLINEDATA
443
443
444 i = ''
444 i = ''
445 try:
445 try:
446 f = self.opener(self.indexfile)
446 f = self.opener(self.indexfile)
447 if "nonlazy" in getattr(self.opener, 'options', {}):
447 if "nonlazy" in getattr(self.opener, 'options', {}):
448 i = f.read()
448 i = f.read()
449 else:
449 else:
450 i = f.read(_prereadsize)
450 i = f.read(_prereadsize)
451 if len(i) > 0:
451 if len(i) > 0:
452 v = struct.unpack(versionformat, i[:4])[0]
452 v = struct.unpack(versionformat, i[:4])[0]
453 except IOError, inst:
453 except IOError, inst:
454 if inst.errno != errno.ENOENT:
454 if inst.errno != errno.ENOENT:
455 raise
455 raise
456
456
457 self.version = v
457 self.version = v
458 self._inline = v & REVLOGNGINLINEDATA
458 self._inline = v & REVLOGNGINLINEDATA
459 flags = v & ~0xFFFF
459 flags = v & ~0xFFFF
460 fmt = v & 0xFFFF
460 fmt = v & 0xFFFF
461 if fmt == REVLOGV0 and flags:
461 if fmt == REVLOGV0 and flags:
462 raise RevlogError(_("index %s unknown flags %#04x for format v0")
462 raise RevlogError(_("index %s unknown flags %#04x for format v0")
463 % (self.indexfile, flags >> 16))
463 % (self.indexfile, flags >> 16))
464 elif fmt == REVLOGNG and flags & ~REVLOGNGINLINEDATA:
464 elif fmt == REVLOGNG and flags & ~REVLOGNGINLINEDATA:
465 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
465 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
466 % (self.indexfile, flags >> 16))
466 % (self.indexfile, flags >> 16))
467 elif fmt > REVLOGNG:
467 elif fmt > REVLOGNG:
468 raise RevlogError(_("index %s unknown format %d")
468 raise RevlogError(_("index %s unknown format %d")
469 % (self.indexfile, fmt))
469 % (self.indexfile, fmt))
470
470
471 self._io = revlogio()
471 self._io = revlogio()
472 if self.version == REVLOGV0:
472 if self.version == REVLOGV0:
473 self._io = revlogoldio()
473 self._io = revlogoldio()
474 if i:
474 if i:
475 try:
475 try:
476 d = self._io.parseindex(f, i, self._inline)
476 d = self._io.parseindex(f, i, self._inline)
477 except (ValueError, IndexError):
477 except (ValueError, IndexError):
478 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
478 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
479 self.index, self.nodemap, self._chunkcache = d
479 self.index, self.nodemap, self._chunkcache = d
480 if not self._chunkcache:
480 if not self._chunkcache:
481 self._chunkclear()
481 self._chunkclear()
482
482
483 # add the magic null revision at -1 (if it hasn't been done already)
483 # add the magic null revision at -1 (if it hasn't been done already)
484 if (self.index == [] or isinstance(self.index, lazyindex) or
484 if (self.index == [] or isinstance(self.index, lazyindex) or
485 self.index[-1][7] != nullid) :
485 self.index[-1][7] != nullid) :
486 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
486 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
487
487
488 def _loadindex(self, start, end):
488 def _loadindex(self, start, end):
489 """load a block of indexes all at once from the lazy parser"""
489 """load a block of indexes all at once from the lazy parser"""
490 if isinstance(self.index, lazyindex):
490 if isinstance(self.index, lazyindex):
491 self.index.p.loadindex(start, end)
491 self.index.p.loadindex(start, end)
492
492
493 def _loadindexmap(self):
493 def _loadindexmap(self):
494 """loads both the map and the index from the lazy parser"""
494 """loads both the map and the index from the lazy parser"""
495 if isinstance(self.index, lazyindex):
495 if isinstance(self.index, lazyindex):
496 p = self.index.p
496 p = self.index.p
497 p.loadindex()
497 p.loadindex()
498 self.nodemap = p.map
498 self.nodemap = p.map
499
499
500 def _loadmap(self):
500 def _loadmap(self):
501 """loads the map from the lazy parser"""
501 """loads the map from the lazy parser"""
502 if isinstance(self.nodemap, lazymap):
502 if isinstance(self.nodemap, lazymap):
503 self.nodemap.p.loadmap()
503 self.nodemap.p.loadmap()
504 self.nodemap = self.nodemap.p.map
504 self.nodemap = self.nodemap.p.map
505
505
506 def tip(self):
506 def tip(self):
507 return self.node(len(self.index) - 2)
507 return self.node(len(self.index) - 2)
508 def __len__(self):
508 def __len__(self):
509 return len(self.index) - 1
509 return len(self.index) - 1
510 def __iter__(self):
510 def __iter__(self):
511 for i in xrange(len(self)):
511 for i in xrange(len(self)):
512 yield i
512 yield i
513 def rev(self, node):
513 def rev(self, node):
514 try:
514 try:
515 return self.nodemap[node]
515 return self.nodemap[node]
516 except KeyError:
516 except KeyError:
517 raise LookupError(node, self.indexfile, _('no node'))
517 raise LookupError(node, self.indexfile, _('no node'))
518 def node(self, rev):
518 def node(self, rev):
519 return self.index[rev][7]
519 return self.index[rev][7]
520 def linkrev(self, rev):
520 def linkrev(self, rev):
521 return self.index[rev][4]
521 return self.index[rev][4]
522 def parents(self, node):
522 def parents(self, node):
523 i = self.index
523 i = self.index
524 d = i[self.rev(node)]
524 d = i[self.rev(node)]
525 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
525 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
526 def parentrevs(self, rev):
526 def parentrevs(self, rev):
527 return self.index[rev][5:7]
527 return self.index[rev][5:7]
528 def start(self, rev):
528 def start(self, rev):
529 return int(self.index[rev][0] >> 16)
529 return int(self.index[rev][0] >> 16)
530 def end(self, rev):
530 def end(self, rev):
531 return self.start(rev) + self.length(rev)
531 return self.start(rev) + self.length(rev)
532 def length(self, rev):
532 def length(self, rev):
533 return self.index[rev][1]
533 return self.index[rev][1]
534 def base(self, rev):
534 def base(self, rev):
535 return self.index[rev][3]
535 return self.index[rev][3]
536
536
537 def size(self, rev):
537 def size(self, rev):
538 """return the length of the uncompressed text for a given revision"""
538 """return the length of the uncompressed text for a given revision"""
539 l = self.index[rev][2]
539 l = self.index[rev][2]
540 if l >= 0:
540 if l >= 0:
541 return l
541 return l
542
542
543 t = self.revision(self.node(rev))
543 t = self.revision(self.node(rev))
544 return len(t)
544 return len(t)
545
545
546 def reachable(self, node, stop=None):
546 def reachable(self, node, stop=None):
547 """return the set of all nodes ancestral to a given node, including
547 """return the set of all nodes ancestral to a given node, including
548 the node itself, stopping when stop is matched"""
548 the node itself, stopping when stop is matched"""
549 reachable = set((node,))
549 reachable = set((node,))
550 visit = [node]
550 visit = [node]
551 if stop:
551 if stop:
552 stopn = self.rev(stop)
552 stopn = self.rev(stop)
553 else:
553 else:
554 stopn = 0
554 stopn = 0
555 while visit:
555 while visit:
556 n = visit.pop(0)
556 n = visit.pop(0)
557 if n == stop:
557 if n == stop:
558 continue
558 continue
559 if n == nullid:
559 if n == nullid:
560 continue
560 continue
561 for p in self.parents(n):
561 for p in self.parents(n):
562 if self.rev(p) < stopn:
562 if self.rev(p) < stopn:
563 continue
563 continue
564 if p not in reachable:
564 if p not in reachable:
565 reachable.add(p)
565 reachable.add(p)
566 visit.append(p)
566 visit.append(p)
567 return reachable
567 return reachable
568
568
569 def ancestors(self, *revs):
569 def ancestors(self, *revs):
570 """Generate the ancestors of 'revs' in reverse topological order.
570 """Generate the ancestors of 'revs' in reverse topological order.
571
571
572 Yield a sequence of revision numbers starting with the parents
572 Yield a sequence of revision numbers starting with the parents
573 of each revision in revs, i.e., each revision is *not* considered
573 of each revision in revs, i.e., each revision is *not* considered
574 an ancestor of itself. Results are in breadth-first order:
574 an ancestor of itself. Results are in breadth-first order:
575 parents of each rev in revs, then parents of those, etc. Result
575 parents of each rev in revs, then parents of those, etc. Result
576 does not include the null revision."""
576 does not include the null revision."""
577 visit = list(revs)
577 visit = list(revs)
578 seen = set([nullrev])
578 seen = set([nullrev])
579 while visit:
579 while visit:
580 for parent in self.parentrevs(visit.pop(0)):
580 for parent in self.parentrevs(visit.pop(0)):
581 if parent not in seen:
581 if parent not in seen:
582 visit.append(parent)
582 visit.append(parent)
583 seen.add(parent)
583 seen.add(parent)
584 yield parent
584 yield parent
585
585
586 def descendants(self, *revs):
586 def descendants(self, *revs):
587 """Generate the descendants of 'revs' in revision order.
587 """Generate the descendants of 'revs' in revision order.
588
588
589 Yield a sequence of revision numbers starting with a child of
589 Yield a sequence of revision numbers starting with a child of
590 some rev in revs, i.e., each revision is *not* considered a
590 some rev in revs, i.e., each revision is *not* considered a
591 descendant of itself. Results are ordered by revision number (a
591 descendant of itself. Results are ordered by revision number (a
592 topological sort)."""
592 topological sort)."""
593 seen = set(revs)
593 seen = set(revs)
594 for i in xrange(min(revs) + 1, len(self)):
594 for i in xrange(min(revs) + 1, len(self)):
595 for x in self.parentrevs(i):
595 for x in self.parentrevs(i):
596 if x != nullrev and x in seen:
596 if x != nullrev and x in seen:
597 seen.add(i)
597 seen.add(i)
598 yield i
598 yield i
599 break
599 break
600
600
601 def findmissing(self, common=None, heads=None):
601 def findmissing(self, common=None, heads=None):
602 """Return the ancestors of heads that are not ancestors of common.
602 """Return the ancestors of heads that are not ancestors of common.
603
603
604 More specifically, return a list of nodes N such that every N
604 More specifically, return a list of nodes N such that every N
605 satisfies the following constraints:
605 satisfies the following constraints:
606
606
607 1. N is an ancestor of some node in 'heads'
607 1. N is an ancestor of some node in 'heads'
608 2. N is not an ancestor of any node in 'common'
608 2. N is not an ancestor of any node in 'common'
609
609
610 The list is sorted by revision number, meaning it is
610 The list is sorted by revision number, meaning it is
611 topologically sorted.
611 topologically sorted.
612
612
613 'heads' and 'common' are both lists of node IDs. If heads is
613 'heads' and 'common' are both lists of node IDs. If heads is
614 not supplied, uses all of the revlog's heads. If common is not
614 not supplied, uses all of the revlog's heads. If common is not
615 supplied, uses nullid."""
615 supplied, uses nullid."""
616 if common is None:
616 if common is None:
617 common = [nullid]
617 common = [nullid]
618 if heads is None:
618 if heads is None:
619 heads = self.heads()
619 heads = self.heads()
620
620
621 common = [self.rev(n) for n in common]
621 common = [self.rev(n) for n in common]
622 heads = [self.rev(n) for n in heads]
622 heads = [self.rev(n) for n in heads]
623
623
624 # we want the ancestors, but inclusive
624 # we want the ancestors, but inclusive
625 has = set(self.ancestors(*common))
625 has = set(self.ancestors(*common))
626 has.add(nullrev)
626 has.add(nullrev)
627 has.update(common)
627 has.update(common)
628
628
629 # take all ancestors from heads that aren't in has
629 # take all ancestors from heads that aren't in has
630 missing = set()
630 missing = set()
631 visit = [r for r in heads if r not in has]
631 visit = [r for r in heads if r not in has]
632 while visit:
632 while visit:
633 r = visit.pop(0)
633 r = visit.pop(0)
634 if r in missing:
634 if r in missing:
635 continue
635 continue
636 else:
636 else:
637 missing.add(r)
637 missing.add(r)
638 for p in self.parentrevs(r):
638 for p in self.parentrevs(r):
639 if p not in has:
639 if p not in has:
640 visit.append(p)
640 visit.append(p)
641 missing = list(missing)
641 missing = list(missing)
642 missing.sort()
642 missing.sort()
643 return [self.node(r) for r in missing]
643 return [self.node(r) for r in missing]
644
644
645 def nodesbetween(self, roots=None, heads=None):
645 def nodesbetween(self, roots=None, heads=None):
646 """Return a topological path from 'roots' to 'heads'.
646 """Return a topological path from 'roots' to 'heads'.
647
647
648 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
648 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
649 topologically sorted list of all nodes N that satisfy both of
649 topologically sorted list of all nodes N that satisfy both of
650 these constraints:
650 these constraints:
651
651
652 1. N is a descendant of some node in 'roots'
652 1. N is a descendant of some node in 'roots'
653 2. N is an ancestor of some node in 'heads'
653 2. N is an ancestor of some node in 'heads'
654
654
655 Every node is considered to be both a descendant and an ancestor
655 Every node is considered to be both a descendant and an ancestor
656 of itself, so every reachable node in 'roots' and 'heads' will be
656 of itself, so every reachable node in 'roots' and 'heads' will be
657 included in 'nodes'.
657 included in 'nodes'.
658
658
659 'outroots' is the list of reachable nodes in 'roots', i.e., the
659 'outroots' is the list of reachable nodes in 'roots', i.e., the
660 subset of 'roots' that is returned in 'nodes'. Likewise,
660 subset of 'roots' that is returned in 'nodes'. Likewise,
661 'outheads' is the subset of 'heads' that is also in 'nodes'.
661 'outheads' is the subset of 'heads' that is also in 'nodes'.
662
662
663 'roots' and 'heads' are both lists of node IDs. If 'roots' is
663 'roots' and 'heads' are both lists of node IDs. If 'roots' is
664 unspecified, uses nullid as the only root. If 'heads' is
664 unspecified, uses nullid as the only root. If 'heads' is
665 unspecified, uses list of all of the revlog's heads."""
665 unspecified, uses list of all of the revlog's heads."""
666 nonodes = ([], [], [])
666 nonodes = ([], [], [])
667 if roots is not None:
667 if roots is not None:
668 roots = list(roots)
668 roots = list(roots)
669 if not roots:
669 if not roots:
670 return nonodes
670 return nonodes
671 lowestrev = min([self.rev(n) for n in roots])
671 lowestrev = min([self.rev(n) for n in roots])
672 else:
672 else:
673 roots = [nullid] # Everybody's a descendent of nullid
673 roots = [nullid] # Everybody's a descendent of nullid
674 lowestrev = nullrev
674 lowestrev = nullrev
675 if (lowestrev == nullrev) and (heads is None):
675 if (lowestrev == nullrev) and (heads is None):
676 # We want _all_ the nodes!
676 # We want _all_ the nodes!
677 return ([self.node(r) for r in self], [nullid], list(self.heads()))
677 return ([self.node(r) for r in self], [nullid], list(self.heads()))
678 if heads is None:
678 if heads is None:
679 # All nodes are ancestors, so the latest ancestor is the last
679 # All nodes are ancestors, so the latest ancestor is the last
680 # node.
680 # node.
681 highestrev = len(self) - 1
681 highestrev = len(self) - 1
682 # Set ancestors to None to signal that every node is an ancestor.
682 # Set ancestors to None to signal that every node is an ancestor.
683 ancestors = None
683 ancestors = None
684 # Set heads to an empty dictionary for later discovery of heads
684 # Set heads to an empty dictionary for later discovery of heads
685 heads = {}
685 heads = {}
686 else:
686 else:
687 heads = list(heads)
687 heads = list(heads)
688 if not heads:
688 if not heads:
689 return nonodes
689 return nonodes
690 ancestors = set()
690 ancestors = set()
691 # Turn heads into a dictionary so we can remove 'fake' heads.
691 # Turn heads into a dictionary so we can remove 'fake' heads.
692 # Also, later we will be using it to filter out the heads we can't
692 # Also, later we will be using it to filter out the heads we can't
693 # find from roots.
693 # find from roots.
694 heads = dict.fromkeys(heads, 0)
694 heads = dict.fromkeys(heads, 0)
695 # Start at the top and keep marking parents until we're done.
695 # Start at the top and keep marking parents until we're done.
696 nodestotag = set(heads)
696 nodestotag = set(heads)
697 # Remember where the top was so we can use it as a limit later.
697 # Remember where the top was so we can use it as a limit later.
698 highestrev = max([self.rev(n) for n in nodestotag])
698 highestrev = max([self.rev(n) for n in nodestotag])
699 while nodestotag:
699 while nodestotag:
700 # grab a node to tag
700 # grab a node to tag
701 n = nodestotag.pop()
701 n = nodestotag.pop()
702 # Never tag nullid
702 # Never tag nullid
703 if n == nullid:
703 if n == nullid:
704 continue
704 continue
705 # A node's revision number represents its place in a
705 # A node's revision number represents its place in a
706 # topologically sorted list of nodes.
706 # topologically sorted list of nodes.
707 r = self.rev(n)
707 r = self.rev(n)
708 if r >= lowestrev:
708 if r >= lowestrev:
709 if n not in ancestors:
709 if n not in ancestors:
710 # If we are possibly a descendent of one of the roots
710 # If we are possibly a descendent of one of the roots
711 # and we haven't already been marked as an ancestor
711 # and we haven't already been marked as an ancestor
712 ancestors.add(n) # Mark as ancestor
712 ancestors.add(n) # Mark as ancestor
713 # Add non-nullid parents to list of nodes to tag.
713 # Add non-nullid parents to list of nodes to tag.
714 nodestotag.update([p for p in self.parents(n) if
714 nodestotag.update([p for p in self.parents(n) if
715 p != nullid])
715 p != nullid])
716 elif n in heads: # We've seen it before, is it a fake head?
716 elif n in heads: # We've seen it before, is it a fake head?
717 # So it is, real heads should not be the ancestors of
717 # So it is, real heads should not be the ancestors of
718 # any other heads.
718 # any other heads.
719 heads.pop(n)
719 heads.pop(n)
720 if not ancestors:
720 if not ancestors:
721 return nonodes
721 return nonodes
722 # Now that we have our set of ancestors, we want to remove any
722 # Now that we have our set of ancestors, we want to remove any
723 # roots that are not ancestors.
723 # roots that are not ancestors.
724
724
725 # If one of the roots was nullid, everything is included anyway.
725 # If one of the roots was nullid, everything is included anyway.
726 if lowestrev > nullrev:
726 if lowestrev > nullrev:
727 # But, since we weren't, let's recompute the lowest rev to not
727 # But, since we weren't, let's recompute the lowest rev to not
728 # include roots that aren't ancestors.
728 # include roots that aren't ancestors.
729
729
730 # Filter out roots that aren't ancestors of heads
730 # Filter out roots that aren't ancestors of heads
731 roots = [n for n in roots if n in ancestors]
731 roots = [n for n in roots if n in ancestors]
732 # Recompute the lowest revision
732 # Recompute the lowest revision
733 if roots:
733 if roots:
734 lowestrev = min([self.rev(n) for n in roots])
734 lowestrev = min([self.rev(n) for n in roots])
735 else:
735 else:
736 # No more roots? Return empty list
736 # No more roots? Return empty list
737 return nonodes
737 return nonodes
738 else:
738 else:
739 # We are descending from nullid, and don't need to care about
739 # We are descending from nullid, and don't need to care about
740 # any other roots.
740 # any other roots.
741 lowestrev = nullrev
741 lowestrev = nullrev
742 roots = [nullid]
742 roots = [nullid]
743 # Transform our roots list into a set.
743 # Transform our roots list into a set.
744 descendents = set(roots)
744 descendents = set(roots)
745 # Also, keep the original roots so we can filter out roots that aren't
745 # Also, keep the original roots so we can filter out roots that aren't
746 # 'real' roots (i.e. are descended from other roots).
746 # 'real' roots (i.e. are descended from other roots).
747 roots = descendents.copy()
747 roots = descendents.copy()
748 # Our topologically sorted list of output nodes.
748 # Our topologically sorted list of output nodes.
749 orderedout = []
749 orderedout = []
750 # Don't start at nullid since we don't want nullid in our output list,
750 # Don't start at nullid since we don't want nullid in our output list,
751 # and if nullid shows up in descedents, empty parents will look like
751 # and if nullid shows up in descedents, empty parents will look like
752 # they're descendents.
752 # they're descendents.
753 for r in xrange(max(lowestrev, 0), highestrev + 1):
753 for r in xrange(max(lowestrev, 0), highestrev + 1):
754 n = self.node(r)
754 n = self.node(r)
755 isdescendent = False
755 isdescendent = False
756 if lowestrev == nullrev: # Everybody is a descendent of nullid
756 if lowestrev == nullrev: # Everybody is a descendent of nullid
757 isdescendent = True
757 isdescendent = True
758 elif n in descendents:
758 elif n in descendents:
759 # n is already a descendent
759 # n is already a descendent
760 isdescendent = True
760 isdescendent = True
761 # This check only needs to be done here because all the roots
761 # This check only needs to be done here because all the roots
762 # will start being marked is descendents before the loop.
762 # will start being marked is descendents before the loop.
763 if n in roots:
763 if n in roots:
764 # If n was a root, check if it's a 'real' root.
764 # If n was a root, check if it's a 'real' root.
765 p = tuple(self.parents(n))
765 p = tuple(self.parents(n))
766 # If any of its parents are descendents, it's not a root.
766 # If any of its parents are descendents, it's not a root.
767 if (p[0] in descendents) or (p[1] in descendents):
767 if (p[0] in descendents) or (p[1] in descendents):
768 roots.remove(n)
768 roots.remove(n)
769 else:
769 else:
770 p = tuple(self.parents(n))
770 p = tuple(self.parents(n))
771 # A node is a descendent if either of its parents are
771 # A node is a descendent if either of its parents are
772 # descendents. (We seeded the dependents list with the roots
772 # descendents. (We seeded the dependents list with the roots
773 # up there, remember?)
773 # up there, remember?)
774 if (p[0] in descendents) or (p[1] in descendents):
774 if (p[0] in descendents) or (p[1] in descendents):
775 descendents.add(n)
775 descendents.add(n)
776 isdescendent = True
776 isdescendent = True
777 if isdescendent and ((ancestors is None) or (n in ancestors)):
777 if isdescendent and ((ancestors is None) or (n in ancestors)):
778 # Only include nodes that are both descendents and ancestors.
778 # Only include nodes that are both descendents and ancestors.
779 orderedout.append(n)
779 orderedout.append(n)
780 if (ancestors is not None) and (n in heads):
780 if (ancestors is not None) and (n in heads):
781 # We're trying to figure out which heads are reachable
781 # We're trying to figure out which heads are reachable
782 # from roots.
782 # from roots.
783 # Mark this head as having been reached
783 # Mark this head as having been reached
784 heads[n] = 1
784 heads[n] = 1
785 elif ancestors is None:
785 elif ancestors is None:
786 # Otherwise, we're trying to discover the heads.
786 # Otherwise, we're trying to discover the heads.
787 # Assume this is a head because if it isn't, the next step
787 # Assume this is a head because if it isn't, the next step
788 # will eventually remove it.
788 # will eventually remove it.
789 heads[n] = 1
789 heads[n] = 1
790 # But, obviously its parents aren't.
790 # But, obviously its parents aren't.
791 for p in self.parents(n):
791 for p in self.parents(n):
792 heads.pop(p, None)
792 heads.pop(p, None)
793 heads = [n for n in heads.iterkeys() if heads[n] != 0]
793 heads = [n for n in heads.iterkeys() if heads[n] != 0]
794 roots = list(roots)
794 roots = list(roots)
795 assert orderedout
795 assert orderedout
796 assert roots
796 assert roots
797 assert heads
797 assert heads
798 return (orderedout, roots, heads)
798 return (orderedout, roots, heads)
799
799
800 def heads(self, start=None, stop=None):
800 def heads(self, start=None, stop=None):
801 """return the list of all nodes that have no children
801 """return the list of all nodes that have no children
802
802
803 if start is specified, only heads that are descendants of
803 if start is specified, only heads that are descendants of
804 start will be returned
804 start will be returned
805 if stop is specified, it will consider all the revs from stop
805 if stop is specified, it will consider all the revs from stop
806 as if they had no children
806 as if they had no children
807 """
807 """
808 if start is None and stop is None:
808 if start is None and stop is None:
809 count = len(self)
809 count = len(self)
810 if not count:
810 if not count:
811 return [nullid]
811 return [nullid]
812 ishead = [1] * (count + 1)
812 ishead = [1] * (count + 1)
813 index = self.index
813 index = self.index
814 for r in xrange(count):
814 for r in xrange(count):
815 e = index[r]
815 e = index[r]
816 ishead[e[5]] = ishead[e[6]] = 0
816 ishead[e[5]] = ishead[e[6]] = 0
817 return [self.node(r) for r in xrange(count) if ishead[r]]
817 return [self.node(r) for r in xrange(count) if ishead[r]]
818
818
819 if start is None:
819 if start is None:
820 start = nullid
820 start = nullid
821 if stop is None:
821 if stop is None:
822 stop = []
822 stop = []
823 stoprevs = set([self.rev(n) for n in stop])
823 stoprevs = set([self.rev(n) for n in stop])
824 startrev = self.rev(start)
824 startrev = self.rev(start)
825 reachable = set((startrev,))
825 reachable = set((startrev,))
826 heads = set((startrev,))
826 heads = set((startrev,))
827
827
828 parentrevs = self.parentrevs
828 parentrevs = self.parentrevs
829 for r in xrange(startrev + 1, len(self)):
829 for r in xrange(startrev + 1, len(self)):
830 for p in parentrevs(r):
830 for p in parentrevs(r):
831 if p in reachable:
831 if p in reachable:
832 if r not in stoprevs:
832 if r not in stoprevs:
833 reachable.add(r)
833 reachable.add(r)
834 heads.add(r)
834 heads.add(r)
835 if p in heads and p not in stoprevs:
835 if p in heads and p not in stoprevs:
836 heads.remove(p)
836 heads.remove(p)
837
837
838 return [self.node(r) for r in heads]
838 return [self.node(r) for r in heads]
839
839
840 def children(self, node):
840 def children(self, node):
841 """find the children of a given node"""
841 """find the children of a given node"""
842 c = []
842 c = []
843 p = self.rev(node)
843 p = self.rev(node)
844 for r in range(p + 1, len(self)):
844 for r in range(p + 1, len(self)):
845 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
845 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
846 if prevs:
846 if prevs:
847 for pr in prevs:
847 for pr in prevs:
848 if pr == p:
848 if pr == p:
849 c.append(self.node(r))
849 c.append(self.node(r))
850 elif p == nullrev:
850 elif p == nullrev:
851 c.append(self.node(r))
851 c.append(self.node(r))
852 return c
852 return c
853
853
854 def descendant(self, start, end):
854 def descendant(self, start, end):
855 for i in self.descendants(start):
855 for i in self.descendants(start):
856 if i == end:
856 if i == end:
857 return True
857 return True
858 elif i > end:
858 elif i > end:
859 break
859 break
860 return False
860 return False
861
861
862 def ancestor(self, a, b):
862 def ancestor(self, a, b):
863 """calculate the least common ancestor of nodes a and b"""
863 """calculate the least common ancestor of nodes a and b"""
864
864
865 # fast path, check if it is a descendant
865 # fast path, check if it is a descendant
866 a, b = self.rev(a), self.rev(b)
866 a, b = self.rev(a), self.rev(b)
867 start, end = sorted((a, b))
867 start, end = sorted((a, b))
868 if self.descendant(start, end):
868 if self.descendant(start, end):
869 return self.node(start)
869 return self.node(start)
870
870
871 def parents(rev):
871 def parents(rev):
872 return [p for p in self.parentrevs(rev) if p != nullrev]
872 return [p for p in self.parentrevs(rev) if p != nullrev]
873
873
874 c = ancestor.ancestor(a, b, parents)
874 c = ancestor.ancestor(a, b, parents)
875 if c is None:
875 if c is None:
876 return nullid
876 return nullid
877
877
878 return self.node(c)
878 return self.node(c)
879
879
880 def _match(self, id):
880 def _match(self, id):
881 if isinstance(id, (long, int)):
881 if isinstance(id, (long, int)):
882 # rev
882 # rev
883 return self.node(id)
883 return self.node(id)
884 if len(id) == 20:
884 if len(id) == 20:
885 # possibly a binary node
885 # possibly a binary node
886 # odds of a binary node being all hex in ASCII are 1 in 10**25
886 # odds of a binary node being all hex in ASCII are 1 in 10**25
887 try:
887 try:
888 node = id
888 node = id
889 self.rev(node) # quick search the index
889 self.rev(node) # quick search the index
890 return node
890 return node
891 except LookupError:
891 except LookupError:
892 pass # may be partial hex id
892 pass # may be partial hex id
893 try:
893 try:
894 # str(rev)
894 # str(rev)
895 rev = int(id)
895 rev = int(id)
896 if str(rev) != id:
896 if str(rev) != id:
897 raise ValueError
897 raise ValueError
898 if rev < 0:
898 if rev < 0:
899 rev = len(self) + rev
899 rev = len(self) + rev
900 if rev < 0 or rev >= len(self):
900 if rev < 0 or rev >= len(self):
901 raise ValueError
901 raise ValueError
902 return self.node(rev)
902 return self.node(rev)
903 except (ValueError, OverflowError):
903 except (ValueError, OverflowError):
904 pass
904 pass
905 if len(id) == 40:
905 if len(id) == 40:
906 try:
906 try:
907 # a full hex nodeid?
907 # a full hex nodeid?
908 node = bin(id)
908 node = bin(id)
909 self.rev(node)
909 self.rev(node)
910 return node
910 return node
911 except (TypeError, LookupError):
911 except (TypeError, LookupError):
912 pass
912 pass
913
913
914 def _partialmatch(self, id):
914 def _partialmatch(self, id):
915 if len(id) < 40:
915 if len(id) < 40:
916 try:
916 try:
917 # hex(node)[:...]
917 # hex(node)[:...]
918 l = len(id) // 2 # grab an even number of digits
918 l = len(id) // 2 # grab an even number of digits
919 bin_id = bin(id[:l * 2])
919 bin_id = bin(id[:l * 2])
920 nl = [n for n in self.nodemap if n[:l] == bin_id]
920 nl = [n for n in self.nodemap if n[:l] == bin_id]
921 nl = [n for n in nl if hex(n).startswith(id)]
921 nl = [n for n in nl if hex(n).startswith(id)]
922 if len(nl) > 0:
922 if len(nl) > 0:
923 if len(nl) == 1:
923 if len(nl) == 1:
924 return nl[0]
924 return nl[0]
925 raise LookupError(id, self.indexfile,
925 raise LookupError(id, self.indexfile,
926 _('ambiguous identifier'))
926 _('ambiguous identifier'))
927 return None
927 return None
928 except TypeError:
928 except TypeError:
929 pass
929 pass
930
930
931 def lookup(self, id):
931 def lookup(self, id):
932 """locate a node based on:
932 """locate a node based on:
933 - revision number or str(revision number)
933 - revision number or str(revision number)
934 - nodeid or subset of hex nodeid
934 - nodeid or subset of hex nodeid
935 """
935 """
936 n = self._match(id)
936 n = self._match(id)
937 if n is not None:
937 if n is not None:
938 return n
938 return n
939 n = self._partialmatch(id)
939 n = self._partialmatch(id)
940 if n:
940 if n:
941 return n
941 return n
942
942
943 raise LookupError(id, self.indexfile, _('no match found'))
943 raise LookupError(id, self.indexfile, _('no match found'))
944
944
945 def cmp(self, node, text):
945 def cmp(self, node, text):
946 """compare text with a given file revision
946 """compare text with a given file revision
947
947
948 returns True if text is different than what is stored.
948 returns True if text is different than what is stored.
949 """
949 """
950 p1, p2 = self.parents(node)
950 p1, p2 = self.parents(node)
951 return hash(text, p1, p2) != node
951 return hash(text, p1, p2) != node
952
952
953 def _addchunk(self, offset, data):
953 def _addchunk(self, offset, data):
954 o, d = self._chunkcache
954 o, d = self._chunkcache
955 # try to add to existing cache
955 # try to add to existing cache
956 if o + len(d) == offset and len(d) + len(data) < _prereadsize:
956 if o + len(d) == offset and len(d) + len(data) < _prereadsize:
957 self._chunkcache = o, d + data
957 self._chunkcache = o, d + data
958 else:
958 else:
959 self._chunkcache = offset, data
959 self._chunkcache = offset, data
960
960
961 def _loadchunk(self, offset, length):
961 def _loadchunk(self, offset, length):
962 if self._inline:
962 if self._inline:
963 df = self.opener(self.indexfile)
963 df = self.opener(self.indexfile)
964 else:
964 else:
965 df = self.opener(self.datafile)
965 df = self.opener(self.datafile)
966
966
967 readahead = max(65536, length)
967 readahead = max(65536, length)
968 df.seek(offset)
968 df.seek(offset)
969 d = df.read(readahead)
969 d = df.read(readahead)
970 self._addchunk(offset, d)
970 self._addchunk(offset, d)
971 if readahead > length:
971 if readahead > length:
972 return d[:length]
972 return d[:length]
973 return d
973 return d
974
974
975 def _getchunk(self, offset, length):
975 def _getchunk(self, offset, length):
976 o, d = self._chunkcache
976 o, d = self._chunkcache
977 l = len(d)
977 l = len(d)
978
978
979 # is it in the cache?
979 # is it in the cache?
980 cachestart = offset - o
980 cachestart = offset - o
981 cacheend = cachestart + length
981 cacheend = cachestart + length
982 if cachestart >= 0 and cacheend <= l:
982 if cachestart >= 0 and cacheend <= l:
983 if cachestart == 0 and cacheend == l:
983 if cachestart == 0 and cacheend == l:
984 return d # avoid a copy
984 return d # avoid a copy
985 return d[cachestart:cacheend]
985 return d[cachestart:cacheend]
986
986
987 return self._loadchunk(offset, length)
987 return self._loadchunk(offset, length)
988
988
989 def _chunkraw(self, startrev, endrev):
989 def _chunkraw(self, startrev, endrev):
990 start = self.start(startrev)
990 start = self.start(startrev)
991 length = self.end(endrev) - start
991 length = self.end(endrev) - start
992 if self._inline:
992 if self._inline:
993 start += (startrev + 1) * self._io.size
993 start += (startrev + 1) * self._io.size
994 return self._getchunk(start, length)
994 return self._getchunk(start, length)
995
995
996 def _chunk(self, rev):
996 def _chunk(self, rev):
997 return decompress(self._chunkraw(rev, rev))
997 return decompress(self._chunkraw(rev, rev))
998
998
999 def _chunkclear(self):
999 def _chunkclear(self):
1000 self._chunkcache = (0, '')
1000 self._chunkcache = (0, '')
1001
1001
1002 def revdiff(self, rev1, rev2):
1002 def revdiff(self, rev1, rev2):
1003 """return or calculate a delta between two revisions"""
1003 """return or calculate a delta between two revisions"""
1004 if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
1004 if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
1005 return self._chunk(rev2)
1005 return self._chunk(rev2)
1006
1006
1007 return mdiff.textdiff(self.revision(self.node(rev1)),
1007 return mdiff.textdiff(self.revision(self.node(rev1)),
1008 self.revision(self.node(rev2)))
1008 self.revision(self.node(rev2)))
1009
1009
1010 def revision(self, node):
1010 def revision(self, node):
1011 """return an uncompressed revision of a given node"""
1011 """return an uncompressed revision of a given node"""
1012 if node == nullid:
1012 if node == nullid:
1013 return ""
1013 return ""
1014 if self._cache and self._cache[0] == node:
1014 if self._cache and self._cache[0] == node:
1015 return self._cache[2]
1015 return self._cache[2]
1016
1016
1017 # look up what we need to read
1017 # look up what we need to read
1018 text = None
1018 text = None
1019 rev = self.rev(node)
1019 rev = self.rev(node)
1020 base = self.base(rev)
1020 base = self.base(rev)
1021
1021
1022 # check rev flags
1022 # check rev flags
1023 if self.index[rev][0] & 0xFFFF:
1023 if self.index[rev][0] & 0xFFFF:
1024 raise RevlogError(_('incompatible revision flag %x') %
1024 raise RevlogError(_('incompatible revision flag %x') %
1025 (self.index[rev][0] & 0xFFFF))
1025 (self.index[rev][0] & 0xFFFF))
1026
1026
1027 # do we have useful data cached?
1027 # do we have useful data cached?
1028 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
1028 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
1029 base = self._cache[1]
1029 base = self._cache[1]
1030 text = self._cache[2]
1030 text = self._cache[2]
1031
1031
1032 self._loadindex(base, rev + 1)
1032 self._loadindex(base, rev + 1)
1033 self._chunkraw(base, rev)
1033 self._chunkraw(base, rev)
1034 if text is None:
1034 if text is None:
1035 text = self._chunk(base)
1035 text = self._chunk(base)
1036
1036
1037 bins = [self._chunk(r) for r in xrange(base + 1, rev + 1)]
1037 bins = [self._chunk(r) for r in xrange(base + 1, rev + 1)]
1038 text = mdiff.patches(text, bins)
1038 text = mdiff.patches(text, bins)
1039 p1, p2 = self.parents(node)
1039 p1, p2 = self.parents(node)
1040 if node != hash(text, p1, p2):
1040 if node != hash(text, p1, p2):
1041 raise RevlogError(_("integrity check failed on %s:%d")
1041 raise RevlogError(_("integrity check failed on %s:%d")
1042 % (self.indexfile, rev))
1042 % (self.indexfile, rev))
1043
1043
1044 self._cache = (node, rev, text)
1044 self._cache = (node, rev, text)
1045 return text
1045 return text
1046
1046
1047 def checkinlinesize(self, tr, fp=None):
1047 def checkinlinesize(self, tr, fp=None):
1048 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1048 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1049 return
1049 return
1050
1050
1051 trinfo = tr.find(self.indexfile)
1051 trinfo = tr.find(self.indexfile)
1052 if trinfo is None:
1052 if trinfo is None:
1053 raise RevlogError(_("%s not found in the transaction")
1053 raise RevlogError(_("%s not found in the transaction")
1054 % self.indexfile)
1054 % self.indexfile)
1055
1055
1056 trindex = trinfo[2]
1056 trindex = trinfo[2]
1057 dataoff = self.start(trindex)
1057 dataoff = self.start(trindex)
1058
1058
1059 tr.add(self.datafile, dataoff)
1059 tr.add(self.datafile, dataoff)
1060
1060
1061 if fp:
1061 if fp:
1062 fp.flush()
1062 fp.flush()
1063 fp.close()
1063 fp.close()
1064
1064
1065 df = self.opener(self.datafile, 'w')
1065 df = self.opener(self.datafile, 'w')
1066 try:
1066 try:
1067 for r in self:
1067 for r in self:
1068 df.write(self._chunkraw(r, r))
1068 df.write(self._chunkraw(r, r))
1069 finally:
1069 finally:
1070 df.close()
1070 df.close()
1071
1071
1072 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1072 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1073 self.version &= ~(REVLOGNGINLINEDATA)
1073 self.version &= ~(REVLOGNGINLINEDATA)
1074 self._inline = False
1074 self._inline = False
1075 for i in self:
1075 for i in self:
1076 e = self._io.packentry(self.index[i], self.node, self.version, i)
1076 e = self._io.packentry(self.index[i], self.node, self.version, i)
1077 fp.write(e)
1077 fp.write(e)
1078
1078
1079 # if we don't call rename, the temp file will never replace the
1079 # if we don't call rename, the temp file will never replace the
1080 # real index
1080 # real index
1081 fp.rename()
1081 fp.rename()
1082
1082
1083 tr.replace(self.indexfile, trindex * self._io.size)
1083 tr.replace(self.indexfile, trindex * self._io.size)
1084 self._chunkclear()
1084 self._chunkclear()
1085
1085
1086 def addrevision(self, text, transaction, link, p1, p2, d=None):
1086 def addrevision(self, text, transaction, link, p1, p2, d=None):
1087 """add a revision to the log
1087 """add a revision to the log
1088
1088
1089 text - the revision data to add
1089 text - the revision data to add
1090 transaction - the transaction object used for rollback
1090 transaction - the transaction object used for rollback
1091 link - the linkrev data to add
1091 link - the linkrev data to add
1092 p1, p2 - the parent nodeids of the revision
1092 p1, p2 - the parent nodeids of the revision
1093 d - an optional precomputed delta
1093 d - an optional precomputed delta
1094 """
1094 """
1095 dfh = None
1095 dfh = None
1096 if not self._inline:
1096 if not self._inline:
1097 dfh = self.opener(self.datafile, "a")
1097 dfh = self.opener(self.datafile, "a")
1098 ifh = self.opener(self.indexfile, "a+")
1098 ifh = self.opener(self.indexfile, "a+")
1099 try:
1099 try:
1100 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1100 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1101 finally:
1101 finally:
1102 if dfh:
1102 if dfh:
1103 dfh.close()
1103 dfh.close()
1104 ifh.close()
1104 ifh.close()
1105
1105
1106 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1106 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1107 node = hash(text, p1, p2)
1107 node = hash(text, p1, p2)
1108 if node in self.nodemap:
1108 if node in self.nodemap:
1109 return node
1109 return node
1110
1110
1111 curr = len(self)
1111 curr = len(self)
1112 prev = curr - 1
1112 prev = curr - 1
1113 base = self.base(prev)
1113 base = self.base(prev)
1114 offset = self.end(prev)
1114 offset = self.end(prev)
1115
1115
1116 if curr:
1116 if curr:
1117 if not d:
1117 if not d:
1118 ptext = self.revision(self.node(prev))
1118 ptext = self.revision(self.node(prev))
1119 d = mdiff.textdiff(ptext, text)
1119 d = mdiff.textdiff(ptext, text)
1120 data = compress(d)
1120 data = compress(d)
1121 l = len(data[1]) + len(data[0])
1121 l = len(data[1]) + len(data[0])
1122 dist = l + offset - self.start(base)
1122 dist = l + offset - self.start(base)
1123
1123
1124 # full versions are inserted when the needed deltas
1124 # full versions are inserted when the needed deltas
1125 # become comparable to the uncompressed text
1125 # become comparable to the uncompressed text
1126 if not curr or dist > len(text) * 2:
1126 if not curr or dist > len(text) * 2:
1127 data = compress(text)
1127 data = compress(text)
1128 l = len(data[1]) + len(data[0])
1128 l = len(data[1]) + len(data[0])
1129 base = curr
1129 base = curr
1130
1130
1131 e = (offset_type(offset, 0), l, len(text),
1131 e = (offset_type(offset, 0), l, len(text),
1132 base, link, self.rev(p1), self.rev(p2), node)
1132 base, link, self.rev(p1), self.rev(p2), node)
1133 self.index.insert(-1, e)
1133 self.index.insert(-1, e)
1134 self.nodemap[node] = curr
1134 self.nodemap[node] = curr
1135
1135
1136 entry = self._io.packentry(e, self.node, self.version, curr)
1136 entry = self._io.packentry(e, self.node, self.version, curr)
1137 if not self._inline:
1137 if not self._inline:
1138 transaction.add(self.datafile, offset)
1138 transaction.add(self.datafile, offset)
1139 transaction.add(self.indexfile, curr * len(entry))
1139 transaction.add(self.indexfile, curr * len(entry))
1140 if data[0]:
1140 if data[0]:
1141 dfh.write(data[0])
1141 dfh.write(data[0])
1142 dfh.write(data[1])
1142 dfh.write(data[1])
1143 dfh.flush()
1143 dfh.flush()
1144 ifh.write(entry)
1144 ifh.write(entry)
1145 else:
1145 else:
1146 offset += curr * self._io.size
1146 offset += curr * self._io.size
1147 transaction.add(self.indexfile, offset, curr)
1147 transaction.add(self.indexfile, offset, curr)
1148 ifh.write(entry)
1148 ifh.write(entry)
1149 ifh.write(data[0])
1149 ifh.write(data[0])
1150 ifh.write(data[1])
1150 ifh.write(data[1])
1151 self.checkinlinesize(transaction, ifh)
1151 self.checkinlinesize(transaction, ifh)
1152
1152
1153 if type(text) == str: # only accept immutable objects
1153 if type(text) == str: # only accept immutable objects
1154 self._cache = (node, curr, text)
1154 self._cache = (node, curr, text)
1155 return node
1155 return node
1156
1156
1157 def group(self, nodelist, lookup, infocollect=None):
1157 def group(self, nodelist, lookup, infocollect=None):
1158 """Calculate a delta group, yielding a sequence of changegroup chunks
1158 """Calculate a delta group, yielding a sequence of changegroup chunks
1159 (strings).
1159 (strings).
1160
1160
1161 Given a list of changeset revs, return a set of deltas and
1161 Given a list of changeset revs, return a set of deltas and
1162 metadata corresponding to nodes. the first delta is
1162 metadata corresponding to nodes. the first delta is
1163 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1163 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1164 have this parent as it has all history before these
1164 have this parent as it has all history before these
1165 changesets. parent is parent[0]
1165 changesets. parent is parent[0]
1166 """
1166 """
1167
1167
1168 revs = [self.rev(n) for n in nodelist]
1168 revs = [self.rev(n) for n in nodelist]
1169
1169
1170 # if we don't have any revisions touched by these changesets, bail
1170 # if we don't have any revisions touched by these changesets, bail
1171 if not revs:
1171 if not revs:
1172 yield changegroup.closechunk()
1172 yield changegroup.closechunk()
1173 return
1173 return
1174
1174
1175 # add the parent of the first rev
1175 # add the parent of the first rev
1176 p = self.parentrevs(revs[0])[0]
1176 p = self.parentrevs(revs[0])[0]
1177 revs.insert(0, p)
1177 revs.insert(0, p)
1178
1178
1179 # build deltas
1179 # build deltas
1180 for d in xrange(len(revs) - 1):
1180 for d in xrange(len(revs) - 1):
1181 a, b = revs[d], revs[d + 1]
1181 a, b = revs[d], revs[d + 1]
1182 nb = self.node(b)
1182 nb = self.node(b)
1183
1183
1184 if infocollect is not None:
1184 if infocollect is not None:
1185 infocollect(nb)
1185 infocollect(nb)
1186
1186
1187 p = self.parents(nb)
1187 p = self.parents(nb)
1188 meta = nb + p[0] + p[1] + lookup(nb)
1188 meta = nb + p[0] + p[1] + lookup(nb)
1189 if a == -1:
1189 if a == -1:
1190 d = self.revision(nb)
1190 d = self.revision(nb)
1191 meta += mdiff.trivialdiffheader(len(d))
1191 meta += mdiff.trivialdiffheader(len(d))
1192 else:
1192 else:
1193 d = self.revdiff(a, b)
1193 d = self.revdiff(a, b)
1194 yield changegroup.chunkheader(len(meta) + len(d))
1194 yield changegroup.chunkheader(len(meta) + len(d))
1195 yield meta
1195 yield meta
1196 if len(d) > 2**20:
1196 yield d
1197 pos = 0
1198 while pos < len(d):
1199 pos2 = pos + 2 ** 18
1200 yield d[pos:pos2]
1201 pos = pos2
1202 else:
1203 yield d
1204
1197
1205 yield changegroup.closechunk()
1198 yield changegroup.closechunk()
1206
1199
1207 def addgroup(self, revs, linkmapper, transaction):
1200 def addgroup(self, revs, linkmapper, transaction):
1208 """
1201 """
1209 add a delta group
1202 add a delta group
1210
1203
1211 given a set of deltas, add them to the revision log. the
1204 given a set of deltas, add them to the revision log. the
1212 first delta is against its parent, which should be in our
1205 first delta is against its parent, which should be in our
1213 log, the rest are against the previous delta.
1206 log, the rest are against the previous delta.
1214 """
1207 """
1215
1208
1216 #track the base of the current delta log
1209 #track the base of the current delta log
1217 r = len(self)
1210 r = len(self)
1218 t = r - 1
1211 t = r - 1
1219 node = None
1212 node = None
1220
1213
1221 base = prev = nullrev
1214 base = prev = nullrev
1222 start = end = textlen = 0
1215 start = end = textlen = 0
1223 if r:
1216 if r:
1224 end = self.end(t)
1217 end = self.end(t)
1225
1218
1226 ifh = self.opener(self.indexfile, "a+")
1219 ifh = self.opener(self.indexfile, "a+")
1227 isize = r * self._io.size
1220 isize = r * self._io.size
1228 if self._inline:
1221 if self._inline:
1229 transaction.add(self.indexfile, end + isize, r)
1222 transaction.add(self.indexfile, end + isize, r)
1230 dfh = None
1223 dfh = None
1231 else:
1224 else:
1232 transaction.add(self.indexfile, isize, r)
1225 transaction.add(self.indexfile, isize, r)
1233 transaction.add(self.datafile, end)
1226 transaction.add(self.datafile, end)
1234 dfh = self.opener(self.datafile, "a")
1227 dfh = self.opener(self.datafile, "a")
1235
1228
1236 try:
1229 try:
1237 # loop through our set of deltas
1230 # loop through our set of deltas
1238 chain = None
1231 chain = None
1239 for chunk in revs:
1232 for chunk in revs:
1240 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1233 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1241 link = linkmapper(cs)
1234 link = linkmapper(cs)
1242 if node in self.nodemap:
1235 if node in self.nodemap:
1243 # this can happen if two branches make the same change
1236 # this can happen if two branches make the same change
1244 chain = node
1237 chain = node
1245 continue
1238 continue
1246 delta = buffer(chunk, 80)
1239 delta = buffer(chunk, 80)
1247 del chunk
1240 del chunk
1248
1241
1249 for p in (p1, p2):
1242 for p in (p1, p2):
1250 if not p in self.nodemap:
1243 if not p in self.nodemap:
1251 raise LookupError(p, self.indexfile, _('unknown parent'))
1244 raise LookupError(p, self.indexfile, _('unknown parent'))
1252
1245
1253 if not chain:
1246 if not chain:
1254 # retrieve the parent revision of the delta chain
1247 # retrieve the parent revision of the delta chain
1255 chain = p1
1248 chain = p1
1256 if not chain in self.nodemap:
1249 if not chain in self.nodemap:
1257 raise LookupError(chain, self.indexfile, _('unknown base'))
1250 raise LookupError(chain, self.indexfile, _('unknown base'))
1258
1251
1259 # full versions are inserted when the needed deltas become
1252 # full versions are inserted when the needed deltas become
1260 # comparable to the uncompressed text or when the previous
1253 # comparable to the uncompressed text or when the previous
1261 # version is not the one we have a delta against. We use
1254 # version is not the one we have a delta against. We use
1262 # the size of the previous full rev as a proxy for the
1255 # the size of the previous full rev as a proxy for the
1263 # current size.
1256 # current size.
1264
1257
1265 if chain == prev:
1258 if chain == prev:
1266 cdelta = compress(delta)
1259 cdelta = compress(delta)
1267 cdeltalen = len(cdelta[0]) + len(cdelta[1])
1260 cdeltalen = len(cdelta[0]) + len(cdelta[1])
1268 textlen = mdiff.patchedsize(textlen, delta)
1261 textlen = mdiff.patchedsize(textlen, delta)
1269
1262
1270 if chain != prev or (end - start + cdeltalen) > textlen * 2:
1263 if chain != prev or (end - start + cdeltalen) > textlen * 2:
1271 # flush our writes here so we can read it in revision
1264 # flush our writes here so we can read it in revision
1272 if dfh:
1265 if dfh:
1273 dfh.flush()
1266 dfh.flush()
1274 ifh.flush()
1267 ifh.flush()
1275 text = self.revision(chain)
1268 text = self.revision(chain)
1276 if len(text) == 0:
1269 if len(text) == 0:
1277 # skip over trivial delta header
1270 # skip over trivial delta header
1278 text = buffer(delta, 12)
1271 text = buffer(delta, 12)
1279 else:
1272 else:
1280 text = mdiff.patches(text, [delta])
1273 text = mdiff.patches(text, [delta])
1281 del delta
1274 del delta
1282 chk = self._addrevision(text, transaction, link, p1, p2, None,
1275 chk = self._addrevision(text, transaction, link, p1, p2, None,
1283 ifh, dfh)
1276 ifh, dfh)
1284 if not dfh and not self._inline:
1277 if not dfh and not self._inline:
1285 # addrevision switched from inline to conventional
1278 # addrevision switched from inline to conventional
1286 # reopen the index
1279 # reopen the index
1287 dfh = self.opener(self.datafile, "a")
1280 dfh = self.opener(self.datafile, "a")
1288 ifh = self.opener(self.indexfile, "a")
1281 ifh = self.opener(self.indexfile, "a")
1289 if chk != node:
1282 if chk != node:
1290 raise RevlogError(_("consistency error adding group"))
1283 raise RevlogError(_("consistency error adding group"))
1291 textlen = len(text)
1284 textlen = len(text)
1292 else:
1285 else:
1293 e = (offset_type(end, 0), cdeltalen, textlen, base,
1286 e = (offset_type(end, 0), cdeltalen, textlen, base,
1294 link, self.rev(p1), self.rev(p2), node)
1287 link, self.rev(p1), self.rev(p2), node)
1295 self.index.insert(-1, e)
1288 self.index.insert(-1, e)
1296 self.nodemap[node] = r
1289 self.nodemap[node] = r
1297 entry = self._io.packentry(e, self.node, self.version, r)
1290 entry = self._io.packentry(e, self.node, self.version, r)
1298 if self._inline:
1291 if self._inline:
1299 ifh.write(entry)
1292 ifh.write(entry)
1300 ifh.write(cdelta[0])
1293 ifh.write(cdelta[0])
1301 ifh.write(cdelta[1])
1294 ifh.write(cdelta[1])
1302 self.checkinlinesize(transaction, ifh)
1295 self.checkinlinesize(transaction, ifh)
1303 if not self._inline:
1296 if not self._inline:
1304 dfh = self.opener(self.datafile, "a")
1297 dfh = self.opener(self.datafile, "a")
1305 ifh = self.opener(self.indexfile, "a")
1298 ifh = self.opener(self.indexfile, "a")
1306 else:
1299 else:
1307 dfh.write(cdelta[0])
1300 dfh.write(cdelta[0])
1308 dfh.write(cdelta[1])
1301 dfh.write(cdelta[1])
1309 ifh.write(entry)
1302 ifh.write(entry)
1310
1303
1311 t, r, chain, prev = r, r + 1, node, node
1304 t, r, chain, prev = r, r + 1, node, node
1312 base = self.base(t)
1305 base = self.base(t)
1313 start = self.start(base)
1306 start = self.start(base)
1314 end = self.end(t)
1307 end = self.end(t)
1315 finally:
1308 finally:
1316 if dfh:
1309 if dfh:
1317 dfh.close()
1310 dfh.close()
1318 ifh.close()
1311 ifh.close()
1319
1312
1320 return node
1313 return node
1321
1314
1322 def strip(self, minlink, transaction):
1315 def strip(self, minlink, transaction):
1323 """truncate the revlog on the first revision with a linkrev >= minlink
1316 """truncate the revlog on the first revision with a linkrev >= minlink
1324
1317
1325 This function is called when we're stripping revision minlink and
1318 This function is called when we're stripping revision minlink and
1326 its descendants from the repository.
1319 its descendants from the repository.
1327
1320
1328 We have to remove all revisions with linkrev >= minlink, because
1321 We have to remove all revisions with linkrev >= minlink, because
1329 the equivalent changelog revisions will be renumbered after the
1322 the equivalent changelog revisions will be renumbered after the
1330 strip.
1323 strip.
1331
1324
1332 So we truncate the revlog on the first of these revisions, and
1325 So we truncate the revlog on the first of these revisions, and
1333 trust that the caller has saved the revisions that shouldn't be
1326 trust that the caller has saved the revisions that shouldn't be
1334 removed and that it'll readd them after this truncation.
1327 removed and that it'll readd them after this truncation.
1335 """
1328 """
1336 if len(self) == 0:
1329 if len(self) == 0:
1337 return
1330 return
1338
1331
1339 if isinstance(self.index, lazyindex):
1332 if isinstance(self.index, lazyindex):
1340 self._loadindexmap()
1333 self._loadindexmap()
1341
1334
1342 for rev in self:
1335 for rev in self:
1343 if self.index[rev][4] >= minlink:
1336 if self.index[rev][4] >= minlink:
1344 break
1337 break
1345 else:
1338 else:
1346 return
1339 return
1347
1340
1348 # first truncate the files on disk
1341 # first truncate the files on disk
1349 end = self.start(rev)
1342 end = self.start(rev)
1350 if not self._inline:
1343 if not self._inline:
1351 transaction.add(self.datafile, end)
1344 transaction.add(self.datafile, end)
1352 end = rev * self._io.size
1345 end = rev * self._io.size
1353 else:
1346 else:
1354 end += rev * self._io.size
1347 end += rev * self._io.size
1355
1348
1356 transaction.add(self.indexfile, end)
1349 transaction.add(self.indexfile, end)
1357
1350
1358 # then reset internal state in memory to forget those revisions
1351 # then reset internal state in memory to forget those revisions
1359 self._cache = None
1352 self._cache = None
1360 self._chunkclear()
1353 self._chunkclear()
1361 for x in xrange(rev, len(self)):
1354 for x in xrange(rev, len(self)):
1362 del self.nodemap[self.node(x)]
1355 del self.nodemap[self.node(x)]
1363
1356
1364 del self.index[rev:-1]
1357 del self.index[rev:-1]
1365
1358
1366 def checksize(self):
1359 def checksize(self):
1367 expected = 0
1360 expected = 0
1368 if len(self):
1361 if len(self):
1369 expected = max(0, self.end(len(self) - 1))
1362 expected = max(0, self.end(len(self) - 1))
1370
1363
1371 try:
1364 try:
1372 f = self.opener(self.datafile)
1365 f = self.opener(self.datafile)
1373 f.seek(0, 2)
1366 f.seek(0, 2)
1374 actual = f.tell()
1367 actual = f.tell()
1375 dd = actual - expected
1368 dd = actual - expected
1376 except IOError, inst:
1369 except IOError, inst:
1377 if inst.errno != errno.ENOENT:
1370 if inst.errno != errno.ENOENT:
1378 raise
1371 raise
1379 dd = 0
1372 dd = 0
1380
1373
1381 try:
1374 try:
1382 f = self.opener(self.indexfile)
1375 f = self.opener(self.indexfile)
1383 f.seek(0, 2)
1376 f.seek(0, 2)
1384 actual = f.tell()
1377 actual = f.tell()
1385 s = self._io.size
1378 s = self._io.size
1386 i = max(0, actual // s)
1379 i = max(0, actual // s)
1387 di = actual - (i * s)
1380 di = actual - (i * s)
1388 if self._inline:
1381 if self._inline:
1389 databytes = 0
1382 databytes = 0
1390 for r in self:
1383 for r in self:
1391 databytes += max(0, self.length(r))
1384 databytes += max(0, self.length(r))
1392 dd = 0
1385 dd = 0
1393 di = actual - len(self) * s - databytes
1386 di = actual - len(self) * s - databytes
1394 except IOError, inst:
1387 except IOError, inst:
1395 if inst.errno != errno.ENOENT:
1388 if inst.errno != errno.ENOENT:
1396 raise
1389 raise
1397 di = 0
1390 di = 0
1398
1391
1399 return (dd, di)
1392 return (dd, di)
1400
1393
1401 def files(self):
1394 def files(self):
1402 res = [self.indexfile]
1395 res = [self.indexfile]
1403 if not self._inline:
1396 if not self._inline:
1404 res.append(self.datafile)
1397 res.append(self.datafile)
1405 return res
1398 return res
@@ -1,1394 +1,1404 b''
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, stat, time, calendar, textwrap, unicodedata, signal
19 import os, stat, time, calendar, textwrap, unicodedata, signal
20 import imp
20 import imp
21
21
22 # Python compatibility
22 # Python compatibility
23
23
24 def sha1(s):
24 def sha1(s):
25 return _fastsha1(s)
25 return _fastsha1(s)
26
26
27 def _fastsha1(s):
27 def _fastsha1(s):
28 # This function will import sha1 from hashlib or sha (whichever is
28 # This function will import sha1 from hashlib or sha (whichever is
29 # available) and overwrite itself with it on the first call.
29 # available) and overwrite itself with it on the first call.
30 # Subsequent calls will go directly to the imported function.
30 # Subsequent calls will go directly to the imported function.
31 try:
31 try:
32 from hashlib import sha1 as _sha1
32 from hashlib import sha1 as _sha1
33 except ImportError:
33 except ImportError:
34 from sha import sha as _sha1
34 from sha import sha as _sha1
35 global _fastsha1, sha1
35 global _fastsha1, sha1
36 _fastsha1 = sha1 = _sha1
36 _fastsha1 = sha1 = _sha1
37 return _sha1(s)
37 return _sha1(s)
38
38
39 import __builtin__
39 import __builtin__
40
40
41 if sys.version_info[0] < 3:
41 if sys.version_info[0] < 3:
42 def fakebuffer(sliceable, offset=0):
42 def fakebuffer(sliceable, offset=0):
43 return sliceable[offset:]
43 return sliceable[offset:]
44 else:
44 else:
45 def fakebuffer(sliceable, offset=0):
45 def fakebuffer(sliceable, offset=0):
46 return memoryview(sliceable)[offset:]
46 return memoryview(sliceable)[offset:]
47 try:
47 try:
48 buffer
48 buffer
49 except NameError:
49 except NameError:
50 __builtin__.buffer = fakebuffer
50 __builtin__.buffer = fakebuffer
51
51
52 import subprocess
52 import subprocess
53 closefds = os.name == 'posix'
53 closefds = os.name == 'posix'
54
54
55 def popen2(cmd, env=None, newlines=False):
55 def popen2(cmd, env=None, newlines=False):
56 # Setting bufsize to -1 lets the system decide the buffer size.
56 # Setting bufsize to -1 lets the system decide the buffer size.
57 # The default for bufsize is 0, meaning unbuffered. This leads to
57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 close_fds=closefds,
60 close_fds=closefds,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 universal_newlines=newlines,
62 universal_newlines=newlines,
63 env=env)
63 env=env)
64 return p.stdin, p.stdout
64 return p.stdin, p.stdout
65
65
66 def popen3(cmd, env=None, newlines=False):
66 def popen3(cmd, env=None, newlines=False):
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 close_fds=closefds,
68 close_fds=closefds,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 stderr=subprocess.PIPE,
70 stderr=subprocess.PIPE,
71 universal_newlines=newlines,
71 universal_newlines=newlines,
72 env=env)
72 env=env)
73 return p.stdin, p.stdout, p.stderr
73 return p.stdin, p.stdout, p.stderr
74
74
75 def version():
75 def version():
76 """Return version information if available."""
76 """Return version information if available."""
77 try:
77 try:
78 import __version__
78 import __version__
79 return __version__.version
79 return __version__.version
80 except ImportError:
80 except ImportError:
81 return 'unknown'
81 return 'unknown'
82
82
83 # used by parsedate
83 # used by parsedate
84 defaultdateformats = (
84 defaultdateformats = (
85 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %H:%M:%S',
86 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %I:%M:%S%p',
87 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %H:%M',
88 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d %I:%M%p',
89 '%Y-%m-%d',
89 '%Y-%m-%d',
90 '%m-%d',
90 '%m-%d',
91 '%m/%d',
91 '%m/%d',
92 '%m/%d/%y',
92 '%m/%d/%y',
93 '%m/%d/%Y',
93 '%m/%d/%Y',
94 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %H:%M:%S %Y',
95 '%a %b %d %I:%M:%S%p %Y',
95 '%a %b %d %I:%M:%S%p %Y',
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 '%b %d %H:%M:%S %Y',
97 '%b %d %H:%M:%S %Y',
98 '%b %d %I:%M:%S%p %Y',
98 '%b %d %I:%M:%S%p %Y',
99 '%b %d %H:%M:%S',
99 '%b %d %H:%M:%S',
100 '%b %d %I:%M:%S%p',
100 '%b %d %I:%M:%S%p',
101 '%b %d %H:%M',
101 '%b %d %H:%M',
102 '%b %d %I:%M%p',
102 '%b %d %I:%M%p',
103 '%b %d %Y',
103 '%b %d %Y',
104 '%b %d',
104 '%b %d',
105 '%H:%M:%S',
105 '%H:%M:%S',
106 '%I:%M:%S%p',
106 '%I:%M:%S%p',
107 '%H:%M',
107 '%H:%M',
108 '%I:%M%p',
108 '%I:%M%p',
109 )
109 )
110
110
111 extendeddateformats = defaultdateformats + (
111 extendeddateformats = defaultdateformats + (
112 "%Y",
112 "%Y",
113 "%Y-%m",
113 "%Y-%m",
114 "%b",
114 "%b",
115 "%b %Y",
115 "%b %Y",
116 )
116 )
117
117
118 def cachefunc(func):
118 def cachefunc(func):
119 '''cache the result of function calls'''
119 '''cache the result of function calls'''
120 # XXX doesn't handle keywords args
120 # XXX doesn't handle keywords args
121 cache = {}
121 cache = {}
122 if func.func_code.co_argcount == 1:
122 if func.func_code.co_argcount == 1:
123 # we gain a small amount of time because
123 # we gain a small amount of time because
124 # we don't need to pack/unpack the list
124 # we don't need to pack/unpack the list
125 def f(arg):
125 def f(arg):
126 if arg not in cache:
126 if arg not in cache:
127 cache[arg] = func(arg)
127 cache[arg] = func(arg)
128 return cache[arg]
128 return cache[arg]
129 else:
129 else:
130 def f(*args):
130 def f(*args):
131 if args not in cache:
131 if args not in cache:
132 cache[args] = func(*args)
132 cache[args] = func(*args)
133 return cache[args]
133 return cache[args]
134
134
135 return f
135 return f
136
136
137 def lrucachefunc(func):
137 def lrucachefunc(func):
138 '''cache most recent results of function calls'''
138 '''cache most recent results of function calls'''
139 cache = {}
139 cache = {}
140 order = []
140 order = []
141 if func.func_code.co_argcount == 1:
141 if func.func_code.co_argcount == 1:
142 def f(arg):
142 def f(arg):
143 if arg not in cache:
143 if arg not in cache:
144 if len(cache) > 20:
144 if len(cache) > 20:
145 del cache[order.pop(0)]
145 del cache[order.pop(0)]
146 cache[arg] = func(arg)
146 cache[arg] = func(arg)
147 else:
147 else:
148 order.remove(arg)
148 order.remove(arg)
149 order.append(arg)
149 order.append(arg)
150 return cache[arg]
150 return cache[arg]
151 else:
151 else:
152 def f(*args):
152 def f(*args):
153 if args not in cache:
153 if args not in cache:
154 if len(cache) > 20:
154 if len(cache) > 20:
155 del cache[order.pop(0)]
155 del cache[order.pop(0)]
156 cache[args] = func(*args)
156 cache[args] = func(*args)
157 else:
157 else:
158 order.remove(args)
158 order.remove(args)
159 order.append(args)
159 order.append(args)
160 return cache[args]
160 return cache[args]
161
161
162 return f
162 return f
163
163
164 class propertycache(object):
164 class propertycache(object):
165 def __init__(self, func):
165 def __init__(self, func):
166 self.func = func
166 self.func = func
167 self.name = func.__name__
167 self.name = func.__name__
168 def __get__(self, obj, type=None):
168 def __get__(self, obj, type=None):
169 result = self.func(obj)
169 result = self.func(obj)
170 setattr(obj, self.name, result)
170 setattr(obj, self.name, result)
171 return result
171 return result
172
172
173 def pipefilter(s, cmd):
173 def pipefilter(s, cmd):
174 '''filter string S through command CMD, returning its output'''
174 '''filter string S through command CMD, returning its output'''
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 pout, perr = p.communicate(s)
177 pout, perr = p.communicate(s)
178 return pout
178 return pout
179
179
180 def tempfilter(s, cmd):
180 def tempfilter(s, cmd):
181 '''filter string S through a pair of temporary files with CMD.
181 '''filter string S through a pair of temporary files with CMD.
182 CMD is used as a template to create the real command to be run,
182 CMD is used as a template to create the real command to be run,
183 with the strings INFILE and OUTFILE replaced by the real names of
183 with the strings INFILE and OUTFILE replaced by the real names of
184 the temporary files generated.'''
184 the temporary files generated.'''
185 inname, outname = None, None
185 inname, outname = None, None
186 try:
186 try:
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 fp = os.fdopen(infd, 'wb')
188 fp = os.fdopen(infd, 'wb')
189 fp.write(s)
189 fp.write(s)
190 fp.close()
190 fp.close()
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 os.close(outfd)
192 os.close(outfd)
193 cmd = cmd.replace('INFILE', inname)
193 cmd = cmd.replace('INFILE', inname)
194 cmd = cmd.replace('OUTFILE', outname)
194 cmd = cmd.replace('OUTFILE', outname)
195 code = os.system(cmd)
195 code = os.system(cmd)
196 if sys.platform == 'OpenVMS' and code & 1:
196 if sys.platform == 'OpenVMS' and code & 1:
197 code = 0
197 code = 0
198 if code:
198 if code:
199 raise Abort(_("command '%s' failed: %s") %
199 raise Abort(_("command '%s' failed: %s") %
200 (cmd, explain_exit(code)))
200 (cmd, explain_exit(code)))
201 return open(outname, 'rb').read()
201 return open(outname, 'rb').read()
202 finally:
202 finally:
203 try:
203 try:
204 if inname:
204 if inname:
205 os.unlink(inname)
205 os.unlink(inname)
206 except:
206 except:
207 pass
207 pass
208 try:
208 try:
209 if outname:
209 if outname:
210 os.unlink(outname)
210 os.unlink(outname)
211 except:
211 except:
212 pass
212 pass
213
213
214 filtertable = {
214 filtertable = {
215 'tempfile:': tempfilter,
215 'tempfile:': tempfilter,
216 'pipe:': pipefilter,
216 'pipe:': pipefilter,
217 }
217 }
218
218
219 def filter(s, cmd):
219 def filter(s, cmd):
220 "filter a string through a command that transforms its input to its output"
220 "filter a string through a command that transforms its input to its output"
221 for name, fn in filtertable.iteritems():
221 for name, fn in filtertable.iteritems():
222 if cmd.startswith(name):
222 if cmd.startswith(name):
223 return fn(s, cmd[len(name):].lstrip())
223 return fn(s, cmd[len(name):].lstrip())
224 return pipefilter(s, cmd)
224 return pipefilter(s, cmd)
225
225
226 def binary(s):
226 def binary(s):
227 """return true if a string is binary data"""
227 """return true if a string is binary data"""
228 return bool(s and '\0' in s)
228 return bool(s and '\0' in s)
229
229
230 def increasingchunks(source, min=1024, max=65536):
230 def increasingchunks(source, min=1024, max=65536):
231 '''return no less than min bytes per chunk while data remains,
231 '''return no less than min bytes per chunk while data remains,
232 doubling min after each chunk until it reaches max'''
232 doubling min after each chunk until it reaches max'''
233 def log2(x):
233 def log2(x):
234 if not x:
234 if not x:
235 return 0
235 return 0
236 i = 0
236 i = 0
237 while x:
237 while x:
238 x >>= 1
238 x >>= 1
239 i += 1
239 i += 1
240 return i - 1
240 return i - 1
241
241
242 buf = []
242 buf = []
243 blen = 0
243 blen = 0
244 for chunk in source:
244 for chunk in source:
245 buf.append(chunk)
245 buf.append(chunk)
246 blen += len(chunk)
246 blen += len(chunk)
247 if blen >= min:
247 if blen >= min:
248 if min < max:
248 if min < max:
249 min = min << 1
249 min = min << 1
250 nmin = 1 << log2(blen)
250 nmin = 1 << log2(blen)
251 if nmin > min:
251 if nmin > min:
252 min = nmin
252 min = nmin
253 if min > max:
253 if min > max:
254 min = max
254 min = max
255 yield ''.join(buf)
255 yield ''.join(buf)
256 blen = 0
256 blen = 0
257 buf = []
257 buf = []
258 if buf:
258 if buf:
259 yield ''.join(buf)
259 yield ''.join(buf)
260
260
261 Abort = error.Abort
261 Abort = error.Abort
262
262
263 def always(fn):
263 def always(fn):
264 return True
264 return True
265
265
266 def never(fn):
266 def never(fn):
267 return False
267 return False
268
268
269 def pathto(root, n1, n2):
269 def pathto(root, n1, n2):
270 '''return the relative path from one place to another.
270 '''return the relative path from one place to another.
271 root should use os.sep to separate directories
271 root should use os.sep to separate directories
272 n1 should use os.sep to separate directories
272 n1 should use os.sep to separate directories
273 n2 should use "/" to separate directories
273 n2 should use "/" to separate directories
274 returns an os.sep-separated path.
274 returns an os.sep-separated path.
275
275
276 If n1 is a relative path, it's assumed it's
276 If n1 is a relative path, it's assumed it's
277 relative to root.
277 relative to root.
278 n2 should always be relative to root.
278 n2 should always be relative to root.
279 '''
279 '''
280 if not n1:
280 if not n1:
281 return localpath(n2)
281 return localpath(n2)
282 if os.path.isabs(n1):
282 if os.path.isabs(n1):
283 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
283 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
284 return os.path.join(root, localpath(n2))
284 return os.path.join(root, localpath(n2))
285 n2 = '/'.join((pconvert(root), n2))
285 n2 = '/'.join((pconvert(root), n2))
286 a, b = splitpath(n1), n2.split('/')
286 a, b = splitpath(n1), n2.split('/')
287 a.reverse()
287 a.reverse()
288 b.reverse()
288 b.reverse()
289 while a and b and a[-1] == b[-1]:
289 while a and b and a[-1] == b[-1]:
290 a.pop()
290 a.pop()
291 b.pop()
291 b.pop()
292 b.reverse()
292 b.reverse()
293 return os.sep.join((['..'] * len(a)) + b) or '.'
293 return os.sep.join((['..'] * len(a)) + b) or '.'
294
294
295 def canonpath(root, cwd, myname):
295 def canonpath(root, cwd, myname):
296 """return the canonical path of myname, given cwd and root"""
296 """return the canonical path of myname, given cwd and root"""
297 if endswithsep(root):
297 if endswithsep(root):
298 rootsep = root
298 rootsep = root
299 else:
299 else:
300 rootsep = root + os.sep
300 rootsep = root + os.sep
301 name = myname
301 name = myname
302 if not os.path.isabs(name):
302 if not os.path.isabs(name):
303 name = os.path.join(root, cwd, name)
303 name = os.path.join(root, cwd, name)
304 name = os.path.normpath(name)
304 name = os.path.normpath(name)
305 audit_path = path_auditor(root)
305 audit_path = path_auditor(root)
306 if name != rootsep and name.startswith(rootsep):
306 if name != rootsep and name.startswith(rootsep):
307 name = name[len(rootsep):]
307 name = name[len(rootsep):]
308 audit_path(name)
308 audit_path(name)
309 return pconvert(name)
309 return pconvert(name)
310 elif name == root:
310 elif name == root:
311 return ''
311 return ''
312 else:
312 else:
313 # Determine whether `name' is in the hierarchy at or beneath `root',
313 # Determine whether `name' is in the hierarchy at or beneath `root',
314 # by iterating name=dirname(name) until that causes no change (can't
314 # by iterating name=dirname(name) until that causes no change (can't
315 # check name == '/', because that doesn't work on windows). For each
315 # check name == '/', because that doesn't work on windows). For each
316 # `name', compare dev/inode numbers. If they match, the list `rel'
316 # `name', compare dev/inode numbers. If they match, the list `rel'
317 # holds the reversed list of components making up the relative file
317 # holds the reversed list of components making up the relative file
318 # name we want.
318 # name we want.
319 root_st = os.stat(root)
319 root_st = os.stat(root)
320 rel = []
320 rel = []
321 while True:
321 while True:
322 try:
322 try:
323 name_st = os.stat(name)
323 name_st = os.stat(name)
324 except OSError:
324 except OSError:
325 break
325 break
326 if samestat(name_st, root_st):
326 if samestat(name_st, root_st):
327 if not rel:
327 if not rel:
328 # name was actually the same as root (maybe a symlink)
328 # name was actually the same as root (maybe a symlink)
329 return ''
329 return ''
330 rel.reverse()
330 rel.reverse()
331 name = os.path.join(*rel)
331 name = os.path.join(*rel)
332 audit_path(name)
332 audit_path(name)
333 return pconvert(name)
333 return pconvert(name)
334 dirname, basename = os.path.split(name)
334 dirname, basename = os.path.split(name)
335 rel.append(basename)
335 rel.append(basename)
336 if dirname == name:
336 if dirname == name:
337 break
337 break
338 name = dirname
338 name = dirname
339
339
340 raise Abort('%s not under root' % myname)
340 raise Abort('%s not under root' % myname)
341
341
342 _hgexecutable = None
342 _hgexecutable = None
343
343
344 def main_is_frozen():
344 def main_is_frozen():
345 """return True if we are a frozen executable.
345 """return True if we are a frozen executable.
346
346
347 The code supports py2exe (most common, Windows only) and tools/freeze
347 The code supports py2exe (most common, Windows only) and tools/freeze
348 (portable, not much used).
348 (portable, not much used).
349 """
349 """
350 return (hasattr(sys, "frozen") or # new py2exe
350 return (hasattr(sys, "frozen") or # new py2exe
351 hasattr(sys, "importers") or # old py2exe
351 hasattr(sys, "importers") or # old py2exe
352 imp.is_frozen("__main__")) # tools/freeze
352 imp.is_frozen("__main__")) # tools/freeze
353
353
354 def hgexecutable():
354 def hgexecutable():
355 """return location of the 'hg' executable.
355 """return location of the 'hg' executable.
356
356
357 Defaults to $HG or 'hg' in the search path.
357 Defaults to $HG or 'hg' in the search path.
358 """
358 """
359 if _hgexecutable is None:
359 if _hgexecutable is None:
360 hg = os.environ.get('HG')
360 hg = os.environ.get('HG')
361 if hg:
361 if hg:
362 set_hgexecutable(hg)
362 set_hgexecutable(hg)
363 elif main_is_frozen():
363 elif main_is_frozen():
364 set_hgexecutable(sys.executable)
364 set_hgexecutable(sys.executable)
365 else:
365 else:
366 exe = find_exe('hg') or os.path.basename(sys.argv[0])
366 exe = find_exe('hg') or os.path.basename(sys.argv[0])
367 set_hgexecutable(exe)
367 set_hgexecutable(exe)
368 return _hgexecutable
368 return _hgexecutable
369
369
370 def set_hgexecutable(path):
370 def set_hgexecutable(path):
371 """set location of the 'hg' executable"""
371 """set location of the 'hg' executable"""
372 global _hgexecutable
372 global _hgexecutable
373 _hgexecutable = path
373 _hgexecutable = path
374
374
375 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
375 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
376 '''enhanced shell command execution.
376 '''enhanced shell command execution.
377 run with environment maybe modified, maybe in different dir.
377 run with environment maybe modified, maybe in different dir.
378
378
379 if command fails and onerr is None, return status. if ui object,
379 if command fails and onerr is None, return status. if ui object,
380 print error message and return status, else raise onerr object as
380 print error message and return status, else raise onerr object as
381 exception.
381 exception.
382
382
383 if out is specified, it is assumed to be a file-like object that has a
383 if out is specified, it is assumed to be a file-like object that has a
384 write() method. stdout and stderr will be redirected to out.'''
384 write() method. stdout and stderr will be redirected to out.'''
385 def py2shell(val):
385 def py2shell(val):
386 'convert python object into string that is useful to shell'
386 'convert python object into string that is useful to shell'
387 if val is None or val is False:
387 if val is None or val is False:
388 return '0'
388 return '0'
389 if val is True:
389 if val is True:
390 return '1'
390 return '1'
391 return str(val)
391 return str(val)
392 origcmd = cmd
392 origcmd = cmd
393 if os.name == 'nt':
393 if os.name == 'nt':
394 cmd = '"%s"' % cmd
394 cmd = '"%s"' % cmd
395 env = dict(os.environ)
395 env = dict(os.environ)
396 env.update((k, py2shell(v)) for k, v in environ.iteritems())
396 env.update((k, py2shell(v)) for k, v in environ.iteritems())
397 env['HG'] = hgexecutable()
397 env['HG'] = hgexecutable()
398 if out is None:
398 if out is None:
399 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
399 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
400 env=env, cwd=cwd)
400 env=env, cwd=cwd)
401 else:
401 else:
402 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
402 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
403 env=env, cwd=cwd, stdout=subprocess.PIPE,
403 env=env, cwd=cwd, stdout=subprocess.PIPE,
404 stderr=subprocess.STDOUT)
404 stderr=subprocess.STDOUT)
405 for line in proc.stdout:
405 for line in proc.stdout:
406 out.write(line)
406 out.write(line)
407 proc.wait()
407 proc.wait()
408 rc = proc.returncode
408 rc = proc.returncode
409 if sys.platform == 'OpenVMS' and rc & 1:
409 if sys.platform == 'OpenVMS' and rc & 1:
410 rc = 0
410 rc = 0
411 if rc and onerr:
411 if rc and onerr:
412 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
412 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
413 explain_exit(rc)[0])
413 explain_exit(rc)[0])
414 if errprefix:
414 if errprefix:
415 errmsg = '%s: %s' % (errprefix, errmsg)
415 errmsg = '%s: %s' % (errprefix, errmsg)
416 try:
416 try:
417 onerr.warn(errmsg + '\n')
417 onerr.warn(errmsg + '\n')
418 except AttributeError:
418 except AttributeError:
419 raise onerr(errmsg)
419 raise onerr(errmsg)
420 return rc
420 return rc
421
421
422 def checksignature(func):
422 def checksignature(func):
423 '''wrap a function with code to check for calling errors'''
423 '''wrap a function with code to check for calling errors'''
424 def check(*args, **kwargs):
424 def check(*args, **kwargs):
425 try:
425 try:
426 return func(*args, **kwargs)
426 return func(*args, **kwargs)
427 except TypeError:
427 except TypeError:
428 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
428 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
429 raise error.SignatureError
429 raise error.SignatureError
430 raise
430 raise
431
431
432 return check
432 return check
433
433
434 # os.path.lexists is not available on python2.3
434 # os.path.lexists is not available on python2.3
435 def lexists(filename):
435 def lexists(filename):
436 "test whether a file with this name exists. does not follow symlinks"
436 "test whether a file with this name exists. does not follow symlinks"
437 try:
437 try:
438 os.lstat(filename)
438 os.lstat(filename)
439 except:
439 except:
440 return False
440 return False
441 return True
441 return True
442
442
443 def unlink(f):
443 def unlink(f):
444 """unlink and remove the directory if it is empty"""
444 """unlink and remove the directory if it is empty"""
445 os.unlink(f)
445 os.unlink(f)
446 # try removing directories that might now be empty
446 # try removing directories that might now be empty
447 try:
447 try:
448 os.removedirs(os.path.dirname(f))
448 os.removedirs(os.path.dirname(f))
449 except OSError:
449 except OSError:
450 pass
450 pass
451
451
452 def copyfile(src, dest):
452 def copyfile(src, dest):
453 "copy a file, preserving mode and atime/mtime"
453 "copy a file, preserving mode and atime/mtime"
454 if os.path.islink(src):
454 if os.path.islink(src):
455 try:
455 try:
456 os.unlink(dest)
456 os.unlink(dest)
457 except:
457 except:
458 pass
458 pass
459 os.symlink(os.readlink(src), dest)
459 os.symlink(os.readlink(src), dest)
460 else:
460 else:
461 try:
461 try:
462 shutil.copyfile(src, dest)
462 shutil.copyfile(src, dest)
463 shutil.copystat(src, dest)
463 shutil.copystat(src, dest)
464 except shutil.Error, inst:
464 except shutil.Error, inst:
465 raise Abort(str(inst))
465 raise Abort(str(inst))
466
466
467 def copyfiles(src, dst, hardlink=None):
467 def copyfiles(src, dst, hardlink=None):
468 """Copy a directory tree using hardlinks if possible"""
468 """Copy a directory tree using hardlinks if possible"""
469
469
470 if hardlink is None:
470 if hardlink is None:
471 hardlink = (os.stat(src).st_dev ==
471 hardlink = (os.stat(src).st_dev ==
472 os.stat(os.path.dirname(dst)).st_dev)
472 os.stat(os.path.dirname(dst)).st_dev)
473
473
474 num = 0
474 num = 0
475 if os.path.isdir(src):
475 if os.path.isdir(src):
476 os.mkdir(dst)
476 os.mkdir(dst)
477 for name, kind in osutil.listdir(src):
477 for name, kind in osutil.listdir(src):
478 srcname = os.path.join(src, name)
478 srcname = os.path.join(src, name)
479 dstname = os.path.join(dst, name)
479 dstname = os.path.join(dst, name)
480 hardlink, n = copyfiles(srcname, dstname, hardlink)
480 hardlink, n = copyfiles(srcname, dstname, hardlink)
481 num += n
481 num += n
482 else:
482 else:
483 if hardlink:
483 if hardlink:
484 try:
484 try:
485 os_link(src, dst)
485 os_link(src, dst)
486 except (IOError, OSError):
486 except (IOError, OSError):
487 hardlink = False
487 hardlink = False
488 shutil.copy(src, dst)
488 shutil.copy(src, dst)
489 else:
489 else:
490 shutil.copy(src, dst)
490 shutil.copy(src, dst)
491 num += 1
491 num += 1
492
492
493 return hardlink, num
493 return hardlink, num
494
494
495 class path_auditor(object):
495 class path_auditor(object):
496 '''ensure that a filesystem path contains no banned components.
496 '''ensure that a filesystem path contains no banned components.
497 the following properties of a path are checked:
497 the following properties of a path are checked:
498
498
499 - under top-level .hg
499 - under top-level .hg
500 - starts at the root of a windows drive
500 - starts at the root of a windows drive
501 - contains ".."
501 - contains ".."
502 - traverses a symlink (e.g. a/symlink_here/b)
502 - traverses a symlink (e.g. a/symlink_here/b)
503 - inside a nested repository'''
503 - inside a nested repository'''
504
504
505 def __init__(self, root):
505 def __init__(self, root):
506 self.audited = set()
506 self.audited = set()
507 self.auditeddir = set()
507 self.auditeddir = set()
508 self.root = root
508 self.root = root
509
509
510 def __call__(self, path):
510 def __call__(self, path):
511 if path in self.audited:
511 if path in self.audited:
512 return
512 return
513 normpath = os.path.normcase(path)
513 normpath = os.path.normcase(path)
514 parts = splitpath(normpath)
514 parts = splitpath(normpath)
515 if (os.path.splitdrive(path)[0]
515 if (os.path.splitdrive(path)[0]
516 or parts[0].lower() in ('.hg', '.hg.', '')
516 or parts[0].lower() in ('.hg', '.hg.', '')
517 or os.pardir in parts):
517 or os.pardir in parts):
518 raise Abort(_("path contains illegal component: %s") % path)
518 raise Abort(_("path contains illegal component: %s") % path)
519 if '.hg' in path.lower():
519 if '.hg' in path.lower():
520 lparts = [p.lower() for p in parts]
520 lparts = [p.lower() for p in parts]
521 for p in '.hg', '.hg.':
521 for p in '.hg', '.hg.':
522 if p in lparts[1:]:
522 if p in lparts[1:]:
523 pos = lparts.index(p)
523 pos = lparts.index(p)
524 base = os.path.join(*parts[:pos])
524 base = os.path.join(*parts[:pos])
525 raise Abort(_('path %r is inside repo %r') % (path, base))
525 raise Abort(_('path %r is inside repo %r') % (path, base))
526 def check(prefix):
526 def check(prefix):
527 curpath = os.path.join(self.root, prefix)
527 curpath = os.path.join(self.root, prefix)
528 try:
528 try:
529 st = os.lstat(curpath)
529 st = os.lstat(curpath)
530 except OSError, err:
530 except OSError, err:
531 # EINVAL can be raised as invalid path syntax under win32.
531 # EINVAL can be raised as invalid path syntax under win32.
532 # They must be ignored for patterns can be checked too.
532 # They must be ignored for patterns can be checked too.
533 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
533 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
534 raise
534 raise
535 else:
535 else:
536 if stat.S_ISLNK(st.st_mode):
536 if stat.S_ISLNK(st.st_mode):
537 raise Abort(_('path %r traverses symbolic link %r') %
537 raise Abort(_('path %r traverses symbolic link %r') %
538 (path, prefix))
538 (path, prefix))
539 elif (stat.S_ISDIR(st.st_mode) and
539 elif (stat.S_ISDIR(st.st_mode) and
540 os.path.isdir(os.path.join(curpath, '.hg'))):
540 os.path.isdir(os.path.join(curpath, '.hg'))):
541 raise Abort(_('path %r is inside repo %r') %
541 raise Abort(_('path %r is inside repo %r') %
542 (path, prefix))
542 (path, prefix))
543 parts.pop()
543 parts.pop()
544 prefixes = []
544 prefixes = []
545 while parts:
545 while parts:
546 prefix = os.sep.join(parts)
546 prefix = os.sep.join(parts)
547 if prefix in self.auditeddir:
547 if prefix in self.auditeddir:
548 break
548 break
549 check(prefix)
549 check(prefix)
550 prefixes.append(prefix)
550 prefixes.append(prefix)
551 parts.pop()
551 parts.pop()
552
552
553 self.audited.add(path)
553 self.audited.add(path)
554 # only add prefixes to the cache after checking everything: we don't
554 # only add prefixes to the cache after checking everything: we don't
555 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
555 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
556 self.auditeddir.update(prefixes)
556 self.auditeddir.update(prefixes)
557
557
558 def nlinks(pathname):
558 def nlinks(pathname):
559 """Return number of hardlinks for the given file."""
559 """Return number of hardlinks for the given file."""
560 return os.lstat(pathname).st_nlink
560 return os.lstat(pathname).st_nlink
561
561
562 if hasattr(os, 'link'):
562 if hasattr(os, 'link'):
563 os_link = os.link
563 os_link = os.link
564 else:
564 else:
565 def os_link(src, dst):
565 def os_link(src, dst):
566 raise OSError(0, _("Hardlinks not supported"))
566 raise OSError(0, _("Hardlinks not supported"))
567
567
568 def lookup_reg(key, name=None, scope=None):
568 def lookup_reg(key, name=None, scope=None):
569 return None
569 return None
570
570
571 def hidewindow():
571 def hidewindow():
572 """Hide current shell window.
572 """Hide current shell window.
573
573
574 Used to hide the window opened when starting asynchronous
574 Used to hide the window opened when starting asynchronous
575 child process under Windows, unneeded on other systems.
575 child process under Windows, unneeded on other systems.
576 """
576 """
577 pass
577 pass
578
578
579 if os.name == 'nt':
579 if os.name == 'nt':
580 from windows import *
580 from windows import *
581 else:
581 else:
582 from posix import *
582 from posix import *
583
583
584 def makelock(info, pathname):
584 def makelock(info, pathname):
585 try:
585 try:
586 return os.symlink(info, pathname)
586 return os.symlink(info, pathname)
587 except OSError, why:
587 except OSError, why:
588 if why.errno == errno.EEXIST:
588 if why.errno == errno.EEXIST:
589 raise
589 raise
590 except AttributeError: # no symlink in os
590 except AttributeError: # no symlink in os
591 pass
591 pass
592
592
593 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
593 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
594 os.write(ld, info)
594 os.write(ld, info)
595 os.close(ld)
595 os.close(ld)
596
596
597 def readlock(pathname):
597 def readlock(pathname):
598 try:
598 try:
599 return os.readlink(pathname)
599 return os.readlink(pathname)
600 except OSError, why:
600 except OSError, why:
601 if why.errno not in (errno.EINVAL, errno.ENOSYS):
601 if why.errno not in (errno.EINVAL, errno.ENOSYS):
602 raise
602 raise
603 except AttributeError: # no symlink in os
603 except AttributeError: # no symlink in os
604 pass
604 pass
605 return posixfile(pathname).read()
605 return posixfile(pathname).read()
606
606
607 def fstat(fp):
607 def fstat(fp):
608 '''stat file object that may not have fileno method.'''
608 '''stat file object that may not have fileno method.'''
609 try:
609 try:
610 return os.fstat(fp.fileno())
610 return os.fstat(fp.fileno())
611 except AttributeError:
611 except AttributeError:
612 return os.stat(fp.name)
612 return os.stat(fp.name)
613
613
614 # File system features
614 # File system features
615
615
616 def checkcase(path):
616 def checkcase(path):
617 """
617 """
618 Check whether the given path is on a case-sensitive filesystem
618 Check whether the given path is on a case-sensitive filesystem
619
619
620 Requires a path (like /foo/.hg) ending with a foldable final
620 Requires a path (like /foo/.hg) ending with a foldable final
621 directory component.
621 directory component.
622 """
622 """
623 s1 = os.stat(path)
623 s1 = os.stat(path)
624 d, b = os.path.split(path)
624 d, b = os.path.split(path)
625 p2 = os.path.join(d, b.upper())
625 p2 = os.path.join(d, b.upper())
626 if path == p2:
626 if path == p2:
627 p2 = os.path.join(d, b.lower())
627 p2 = os.path.join(d, b.lower())
628 try:
628 try:
629 s2 = os.stat(p2)
629 s2 = os.stat(p2)
630 if s2 == s1:
630 if s2 == s1:
631 return False
631 return False
632 return True
632 return True
633 except:
633 except:
634 return True
634 return True
635
635
636 _fspathcache = {}
636 _fspathcache = {}
637 def fspath(name, root):
637 def fspath(name, root):
638 '''Get name in the case stored in the filesystem
638 '''Get name in the case stored in the filesystem
639
639
640 The name is either relative to root, or it is an absolute path starting
640 The name is either relative to root, or it is an absolute path starting
641 with root. Note that this function is unnecessary, and should not be
641 with root. Note that this function is unnecessary, and should not be
642 called, for case-sensitive filesystems (simply because it's expensive).
642 called, for case-sensitive filesystems (simply because it's expensive).
643 '''
643 '''
644 # If name is absolute, make it relative
644 # If name is absolute, make it relative
645 if name.lower().startswith(root.lower()):
645 if name.lower().startswith(root.lower()):
646 l = len(root)
646 l = len(root)
647 if name[l] == os.sep or name[l] == os.altsep:
647 if name[l] == os.sep or name[l] == os.altsep:
648 l = l + 1
648 l = l + 1
649 name = name[l:]
649 name = name[l:]
650
650
651 if not os.path.exists(os.path.join(root, name)):
651 if not os.path.exists(os.path.join(root, name)):
652 return None
652 return None
653
653
654 seps = os.sep
654 seps = os.sep
655 if os.altsep:
655 if os.altsep:
656 seps = seps + os.altsep
656 seps = seps + os.altsep
657 # Protect backslashes. This gets silly very quickly.
657 # Protect backslashes. This gets silly very quickly.
658 seps.replace('\\','\\\\')
658 seps.replace('\\','\\\\')
659 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
659 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
660 dir = os.path.normcase(os.path.normpath(root))
660 dir = os.path.normcase(os.path.normpath(root))
661 result = []
661 result = []
662 for part, sep in pattern.findall(name):
662 for part, sep in pattern.findall(name):
663 if sep:
663 if sep:
664 result.append(sep)
664 result.append(sep)
665 continue
665 continue
666
666
667 if dir not in _fspathcache:
667 if dir not in _fspathcache:
668 _fspathcache[dir] = os.listdir(dir)
668 _fspathcache[dir] = os.listdir(dir)
669 contents = _fspathcache[dir]
669 contents = _fspathcache[dir]
670
670
671 lpart = part.lower()
671 lpart = part.lower()
672 lenp = len(part)
672 lenp = len(part)
673 for n in contents:
673 for n in contents:
674 if lenp == len(n) and n.lower() == lpart:
674 if lenp == len(n) and n.lower() == lpart:
675 result.append(n)
675 result.append(n)
676 break
676 break
677 else:
677 else:
678 # Cannot happen, as the file exists!
678 # Cannot happen, as the file exists!
679 result.append(part)
679 result.append(part)
680 dir = os.path.join(dir, lpart)
680 dir = os.path.join(dir, lpart)
681
681
682 return ''.join(result)
682 return ''.join(result)
683
683
684 def checkexec(path):
684 def checkexec(path):
685 """
685 """
686 Check whether the given path is on a filesystem with UNIX-like exec flags
686 Check whether the given path is on a filesystem with UNIX-like exec flags
687
687
688 Requires a directory (like /foo/.hg)
688 Requires a directory (like /foo/.hg)
689 """
689 """
690
690
691 # VFAT on some Linux versions can flip mode but it doesn't persist
691 # VFAT on some Linux versions can flip mode but it doesn't persist
692 # a FS remount. Frequently we can detect it if files are created
692 # a FS remount. Frequently we can detect it if files are created
693 # with exec bit on.
693 # with exec bit on.
694
694
695 try:
695 try:
696 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
696 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
697 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
697 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
698 try:
698 try:
699 os.close(fh)
699 os.close(fh)
700 m = os.stat(fn).st_mode & 0777
700 m = os.stat(fn).st_mode & 0777
701 new_file_has_exec = m & EXECFLAGS
701 new_file_has_exec = m & EXECFLAGS
702 os.chmod(fn, m ^ EXECFLAGS)
702 os.chmod(fn, m ^ EXECFLAGS)
703 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
703 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
704 finally:
704 finally:
705 os.unlink(fn)
705 os.unlink(fn)
706 except (IOError, OSError):
706 except (IOError, OSError):
707 # we don't care, the user probably won't be able to commit anyway
707 # we don't care, the user probably won't be able to commit anyway
708 return False
708 return False
709 return not (new_file_has_exec or exec_flags_cannot_flip)
709 return not (new_file_has_exec or exec_flags_cannot_flip)
710
710
711 def checklink(path):
711 def checklink(path):
712 """check whether the given path is on a symlink-capable filesystem"""
712 """check whether the given path is on a symlink-capable filesystem"""
713 # mktemp is not racy because symlink creation will fail if the
713 # mktemp is not racy because symlink creation will fail if the
714 # file already exists
714 # file already exists
715 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
715 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
716 try:
716 try:
717 os.symlink(".", name)
717 os.symlink(".", name)
718 os.unlink(name)
718 os.unlink(name)
719 return True
719 return True
720 except (OSError, AttributeError):
720 except (OSError, AttributeError):
721 return False
721 return False
722
722
723 def needbinarypatch():
723 def needbinarypatch():
724 """return True if patches should be applied in binary mode by default."""
724 """return True if patches should be applied in binary mode by default."""
725 return os.name == 'nt'
725 return os.name == 'nt'
726
726
727 def endswithsep(path):
727 def endswithsep(path):
728 '''Check path ends with os.sep or os.altsep.'''
728 '''Check path ends with os.sep or os.altsep.'''
729 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
729 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
730
730
731 def splitpath(path):
731 def splitpath(path):
732 '''Split path by os.sep.
732 '''Split path by os.sep.
733 Note that this function does not use os.altsep because this is
733 Note that this function does not use os.altsep because this is
734 an alternative of simple "xxx.split(os.sep)".
734 an alternative of simple "xxx.split(os.sep)".
735 It is recommended to use os.path.normpath() before using this
735 It is recommended to use os.path.normpath() before using this
736 function if need.'''
736 function if need.'''
737 return path.split(os.sep)
737 return path.split(os.sep)
738
738
739 def gui():
739 def gui():
740 '''Are we running in a GUI?'''
740 '''Are we running in a GUI?'''
741 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
741 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
742
742
743 def mktempcopy(name, emptyok=False, createmode=None):
743 def mktempcopy(name, emptyok=False, createmode=None):
744 """Create a temporary file with the same contents from name
744 """Create a temporary file with the same contents from name
745
745
746 The permission bits are copied from the original file.
746 The permission bits are copied from the original file.
747
747
748 If the temporary file is going to be truncated immediately, you
748 If the temporary file is going to be truncated immediately, you
749 can use emptyok=True as an optimization.
749 can use emptyok=True as an optimization.
750
750
751 Returns the name of the temporary file.
751 Returns the name of the temporary file.
752 """
752 """
753 d, fn = os.path.split(name)
753 d, fn = os.path.split(name)
754 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
754 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
755 os.close(fd)
755 os.close(fd)
756 # Temporary files are created with mode 0600, which is usually not
756 # Temporary files are created with mode 0600, which is usually not
757 # what we want. If the original file already exists, just copy
757 # what we want. If the original file already exists, just copy
758 # its mode. Otherwise, manually obey umask.
758 # its mode. Otherwise, manually obey umask.
759 try:
759 try:
760 st_mode = os.lstat(name).st_mode & 0777
760 st_mode = os.lstat(name).st_mode & 0777
761 except OSError, inst:
761 except OSError, inst:
762 if inst.errno != errno.ENOENT:
762 if inst.errno != errno.ENOENT:
763 raise
763 raise
764 st_mode = createmode
764 st_mode = createmode
765 if st_mode is None:
765 if st_mode is None:
766 st_mode = ~umask
766 st_mode = ~umask
767 st_mode &= 0666
767 st_mode &= 0666
768 os.chmod(temp, st_mode)
768 os.chmod(temp, st_mode)
769 if emptyok:
769 if emptyok:
770 return temp
770 return temp
771 try:
771 try:
772 try:
772 try:
773 ifp = posixfile(name, "rb")
773 ifp = posixfile(name, "rb")
774 except IOError, inst:
774 except IOError, inst:
775 if inst.errno == errno.ENOENT:
775 if inst.errno == errno.ENOENT:
776 return temp
776 return temp
777 if not getattr(inst, 'filename', None):
777 if not getattr(inst, 'filename', None):
778 inst.filename = name
778 inst.filename = name
779 raise
779 raise
780 ofp = posixfile(temp, "wb")
780 ofp = posixfile(temp, "wb")
781 for chunk in filechunkiter(ifp):
781 for chunk in filechunkiter(ifp):
782 ofp.write(chunk)
782 ofp.write(chunk)
783 ifp.close()
783 ifp.close()
784 ofp.close()
784 ofp.close()
785 except:
785 except:
786 try: os.unlink(temp)
786 try: os.unlink(temp)
787 except: pass
787 except: pass
788 raise
788 raise
789 return temp
789 return temp
790
790
791 class atomictempfile(object):
791 class atomictempfile(object):
792 """file-like object that atomically updates a file
792 """file-like object that atomically updates a file
793
793
794 All writes will be redirected to a temporary copy of the original
794 All writes will be redirected to a temporary copy of the original
795 file. When rename is called, the copy is renamed to the original
795 file. When rename is called, the copy is renamed to the original
796 name, making the changes visible.
796 name, making the changes visible.
797 """
797 """
798 def __init__(self, name, mode='w+b', createmode=None):
798 def __init__(self, name, mode='w+b', createmode=None):
799 self.__name = name
799 self.__name = name
800 self._fp = None
800 self._fp = None
801 self.temp = mktempcopy(name, emptyok=('w' in mode),
801 self.temp = mktempcopy(name, emptyok=('w' in mode),
802 createmode=createmode)
802 createmode=createmode)
803 self._fp = posixfile(self.temp, mode)
803 self._fp = posixfile(self.temp, mode)
804
804
805 def __getattr__(self, name):
805 def __getattr__(self, name):
806 return getattr(self._fp, name)
806 return getattr(self._fp, name)
807
807
808 def rename(self):
808 def rename(self):
809 if not self._fp.closed:
809 if not self._fp.closed:
810 self._fp.close()
810 self._fp.close()
811 rename(self.temp, localpath(self.__name))
811 rename(self.temp, localpath(self.__name))
812
812
813 def __del__(self):
813 def __del__(self):
814 if not self._fp:
814 if not self._fp:
815 return
815 return
816 if not self._fp.closed:
816 if not self._fp.closed:
817 try:
817 try:
818 os.unlink(self.temp)
818 os.unlink(self.temp)
819 except: pass
819 except: pass
820 self._fp.close()
820 self._fp.close()
821
821
822 def makedirs(name, mode=None):
822 def makedirs(name, mode=None):
823 """recursive directory creation with parent mode inheritance"""
823 """recursive directory creation with parent mode inheritance"""
824 try:
824 try:
825 os.mkdir(name)
825 os.mkdir(name)
826 if mode is not None:
826 if mode is not None:
827 os.chmod(name, mode)
827 os.chmod(name, mode)
828 return
828 return
829 except OSError, err:
829 except OSError, err:
830 if err.errno == errno.EEXIST:
830 if err.errno == errno.EEXIST:
831 return
831 return
832 if err.errno != errno.ENOENT:
832 if err.errno != errno.ENOENT:
833 raise
833 raise
834 parent = os.path.abspath(os.path.dirname(name))
834 parent = os.path.abspath(os.path.dirname(name))
835 makedirs(parent, mode)
835 makedirs(parent, mode)
836 makedirs(name, mode)
836 makedirs(name, mode)
837
837
838 class opener(object):
838 class opener(object):
839 """Open files relative to a base directory
839 """Open files relative to a base directory
840
840
841 This class is used to hide the details of COW semantics and
841 This class is used to hide the details of COW semantics and
842 remote file access from higher level code.
842 remote file access from higher level code.
843 """
843 """
844 def __init__(self, base, audit=True):
844 def __init__(self, base, audit=True):
845 self.base = base
845 self.base = base
846 if audit:
846 if audit:
847 self.audit_path = path_auditor(base)
847 self.audit_path = path_auditor(base)
848 else:
848 else:
849 self.audit_path = always
849 self.audit_path = always
850 self.createmode = None
850 self.createmode = None
851
851
852 @propertycache
852 @propertycache
853 def _can_symlink(self):
853 def _can_symlink(self):
854 return checklink(self.base)
854 return checklink(self.base)
855
855
856 def _fixfilemode(self, name):
856 def _fixfilemode(self, name):
857 if self.createmode is None:
857 if self.createmode is None:
858 return
858 return
859 os.chmod(name, self.createmode & 0666)
859 os.chmod(name, self.createmode & 0666)
860
860
861 def __call__(self, path, mode="r", text=False, atomictemp=False):
861 def __call__(self, path, mode="r", text=False, atomictemp=False):
862 self.audit_path(path)
862 self.audit_path(path)
863 f = os.path.join(self.base, path)
863 f = os.path.join(self.base, path)
864
864
865 if not text and "b" not in mode:
865 if not text and "b" not in mode:
866 mode += "b" # for that other OS
866 mode += "b" # for that other OS
867
867
868 nlink = -1
868 nlink = -1
869 if mode not in ("r", "rb"):
869 if mode not in ("r", "rb"):
870 try:
870 try:
871 nlink = nlinks(f)
871 nlink = nlinks(f)
872 except OSError:
872 except OSError:
873 nlink = 0
873 nlink = 0
874 d = os.path.dirname(f)
874 d = os.path.dirname(f)
875 if not os.path.isdir(d):
875 if not os.path.isdir(d):
876 makedirs(d, self.createmode)
876 makedirs(d, self.createmode)
877 if atomictemp:
877 if atomictemp:
878 return atomictempfile(f, mode, self.createmode)
878 return atomictempfile(f, mode, self.createmode)
879 if nlink > 1:
879 if nlink > 1:
880 rename(mktempcopy(f), f)
880 rename(mktempcopy(f), f)
881 fp = posixfile(f, mode)
881 fp = posixfile(f, mode)
882 if nlink == 0:
882 if nlink == 0:
883 self._fixfilemode(f)
883 self._fixfilemode(f)
884 return fp
884 return fp
885
885
886 def symlink(self, src, dst):
886 def symlink(self, src, dst):
887 self.audit_path(dst)
887 self.audit_path(dst)
888 linkname = os.path.join(self.base, dst)
888 linkname = os.path.join(self.base, dst)
889 try:
889 try:
890 os.unlink(linkname)
890 os.unlink(linkname)
891 except OSError:
891 except OSError:
892 pass
892 pass
893
893
894 dirname = os.path.dirname(linkname)
894 dirname = os.path.dirname(linkname)
895 if not os.path.exists(dirname):
895 if not os.path.exists(dirname):
896 makedirs(dirname, self.createmode)
896 makedirs(dirname, self.createmode)
897
897
898 if self._can_symlink:
898 if self._can_symlink:
899 try:
899 try:
900 os.symlink(src, linkname)
900 os.symlink(src, linkname)
901 except OSError, err:
901 except OSError, err:
902 raise OSError(err.errno, _('could not symlink to %r: %s') %
902 raise OSError(err.errno, _('could not symlink to %r: %s') %
903 (src, err.strerror), linkname)
903 (src, err.strerror), linkname)
904 else:
904 else:
905 f = self(dst, "w")
905 f = self(dst, "w")
906 f.write(src)
906 f.write(src)
907 f.close()
907 f.close()
908 self._fixfilemode(dst)
908 self._fixfilemode(dst)
909
909
910 class chunkbuffer(object):
910 class chunkbuffer(object):
911 """Allow arbitrary sized chunks of data to be efficiently read from an
911 """Allow arbitrary sized chunks of data to be efficiently read from an
912 iterator over chunks of arbitrary size."""
912 iterator over chunks of arbitrary size."""
913
913
914 def __init__(self, in_iter):
914 def __init__(self, in_iter):
915 """in_iter is the iterator that's iterating over the input chunks.
915 """in_iter is the iterator that's iterating over the input chunks.
916 targetsize is how big a buffer to try to maintain."""
916 targetsize is how big a buffer to try to maintain."""
917 self.iter = iter(in_iter)
917 def splitbig(chunks):
918 for chunk in chunks:
919 if len(chunk) > 2**20:
920 pos = 0
921 while pos < len(chunk):
922 end = pos + 2 ** 18
923 yield chunk[pos:end]
924 pos = end
925 else:
926 yield chunk
927 self.iter = splitbig(in_iter)
918 self.buf = ''
928 self.buf = ''
919
929
920 def read(self, l):
930 def read(self, l):
921 """Read L bytes of data from the iterator of chunks of data.
931 """Read L bytes of data from the iterator of chunks of data.
922 Returns less than L bytes if the iterator runs dry."""
932 Returns less than L bytes if the iterator runs dry."""
923 if l > len(self.buf) and self.iter:
933 if l > len(self.buf) and self.iter:
924 # Clamp to a multiple of 2**16
934 # Clamp to a multiple of 2**16
925 targetsize = max(l, 2**16)
935 targetsize = max(l, 2**16)
926 collector = [str(self.buf)]
936 collector = [str(self.buf)]
927 collected = len(self.buf)
937 collected = len(self.buf)
928 for chunk in self.iter:
938 for chunk in self.iter:
929 collector.append(chunk)
939 collector.append(chunk)
930 collected += len(chunk)
940 collected += len(chunk)
931 if collected >= targetsize:
941 if collected >= targetsize:
932 break
942 break
933 else:
943 else:
934 self.iter = False
944 self.iter = False
935 self.buf = ''.join(collector)
945 self.buf = ''.join(collector)
936 if len(self.buf) == l:
946 if len(self.buf) == l:
937 s, self.buf = str(self.buf), ''
947 s, self.buf = str(self.buf), ''
938 else:
948 else:
939 s, self.buf = self.buf[:l], buffer(self.buf, l)
949 s, self.buf = self.buf[:l], buffer(self.buf, l)
940 return s
950 return s
941
951
942 def filechunkiter(f, size=65536, limit=None):
952 def filechunkiter(f, size=65536, limit=None):
943 """Create a generator that produces the data in the file size
953 """Create a generator that produces the data in the file size
944 (default 65536) bytes at a time, up to optional limit (default is
954 (default 65536) bytes at a time, up to optional limit (default is
945 to read all data). Chunks may be less than size bytes if the
955 to read all data). Chunks may be less than size bytes if the
946 chunk is the last chunk in the file, or the file is a socket or
956 chunk is the last chunk in the file, or the file is a socket or
947 some other type of file that sometimes reads less data than is
957 some other type of file that sometimes reads less data than is
948 requested."""
958 requested."""
949 assert size >= 0
959 assert size >= 0
950 assert limit is None or limit >= 0
960 assert limit is None or limit >= 0
951 while True:
961 while True:
952 if limit is None:
962 if limit is None:
953 nbytes = size
963 nbytes = size
954 else:
964 else:
955 nbytes = min(limit, size)
965 nbytes = min(limit, size)
956 s = nbytes and f.read(nbytes)
966 s = nbytes and f.read(nbytes)
957 if not s:
967 if not s:
958 break
968 break
959 if limit:
969 if limit:
960 limit -= len(s)
970 limit -= len(s)
961 yield s
971 yield s
962
972
963 def makedate():
973 def makedate():
964 lt = time.localtime()
974 lt = time.localtime()
965 if lt[8] == 1 and time.daylight:
975 if lt[8] == 1 and time.daylight:
966 tz = time.altzone
976 tz = time.altzone
967 else:
977 else:
968 tz = time.timezone
978 tz = time.timezone
969 return time.mktime(lt), tz
979 return time.mktime(lt), tz
970
980
971 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
981 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
972 """represent a (unixtime, offset) tuple as a localized time.
982 """represent a (unixtime, offset) tuple as a localized time.
973 unixtime is seconds since the epoch, and offset is the time zone's
983 unixtime is seconds since the epoch, and offset is the time zone's
974 number of seconds away from UTC. if timezone is false, do not
984 number of seconds away from UTC. if timezone is false, do not
975 append time zone to string."""
985 append time zone to string."""
976 t, tz = date or makedate()
986 t, tz = date or makedate()
977 if "%1" in format or "%2" in format:
987 if "%1" in format or "%2" in format:
978 sign = (tz > 0) and "-" or "+"
988 sign = (tz > 0) and "-" or "+"
979 minutes = abs(tz) // 60
989 minutes = abs(tz) // 60
980 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
990 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
981 format = format.replace("%2", "%02d" % (minutes % 60))
991 format = format.replace("%2", "%02d" % (minutes % 60))
982 s = time.strftime(format, time.gmtime(float(t) - tz))
992 s = time.strftime(format, time.gmtime(float(t) - tz))
983 return s
993 return s
984
994
985 def shortdate(date=None):
995 def shortdate(date=None):
986 """turn (timestamp, tzoff) tuple into iso 8631 date."""
996 """turn (timestamp, tzoff) tuple into iso 8631 date."""
987 return datestr(date, format='%Y-%m-%d')
997 return datestr(date, format='%Y-%m-%d')
988
998
989 def strdate(string, format, defaults=[]):
999 def strdate(string, format, defaults=[]):
990 """parse a localized time string and return a (unixtime, offset) tuple.
1000 """parse a localized time string and return a (unixtime, offset) tuple.
991 if the string cannot be parsed, ValueError is raised."""
1001 if the string cannot be parsed, ValueError is raised."""
992 def timezone(string):
1002 def timezone(string):
993 tz = string.split()[-1]
1003 tz = string.split()[-1]
994 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1004 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
995 sign = (tz[0] == "+") and 1 or -1
1005 sign = (tz[0] == "+") and 1 or -1
996 hours = int(tz[1:3])
1006 hours = int(tz[1:3])
997 minutes = int(tz[3:5])
1007 minutes = int(tz[3:5])
998 return -sign * (hours * 60 + minutes) * 60
1008 return -sign * (hours * 60 + minutes) * 60
999 if tz == "GMT" or tz == "UTC":
1009 if tz == "GMT" or tz == "UTC":
1000 return 0
1010 return 0
1001 return None
1011 return None
1002
1012
1003 # NOTE: unixtime = localunixtime + offset
1013 # NOTE: unixtime = localunixtime + offset
1004 offset, date = timezone(string), string
1014 offset, date = timezone(string), string
1005 if offset != None:
1015 if offset != None:
1006 date = " ".join(string.split()[:-1])
1016 date = " ".join(string.split()[:-1])
1007
1017
1008 # add missing elements from defaults
1018 # add missing elements from defaults
1009 for part in defaults:
1019 for part in defaults:
1010 found = [True for p in part if ("%"+p) in format]
1020 found = [True for p in part if ("%"+p) in format]
1011 if not found:
1021 if not found:
1012 date += "@" + defaults[part]
1022 date += "@" + defaults[part]
1013 format += "@%" + part[0]
1023 format += "@%" + part[0]
1014
1024
1015 timetuple = time.strptime(date, format)
1025 timetuple = time.strptime(date, format)
1016 localunixtime = int(calendar.timegm(timetuple))
1026 localunixtime = int(calendar.timegm(timetuple))
1017 if offset is None:
1027 if offset is None:
1018 # local timezone
1028 # local timezone
1019 unixtime = int(time.mktime(timetuple))
1029 unixtime = int(time.mktime(timetuple))
1020 offset = unixtime - localunixtime
1030 offset = unixtime - localunixtime
1021 else:
1031 else:
1022 unixtime = localunixtime + offset
1032 unixtime = localunixtime + offset
1023 return unixtime, offset
1033 return unixtime, offset
1024
1034
1025 def parsedate(date, formats=None, defaults=None):
1035 def parsedate(date, formats=None, defaults=None):
1026 """parse a localized date/time string and return a (unixtime, offset) tuple.
1036 """parse a localized date/time string and return a (unixtime, offset) tuple.
1027
1037
1028 The date may be a "unixtime offset" string or in one of the specified
1038 The date may be a "unixtime offset" string or in one of the specified
1029 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1039 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1030 """
1040 """
1031 if not date:
1041 if not date:
1032 return 0, 0
1042 return 0, 0
1033 if isinstance(date, tuple) and len(date) == 2:
1043 if isinstance(date, tuple) and len(date) == 2:
1034 return date
1044 return date
1035 if not formats:
1045 if not formats:
1036 formats = defaultdateformats
1046 formats = defaultdateformats
1037 date = date.strip()
1047 date = date.strip()
1038 try:
1048 try:
1039 when, offset = map(int, date.split(' '))
1049 when, offset = map(int, date.split(' '))
1040 except ValueError:
1050 except ValueError:
1041 # fill out defaults
1051 # fill out defaults
1042 if not defaults:
1052 if not defaults:
1043 defaults = {}
1053 defaults = {}
1044 now = makedate()
1054 now = makedate()
1045 for part in "d mb yY HI M S".split():
1055 for part in "d mb yY HI M S".split():
1046 if part not in defaults:
1056 if part not in defaults:
1047 if part[0] in "HMS":
1057 if part[0] in "HMS":
1048 defaults[part] = "00"
1058 defaults[part] = "00"
1049 else:
1059 else:
1050 defaults[part] = datestr(now, "%" + part[0])
1060 defaults[part] = datestr(now, "%" + part[0])
1051
1061
1052 for format in formats:
1062 for format in formats:
1053 try:
1063 try:
1054 when, offset = strdate(date, format, defaults)
1064 when, offset = strdate(date, format, defaults)
1055 except (ValueError, OverflowError):
1065 except (ValueError, OverflowError):
1056 pass
1066 pass
1057 else:
1067 else:
1058 break
1068 break
1059 else:
1069 else:
1060 raise Abort(_('invalid date: %r ') % date)
1070 raise Abort(_('invalid date: %r ') % date)
1061 # validate explicit (probably user-specified) date and
1071 # validate explicit (probably user-specified) date and
1062 # time zone offset. values must fit in signed 32 bits for
1072 # time zone offset. values must fit in signed 32 bits for
1063 # current 32-bit linux runtimes. timezones go from UTC-12
1073 # current 32-bit linux runtimes. timezones go from UTC-12
1064 # to UTC+14
1074 # to UTC+14
1065 if abs(when) > 0x7fffffff:
1075 if abs(when) > 0x7fffffff:
1066 raise Abort(_('date exceeds 32 bits: %d') % when)
1076 raise Abort(_('date exceeds 32 bits: %d') % when)
1067 if offset < -50400 or offset > 43200:
1077 if offset < -50400 or offset > 43200:
1068 raise Abort(_('impossible time zone offset: %d') % offset)
1078 raise Abort(_('impossible time zone offset: %d') % offset)
1069 return when, offset
1079 return when, offset
1070
1080
1071 def matchdate(date):
1081 def matchdate(date):
1072 """Return a function that matches a given date match specifier
1082 """Return a function that matches a given date match specifier
1073
1083
1074 Formats include:
1084 Formats include:
1075
1085
1076 '{date}' match a given date to the accuracy provided
1086 '{date}' match a given date to the accuracy provided
1077
1087
1078 '<{date}' on or before a given date
1088 '<{date}' on or before a given date
1079
1089
1080 '>{date}' on or after a given date
1090 '>{date}' on or after a given date
1081
1091
1082 """
1092 """
1083
1093
1084 def lower(date):
1094 def lower(date):
1085 d = dict(mb="1", d="1")
1095 d = dict(mb="1", d="1")
1086 return parsedate(date, extendeddateformats, d)[0]
1096 return parsedate(date, extendeddateformats, d)[0]
1087
1097
1088 def upper(date):
1098 def upper(date):
1089 d = dict(mb="12", HI="23", M="59", S="59")
1099 d = dict(mb="12", HI="23", M="59", S="59")
1090 for days in "31 30 29".split():
1100 for days in "31 30 29".split():
1091 try:
1101 try:
1092 d["d"] = days
1102 d["d"] = days
1093 return parsedate(date, extendeddateformats, d)[0]
1103 return parsedate(date, extendeddateformats, d)[0]
1094 except:
1104 except:
1095 pass
1105 pass
1096 d["d"] = "28"
1106 d["d"] = "28"
1097 return parsedate(date, extendeddateformats, d)[0]
1107 return parsedate(date, extendeddateformats, d)[0]
1098
1108
1099 date = date.strip()
1109 date = date.strip()
1100 if date[0] == "<":
1110 if date[0] == "<":
1101 when = upper(date[1:])
1111 when = upper(date[1:])
1102 return lambda x: x <= when
1112 return lambda x: x <= when
1103 elif date[0] == ">":
1113 elif date[0] == ">":
1104 when = lower(date[1:])
1114 when = lower(date[1:])
1105 return lambda x: x >= when
1115 return lambda x: x >= when
1106 elif date[0] == "-":
1116 elif date[0] == "-":
1107 try:
1117 try:
1108 days = int(date[1:])
1118 days = int(date[1:])
1109 except ValueError:
1119 except ValueError:
1110 raise Abort(_("invalid day spec: %s") % date[1:])
1120 raise Abort(_("invalid day spec: %s") % date[1:])
1111 when = makedate()[0] - days * 3600 * 24
1121 when = makedate()[0] - days * 3600 * 24
1112 return lambda x: x >= when
1122 return lambda x: x >= when
1113 elif " to " in date:
1123 elif " to " in date:
1114 a, b = date.split(" to ")
1124 a, b = date.split(" to ")
1115 start, stop = lower(a), upper(b)
1125 start, stop = lower(a), upper(b)
1116 return lambda x: x >= start and x <= stop
1126 return lambda x: x >= start and x <= stop
1117 else:
1127 else:
1118 start, stop = lower(date), upper(date)
1128 start, stop = lower(date), upper(date)
1119 return lambda x: x >= start and x <= stop
1129 return lambda x: x >= start and x <= stop
1120
1130
1121 def shortuser(user):
1131 def shortuser(user):
1122 """Return a short representation of a user name or email address."""
1132 """Return a short representation of a user name or email address."""
1123 f = user.find('@')
1133 f = user.find('@')
1124 if f >= 0:
1134 if f >= 0:
1125 user = user[:f]
1135 user = user[:f]
1126 f = user.find('<')
1136 f = user.find('<')
1127 if f >= 0:
1137 if f >= 0:
1128 user = user[f + 1:]
1138 user = user[f + 1:]
1129 f = user.find(' ')
1139 f = user.find(' ')
1130 if f >= 0:
1140 if f >= 0:
1131 user = user[:f]
1141 user = user[:f]
1132 f = user.find('.')
1142 f = user.find('.')
1133 if f >= 0:
1143 if f >= 0:
1134 user = user[:f]
1144 user = user[:f]
1135 return user
1145 return user
1136
1146
1137 def email(author):
1147 def email(author):
1138 '''get email of author.'''
1148 '''get email of author.'''
1139 r = author.find('>')
1149 r = author.find('>')
1140 if r == -1:
1150 if r == -1:
1141 r = None
1151 r = None
1142 return author[author.find('<') + 1:r]
1152 return author[author.find('<') + 1:r]
1143
1153
1144 def ellipsis(text, maxlength=400):
1154 def ellipsis(text, maxlength=400):
1145 """Trim string to at most maxlength (default: 400) characters."""
1155 """Trim string to at most maxlength (default: 400) characters."""
1146 if len(text) <= maxlength:
1156 if len(text) <= maxlength:
1147 return text
1157 return text
1148 else:
1158 else:
1149 return "%s..." % (text[:maxlength - 3])
1159 return "%s..." % (text[:maxlength - 3])
1150
1160
1151 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1161 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1152 '''yield every hg repository under path, recursively.'''
1162 '''yield every hg repository under path, recursively.'''
1153 def errhandler(err):
1163 def errhandler(err):
1154 if err.filename == path:
1164 if err.filename == path:
1155 raise err
1165 raise err
1156 if followsym and hasattr(os.path, 'samestat'):
1166 if followsym and hasattr(os.path, 'samestat'):
1157 def _add_dir_if_not_there(dirlst, dirname):
1167 def _add_dir_if_not_there(dirlst, dirname):
1158 match = False
1168 match = False
1159 samestat = os.path.samestat
1169 samestat = os.path.samestat
1160 dirstat = os.stat(dirname)
1170 dirstat = os.stat(dirname)
1161 for lstdirstat in dirlst:
1171 for lstdirstat in dirlst:
1162 if samestat(dirstat, lstdirstat):
1172 if samestat(dirstat, lstdirstat):
1163 match = True
1173 match = True
1164 break
1174 break
1165 if not match:
1175 if not match:
1166 dirlst.append(dirstat)
1176 dirlst.append(dirstat)
1167 return not match
1177 return not match
1168 else:
1178 else:
1169 followsym = False
1179 followsym = False
1170
1180
1171 if (seen_dirs is None) and followsym:
1181 if (seen_dirs is None) and followsym:
1172 seen_dirs = []
1182 seen_dirs = []
1173 _add_dir_if_not_there(seen_dirs, path)
1183 _add_dir_if_not_there(seen_dirs, path)
1174 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1184 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1175 dirs.sort()
1185 dirs.sort()
1176 if '.hg' in dirs:
1186 if '.hg' in dirs:
1177 yield root # found a repository
1187 yield root # found a repository
1178 qroot = os.path.join(root, '.hg', 'patches')
1188 qroot = os.path.join(root, '.hg', 'patches')
1179 if os.path.isdir(os.path.join(qroot, '.hg')):
1189 if os.path.isdir(os.path.join(qroot, '.hg')):
1180 yield qroot # we have a patch queue repo here
1190 yield qroot # we have a patch queue repo here
1181 if recurse:
1191 if recurse:
1182 # avoid recursing inside the .hg directory
1192 # avoid recursing inside the .hg directory
1183 dirs.remove('.hg')
1193 dirs.remove('.hg')
1184 else:
1194 else:
1185 dirs[:] = [] # don't descend further
1195 dirs[:] = [] # don't descend further
1186 elif followsym:
1196 elif followsym:
1187 newdirs = []
1197 newdirs = []
1188 for d in dirs:
1198 for d in dirs:
1189 fname = os.path.join(root, d)
1199 fname = os.path.join(root, d)
1190 if _add_dir_if_not_there(seen_dirs, fname):
1200 if _add_dir_if_not_there(seen_dirs, fname):
1191 if os.path.islink(fname):
1201 if os.path.islink(fname):
1192 for hgname in walkrepos(fname, True, seen_dirs):
1202 for hgname in walkrepos(fname, True, seen_dirs):
1193 yield hgname
1203 yield hgname
1194 else:
1204 else:
1195 newdirs.append(d)
1205 newdirs.append(d)
1196 dirs[:] = newdirs
1206 dirs[:] = newdirs
1197
1207
1198 _rcpath = None
1208 _rcpath = None
1199
1209
1200 def os_rcpath():
1210 def os_rcpath():
1201 '''return default os-specific hgrc search path'''
1211 '''return default os-specific hgrc search path'''
1202 path = system_rcpath()
1212 path = system_rcpath()
1203 path.extend(user_rcpath())
1213 path.extend(user_rcpath())
1204 path = [os.path.normpath(f) for f in path]
1214 path = [os.path.normpath(f) for f in path]
1205 return path
1215 return path
1206
1216
1207 def rcpath():
1217 def rcpath():
1208 '''return hgrc search path. if env var HGRCPATH is set, use it.
1218 '''return hgrc search path. if env var HGRCPATH is set, use it.
1209 for each item in path, if directory, use files ending in .rc,
1219 for each item in path, if directory, use files ending in .rc,
1210 else use item.
1220 else use item.
1211 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1221 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1212 if no HGRCPATH, use default os-specific path.'''
1222 if no HGRCPATH, use default os-specific path.'''
1213 global _rcpath
1223 global _rcpath
1214 if _rcpath is None:
1224 if _rcpath is None:
1215 if 'HGRCPATH' in os.environ:
1225 if 'HGRCPATH' in os.environ:
1216 _rcpath = []
1226 _rcpath = []
1217 for p in os.environ['HGRCPATH'].split(os.pathsep):
1227 for p in os.environ['HGRCPATH'].split(os.pathsep):
1218 if not p:
1228 if not p:
1219 continue
1229 continue
1220 p = expandpath(p)
1230 p = expandpath(p)
1221 if os.path.isdir(p):
1231 if os.path.isdir(p):
1222 for f, kind in osutil.listdir(p):
1232 for f, kind in osutil.listdir(p):
1223 if f.endswith('.rc'):
1233 if f.endswith('.rc'):
1224 _rcpath.append(os.path.join(p, f))
1234 _rcpath.append(os.path.join(p, f))
1225 else:
1235 else:
1226 _rcpath.append(p)
1236 _rcpath.append(p)
1227 else:
1237 else:
1228 _rcpath = os_rcpath()
1238 _rcpath = os_rcpath()
1229 return _rcpath
1239 return _rcpath
1230
1240
1231 def bytecount(nbytes):
1241 def bytecount(nbytes):
1232 '''return byte count formatted as readable string, with units'''
1242 '''return byte count formatted as readable string, with units'''
1233
1243
1234 units = (
1244 units = (
1235 (100, 1 << 30, _('%.0f GB')),
1245 (100, 1 << 30, _('%.0f GB')),
1236 (10, 1 << 30, _('%.1f GB')),
1246 (10, 1 << 30, _('%.1f GB')),
1237 (1, 1 << 30, _('%.2f GB')),
1247 (1, 1 << 30, _('%.2f GB')),
1238 (100, 1 << 20, _('%.0f MB')),
1248 (100, 1 << 20, _('%.0f MB')),
1239 (10, 1 << 20, _('%.1f MB')),
1249 (10, 1 << 20, _('%.1f MB')),
1240 (1, 1 << 20, _('%.2f MB')),
1250 (1, 1 << 20, _('%.2f MB')),
1241 (100, 1 << 10, _('%.0f KB')),
1251 (100, 1 << 10, _('%.0f KB')),
1242 (10, 1 << 10, _('%.1f KB')),
1252 (10, 1 << 10, _('%.1f KB')),
1243 (1, 1 << 10, _('%.2f KB')),
1253 (1, 1 << 10, _('%.2f KB')),
1244 (1, 1, _('%.0f bytes')),
1254 (1, 1, _('%.0f bytes')),
1245 )
1255 )
1246
1256
1247 for multiplier, divisor, format in units:
1257 for multiplier, divisor, format in units:
1248 if nbytes >= divisor * multiplier:
1258 if nbytes >= divisor * multiplier:
1249 return format % (nbytes / float(divisor))
1259 return format % (nbytes / float(divisor))
1250 return units[-1][2] % nbytes
1260 return units[-1][2] % nbytes
1251
1261
1252 def drop_scheme(scheme, path):
1262 def drop_scheme(scheme, path):
1253 sc = scheme + ':'
1263 sc = scheme + ':'
1254 if path.startswith(sc):
1264 if path.startswith(sc):
1255 path = path[len(sc):]
1265 path = path[len(sc):]
1256 if path.startswith('//'):
1266 if path.startswith('//'):
1257 if scheme == 'file':
1267 if scheme == 'file':
1258 i = path.find('/', 2)
1268 i = path.find('/', 2)
1259 if i == -1:
1269 if i == -1:
1260 return ''
1270 return ''
1261 # On Windows, absolute paths are rooted at the current drive
1271 # On Windows, absolute paths are rooted at the current drive
1262 # root. On POSIX they are rooted at the file system root.
1272 # root. On POSIX they are rooted at the file system root.
1263 if os.name == 'nt':
1273 if os.name == 'nt':
1264 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1274 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1265 path = os.path.join(droot, path[i + 1:])
1275 path = os.path.join(droot, path[i + 1:])
1266 else:
1276 else:
1267 path = path[i:]
1277 path = path[i:]
1268 else:
1278 else:
1269 path = path[2:]
1279 path = path[2:]
1270 return path
1280 return path
1271
1281
1272 def uirepr(s):
1282 def uirepr(s):
1273 # Avoid double backslash in Windows path repr()
1283 # Avoid double backslash in Windows path repr()
1274 return repr(s).replace('\\\\', '\\')
1284 return repr(s).replace('\\\\', '\\')
1275
1285
1276 #### naming convention of below implementation follows 'textwrap' module
1286 #### naming convention of below implementation follows 'textwrap' module
1277
1287
1278 class MBTextWrapper(textwrap.TextWrapper):
1288 class MBTextWrapper(textwrap.TextWrapper):
1279 def __init__(self, **kwargs):
1289 def __init__(self, **kwargs):
1280 textwrap.TextWrapper.__init__(self, **kwargs)
1290 textwrap.TextWrapper.__init__(self, **kwargs)
1281
1291
1282 def _cutdown(self, str, space_left):
1292 def _cutdown(self, str, space_left):
1283 l = 0
1293 l = 0
1284 ucstr = unicode(str, encoding.encoding)
1294 ucstr = unicode(str, encoding.encoding)
1285 w = unicodedata.east_asian_width
1295 w = unicodedata.east_asian_width
1286 for i in xrange(len(ucstr)):
1296 for i in xrange(len(ucstr)):
1287 l += w(ucstr[i]) in 'WFA' and 2 or 1
1297 l += w(ucstr[i]) in 'WFA' and 2 or 1
1288 if space_left < l:
1298 if space_left < l:
1289 return (ucstr[:i].encode(encoding.encoding),
1299 return (ucstr[:i].encode(encoding.encoding),
1290 ucstr[i:].encode(encoding.encoding))
1300 ucstr[i:].encode(encoding.encoding))
1291 return str, ''
1301 return str, ''
1292
1302
1293 # ----------------------------------------
1303 # ----------------------------------------
1294 # overriding of base class
1304 # overriding of base class
1295
1305
1296 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1306 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1297 space_left = max(width - cur_len, 1)
1307 space_left = max(width - cur_len, 1)
1298
1308
1299 if self.break_long_words:
1309 if self.break_long_words:
1300 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1310 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1301 cur_line.append(cut)
1311 cur_line.append(cut)
1302 reversed_chunks[-1] = res
1312 reversed_chunks[-1] = res
1303 elif not cur_line:
1313 elif not cur_line:
1304 cur_line.append(reversed_chunks.pop())
1314 cur_line.append(reversed_chunks.pop())
1305
1315
1306 #### naming convention of above implementation follows 'textwrap' module
1316 #### naming convention of above implementation follows 'textwrap' module
1307
1317
1308 def wrap(line, width=None, initindent='', hangindent=''):
1318 def wrap(line, width=None, initindent='', hangindent=''):
1309 if width is None:
1319 if width is None:
1310 width = termwidth() - 2
1320 width = termwidth() - 2
1311 maxindent = max(len(hangindent), len(initindent))
1321 maxindent = max(len(hangindent), len(initindent))
1312 if width <= maxindent:
1322 if width <= maxindent:
1313 # adjust for weird terminal size
1323 # adjust for weird terminal size
1314 width = max(78, maxindent + 1)
1324 width = max(78, maxindent + 1)
1315 wrapper = MBTextWrapper(width=width,
1325 wrapper = MBTextWrapper(width=width,
1316 initial_indent=initindent,
1326 initial_indent=initindent,
1317 subsequent_indent=hangindent)
1327 subsequent_indent=hangindent)
1318 return wrapper.fill(line)
1328 return wrapper.fill(line)
1319
1329
1320 def iterlines(iterator):
1330 def iterlines(iterator):
1321 for chunk in iterator:
1331 for chunk in iterator:
1322 for line in chunk.splitlines():
1332 for line in chunk.splitlines():
1323 yield line
1333 yield line
1324
1334
1325 def expandpath(path):
1335 def expandpath(path):
1326 return os.path.expanduser(os.path.expandvars(path))
1336 return os.path.expanduser(os.path.expandvars(path))
1327
1337
1328 def hgcmd():
1338 def hgcmd():
1329 """Return the command used to execute current hg
1339 """Return the command used to execute current hg
1330
1340
1331 This is different from hgexecutable() because on Windows we want
1341 This is different from hgexecutable() because on Windows we want
1332 to avoid things opening new shell windows like batch files, so we
1342 to avoid things opening new shell windows like batch files, so we
1333 get either the python call or current executable.
1343 get either the python call or current executable.
1334 """
1344 """
1335 if main_is_frozen():
1345 if main_is_frozen():
1336 return [sys.executable]
1346 return [sys.executable]
1337 return gethgcmd()
1347 return gethgcmd()
1338
1348
1339 def rundetached(args, condfn):
1349 def rundetached(args, condfn):
1340 """Execute the argument list in a detached process.
1350 """Execute the argument list in a detached process.
1341
1351
1342 condfn is a callable which is called repeatedly and should return
1352 condfn is a callable which is called repeatedly and should return
1343 True once the child process is known to have started successfully.
1353 True once the child process is known to have started successfully.
1344 At this point, the child process PID is returned. If the child
1354 At this point, the child process PID is returned. If the child
1345 process fails to start or finishes before condfn() evaluates to
1355 process fails to start or finishes before condfn() evaluates to
1346 True, return -1.
1356 True, return -1.
1347 """
1357 """
1348 # Windows case is easier because the child process is either
1358 # Windows case is easier because the child process is either
1349 # successfully starting and validating the condition or exiting
1359 # successfully starting and validating the condition or exiting
1350 # on failure. We just poll on its PID. On Unix, if the child
1360 # on failure. We just poll on its PID. On Unix, if the child
1351 # process fails to start, it will be left in a zombie state until
1361 # process fails to start, it will be left in a zombie state until
1352 # the parent wait on it, which we cannot do since we expect a long
1362 # the parent wait on it, which we cannot do since we expect a long
1353 # running process on success. Instead we listen for SIGCHLD telling
1363 # running process on success. Instead we listen for SIGCHLD telling
1354 # us our child process terminated.
1364 # us our child process terminated.
1355 terminated = set()
1365 terminated = set()
1356 def handler(signum, frame):
1366 def handler(signum, frame):
1357 terminated.add(os.wait())
1367 terminated.add(os.wait())
1358 prevhandler = None
1368 prevhandler = None
1359 if hasattr(signal, 'SIGCHLD'):
1369 if hasattr(signal, 'SIGCHLD'):
1360 prevhandler = signal.signal(signal.SIGCHLD, handler)
1370 prevhandler = signal.signal(signal.SIGCHLD, handler)
1361 try:
1371 try:
1362 pid = spawndetached(args)
1372 pid = spawndetached(args)
1363 while not condfn():
1373 while not condfn():
1364 if ((pid in terminated or not testpid(pid))
1374 if ((pid in terminated or not testpid(pid))
1365 and not condfn()):
1375 and not condfn()):
1366 return -1
1376 return -1
1367 time.sleep(0.1)
1377 time.sleep(0.1)
1368 return pid
1378 return pid
1369 finally:
1379 finally:
1370 if prevhandler is not None:
1380 if prevhandler is not None:
1371 signal.signal(signal.SIGCHLD, prevhandler)
1381 signal.signal(signal.SIGCHLD, prevhandler)
1372
1382
1373 try:
1383 try:
1374 any, all = any, all
1384 any, all = any, all
1375 except NameError:
1385 except NameError:
1376 def any(iterable):
1386 def any(iterable):
1377 for i in iterable:
1387 for i in iterable:
1378 if i:
1388 if i:
1379 return True
1389 return True
1380 return False
1390 return False
1381
1391
1382 def all(iterable):
1392 def all(iterable):
1383 for i in iterable:
1393 for i in iterable:
1384 if not i:
1394 if not i:
1385 return False
1395 return False
1386 return True
1396 return True
1387
1397
1388 def termwidth():
1398 def termwidth():
1389 if 'COLUMNS' in os.environ:
1399 if 'COLUMNS' in os.environ:
1390 try:
1400 try:
1391 return int(os.environ['COLUMNS'])
1401 return int(os.environ['COLUMNS'])
1392 except ValueError:
1402 except ValueError:
1393 pass
1403 pass
1394 return termwidth_()
1404 return termwidth_()
General Comments 0
You need to be logged in to leave comments. Login now