##// END OF EJS Templates
windows: revlog.lazyparser not always safe to use....
Vadim Gelfer -
r2250:45aef5dd default
parent child Browse files
Show More
@@ -1,1246 +1,1254 b''
1 """
1 """
2 revlog.py - storage back-end for mercurial
2 revlog.py - storage back-end for mercurial
3
3
4 This provides efficient delta storage with O(1) retrieve and append
4 This provides efficient delta storage with O(1) retrieve and append
5 and O(changes) merge between branches
5 and O(changes) merge between branches
6
6
7 Copyright 2005 Matt Mackall <mpm@selenic.com>
7 Copyright 2005 Matt Mackall <mpm@selenic.com>
8
8
9 This software may be used and distributed according to the terms
9 This software may be used and distributed according to the terms
10 of the GNU General Public License, incorporated herein by reference.
10 of the GNU General Public License, incorporated herein by reference.
11 """
11 """
12
12
13 from node import *
13 from node import *
14 from i18n import gettext as _
14 from i18n import gettext as _
15 from demandload import demandload
15 from demandload import demandload
16 demandload(globals(), "binascii changegroup errno heapq mdiff os")
16 demandload(globals(), "binascii changegroup errno heapq mdiff os")
17 demandload(globals(), "sha struct util zlib")
17 demandload(globals(), "sha struct util zlib")
18
18
19 # revlog version strings
19 # revlog version strings
20 REVLOGV0 = 0
20 REVLOGV0 = 0
21 REVLOGNG = 1
21 REVLOGNG = 1
22
22
23 # revlog flags
23 # revlog flags
24 REVLOGNGINLINEDATA = (1 << 16)
24 REVLOGNGINLINEDATA = (1 << 16)
25 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
25 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
26
26
27 REVLOG_DEFAULT_FORMAT = REVLOGNG
27 REVLOG_DEFAULT_FORMAT = REVLOGNG
28 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
28 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
29
29
30 def flagstr(flag):
30 def flagstr(flag):
31 if flag == "inline":
31 if flag == "inline":
32 return REVLOGNGINLINEDATA
32 return REVLOGNGINLINEDATA
33 raise RevlogError(_("unknown revlog flag %s" % flag))
33 raise RevlogError(_("unknown revlog flag %s" % flag))
34
34
35 def hash(text, p1, p2):
35 def hash(text, p1, p2):
36 """generate a hash from the given text and its parent hashes
36 """generate a hash from the given text and its parent hashes
37
37
38 This hash combines both the current file contents and its history
38 This hash combines both the current file contents and its history
39 in a manner that makes it easy to distinguish nodes with the same
39 in a manner that makes it easy to distinguish nodes with the same
40 content in the revision graph.
40 content in the revision graph.
41 """
41 """
42 l = [p1, p2]
42 l = [p1, p2]
43 l.sort()
43 l.sort()
44 s = sha.new(l[0])
44 s = sha.new(l[0])
45 s.update(l[1])
45 s.update(l[1])
46 s.update(text)
46 s.update(text)
47 return s.digest()
47 return s.digest()
48
48
49 def compress(text):
49 def compress(text):
50 """ generate a possibly-compressed representation of text """
50 """ generate a possibly-compressed representation of text """
51 if not text: return ("", text)
51 if not text: return ("", text)
52 if len(text) < 44:
52 if len(text) < 44:
53 if text[0] == '\0': return ("", text)
53 if text[0] == '\0': return ("", text)
54 return ('u', text)
54 return ('u', text)
55 bin = zlib.compress(text)
55 bin = zlib.compress(text)
56 if len(bin) > len(text):
56 if len(bin) > len(text):
57 if text[0] == '\0': return ("", text)
57 if text[0] == '\0': return ("", text)
58 return ('u', text)
58 return ('u', text)
59 return ("", bin)
59 return ("", bin)
60
60
61 def decompress(bin):
61 def decompress(bin):
62 """ decompress the given input """
62 """ decompress the given input """
63 if not bin: return bin
63 if not bin: return bin
64 t = bin[0]
64 t = bin[0]
65 if t == '\0': return bin
65 if t == '\0': return bin
66 if t == 'x': return zlib.decompress(bin)
66 if t == 'x': return zlib.decompress(bin)
67 if t == 'u': return bin[1:]
67 if t == 'u': return bin[1:]
68 raise RevlogError(_("unknown compression type %r") % t)
68 raise RevlogError(_("unknown compression type %r") % t)
69
69
70 indexformatv0 = ">4l20s20s20s"
70 indexformatv0 = ">4l20s20s20s"
71 v0shaoffset = 56
71 v0shaoffset = 56
72 # index ng:
72 # index ng:
73 # 6 bytes offset
73 # 6 bytes offset
74 # 2 bytes flags
74 # 2 bytes flags
75 # 4 bytes compressed length
75 # 4 bytes compressed length
76 # 4 bytes uncompressed length
76 # 4 bytes uncompressed length
77 # 4 bytes: base rev
77 # 4 bytes: base rev
78 # 4 bytes link rev
78 # 4 bytes link rev
79 # 4 bytes parent 1 rev
79 # 4 bytes parent 1 rev
80 # 4 bytes parent 2 rev
80 # 4 bytes parent 2 rev
81 # 32 bytes: nodeid
81 # 32 bytes: nodeid
82 indexformatng = ">Qiiiiii20s12x"
82 indexformatng = ">Qiiiiii20s12x"
83 ngshaoffset = 32
83 ngshaoffset = 32
84 versionformat = ">i"
84 versionformat = ">i"
85
85
86 class lazyparser(object):
86 class lazyparser(object):
87 """
87 """
88 this class avoids the need to parse the entirety of large indices
88 this class avoids the need to parse the entirety of large indices
89 """
89 """
90
91 # lazyparser is not safe to use on windows if win32 extensions not
92 # available. it keeps file handle open, which make it not possible
93 # to break hardlinks on local cloned repos.
94 safe_to_use = os.name != 'nt' or (not util.is_win_9x() and
95 hasattr(util, 'win32api'))
96
90 def __init__(self, dataf, size, indexformat, shaoffset):
97 def __init__(self, dataf, size, indexformat, shaoffset):
91 self.dataf = dataf
98 self.dataf = dataf
92 self.format = indexformat
99 self.format = indexformat
93 self.s = struct.calcsize(indexformat)
100 self.s = struct.calcsize(indexformat)
94 self.indexformat = indexformat
101 self.indexformat = indexformat
95 self.datasize = size
102 self.datasize = size
96 self.l = size/self.s
103 self.l = size/self.s
97 self.index = [None] * self.l
104 self.index = [None] * self.l
98 self.map = {nullid: -1}
105 self.map = {nullid: -1}
99 self.allmap = 0
106 self.allmap = 0
100 self.all = 0
107 self.all = 0
101 self.mapfind_count = 0
108 self.mapfind_count = 0
102 self.shaoffset = shaoffset
109 self.shaoffset = shaoffset
103
110
104 def loadmap(self):
111 def loadmap(self):
105 """
112 """
106 during a commit, we need to make sure the rev being added is
113 during a commit, we need to make sure the rev being added is
107 not a duplicate. This requires loading the entire index,
114 not a duplicate. This requires loading the entire index,
108 which is fairly slow. loadmap can load up just the node map,
115 which is fairly slow. loadmap can load up just the node map,
109 which takes much less time.
116 which takes much less time.
110 """
117 """
111 if self.allmap: return
118 if self.allmap: return
112 start = 0
119 start = 0
113 end = self.datasize
120 end = self.datasize
114 self.allmap = 1
121 self.allmap = 1
115 cur = 0
122 cur = 0
116 count = 0
123 count = 0
117 blocksize = self.s * 256
124 blocksize = self.s * 256
118 self.dataf.seek(0)
125 self.dataf.seek(0)
119 while cur < end:
126 while cur < end:
120 data = self.dataf.read(blocksize)
127 data = self.dataf.read(blocksize)
121 off = 0
128 off = 0
122 for x in xrange(256):
129 for x in xrange(256):
123 n = data[off + self.shaoffset:off + self.shaoffset + 20]
130 n = data[off + self.shaoffset:off + self.shaoffset + 20]
124 self.map[n] = count
131 self.map[n] = count
125 count += 1
132 count += 1
126 if count >= self.l:
133 if count >= self.l:
127 break
134 break
128 off += self.s
135 off += self.s
129 cur += blocksize
136 cur += blocksize
130
137
131 def loadblock(self, blockstart, blocksize, data=None):
138 def loadblock(self, blockstart, blocksize, data=None):
132 if self.all: return
139 if self.all: return
133 if data is None:
140 if data is None:
134 self.dataf.seek(blockstart)
141 self.dataf.seek(blockstart)
135 data = self.dataf.read(blocksize)
142 data = self.dataf.read(blocksize)
136 lend = len(data) / self.s
143 lend = len(data) / self.s
137 i = blockstart / self.s
144 i = blockstart / self.s
138 off = 0
145 off = 0
139 for x in xrange(lend):
146 for x in xrange(lend):
140 if self.index[i + x] == None:
147 if self.index[i + x] == None:
141 b = data[off : off + self.s]
148 b = data[off : off + self.s]
142 self.index[i + x] = b
149 self.index[i + x] = b
143 n = b[self.shaoffset:self.shaoffset + 20]
150 n = b[self.shaoffset:self.shaoffset + 20]
144 self.map[n] = i + x
151 self.map[n] = i + x
145 off += self.s
152 off += self.s
146
153
147 def findnode(self, node):
154 def findnode(self, node):
148 """search backwards through the index file for a specific node"""
155 """search backwards through the index file for a specific node"""
149 if self.allmap: return None
156 if self.allmap: return None
150
157
151 # hg log will cause many many searches for the manifest
158 # hg log will cause many many searches for the manifest
152 # nodes. After we get called a few times, just load the whole
159 # nodes. After we get called a few times, just load the whole
153 # thing.
160 # thing.
154 if self.mapfind_count > 8:
161 if self.mapfind_count > 8:
155 self.loadmap()
162 self.loadmap()
156 if node in self.map:
163 if node in self.map:
157 return node
164 return node
158 return None
165 return None
159 self.mapfind_count += 1
166 self.mapfind_count += 1
160 last = self.l - 1
167 last = self.l - 1
161 while self.index[last] != None:
168 while self.index[last] != None:
162 if last == 0:
169 if last == 0:
163 self.all = 1
170 self.all = 1
164 self.allmap = 1
171 self.allmap = 1
165 return None
172 return None
166 last -= 1
173 last -= 1
167 end = (last + 1) * self.s
174 end = (last + 1) * self.s
168 blocksize = self.s * 256
175 blocksize = self.s * 256
169 while end >= 0:
176 while end >= 0:
170 start = max(end - blocksize, 0)
177 start = max(end - blocksize, 0)
171 self.dataf.seek(start)
178 self.dataf.seek(start)
172 data = self.dataf.read(end - start)
179 data = self.dataf.read(end - start)
173 findend = end - start
180 findend = end - start
174 while True:
181 while True:
175 # we're searching backwards, so weh have to make sure
182 # we're searching backwards, so weh have to make sure
176 # we don't find a changeset where this node is a parent
183 # we don't find a changeset where this node is a parent
177 off = data.rfind(node, 0, findend)
184 off = data.rfind(node, 0, findend)
178 findend = off
185 findend = off
179 if off >= 0:
186 if off >= 0:
180 i = off / self.s
187 i = off / self.s
181 off = i * self.s
188 off = i * self.s
182 n = data[off + self.shaoffset:off + self.shaoffset + 20]
189 n = data[off + self.shaoffset:off + self.shaoffset + 20]
183 if n == node:
190 if n == node:
184 self.map[n] = i + start / self.s
191 self.map[n] = i + start / self.s
185 return node
192 return node
186 else:
193 else:
187 break
194 break
188 end -= blocksize
195 end -= blocksize
189 return None
196 return None
190
197
191 def loadindex(self, i=None, end=None):
198 def loadindex(self, i=None, end=None):
192 if self.all: return
199 if self.all: return
193 all = False
200 all = False
194 if i == None:
201 if i == None:
195 blockstart = 0
202 blockstart = 0
196 blocksize = (512 / self.s) * self.s
203 blocksize = (512 / self.s) * self.s
197 end = self.datasize
204 end = self.datasize
198 all = True
205 all = True
199 else:
206 else:
200 if end:
207 if end:
201 blockstart = i * self.s
208 blockstart = i * self.s
202 end = end * self.s
209 end = end * self.s
203 blocksize = end - blockstart
210 blocksize = end - blockstart
204 else:
211 else:
205 blockstart = (i & ~(32)) * self.s
212 blockstart = (i & ~(32)) * self.s
206 blocksize = self.s * 64
213 blocksize = self.s * 64
207 end = blockstart + blocksize
214 end = blockstart + blocksize
208 while blockstart < end:
215 while blockstart < end:
209 self.loadblock(blockstart, blocksize)
216 self.loadblock(blockstart, blocksize)
210 blockstart += blocksize
217 blockstart += blocksize
211 if all: self.all = True
218 if all: self.all = True
212
219
213 class lazyindex(object):
220 class lazyindex(object):
214 """a lazy version of the index array"""
221 """a lazy version of the index array"""
215 def __init__(self, parser):
222 def __init__(self, parser):
216 self.p = parser
223 self.p = parser
217 def __len__(self):
224 def __len__(self):
218 return len(self.p.index)
225 return len(self.p.index)
219 def load(self, pos):
226 def load(self, pos):
220 if pos < 0:
227 if pos < 0:
221 pos += len(self.p.index)
228 pos += len(self.p.index)
222 self.p.loadindex(pos)
229 self.p.loadindex(pos)
223 return self.p.index[pos]
230 return self.p.index[pos]
224 def __getitem__(self, pos):
231 def __getitem__(self, pos):
225 ret = self.p.index[pos] or self.load(pos)
232 ret = self.p.index[pos] or self.load(pos)
226 if isinstance(ret, str):
233 if isinstance(ret, str):
227 ret = struct.unpack(self.p.indexformat, ret)
234 ret = struct.unpack(self.p.indexformat, ret)
228 return ret
235 return ret
229 def __setitem__(self, pos, item):
236 def __setitem__(self, pos, item):
230 self.p.index[pos] = item
237 self.p.index[pos] = item
231 def __delitem__(self, pos):
238 def __delitem__(self, pos):
232 del self.p.index[pos]
239 del self.p.index[pos]
233 def append(self, e):
240 def append(self, e):
234 self.p.index.append(e)
241 self.p.index.append(e)
235
242
236 class lazymap(object):
243 class lazymap(object):
237 """a lazy version of the node map"""
244 """a lazy version of the node map"""
238 def __init__(self, parser):
245 def __init__(self, parser):
239 self.p = parser
246 self.p = parser
240 def load(self, key):
247 def load(self, key):
241 n = self.p.findnode(key)
248 n = self.p.findnode(key)
242 if n == None:
249 if n == None:
243 raise KeyError(key)
250 raise KeyError(key)
244 def __contains__(self, key):
251 def __contains__(self, key):
245 if key in self.p.map:
252 if key in self.p.map:
246 return True
253 return True
247 self.p.loadmap()
254 self.p.loadmap()
248 return key in self.p.map
255 return key in self.p.map
249 def __iter__(self):
256 def __iter__(self):
250 yield nullid
257 yield nullid
251 for i in xrange(self.p.l):
258 for i in xrange(self.p.l):
252 ret = self.p.index[i]
259 ret = self.p.index[i]
253 if not ret:
260 if not ret:
254 self.p.loadindex(i)
261 self.p.loadindex(i)
255 ret = self.p.index[i]
262 ret = self.p.index[i]
256 if isinstance(ret, str):
263 if isinstance(ret, str):
257 ret = struct.unpack(self.p.indexformat, ret)
264 ret = struct.unpack(self.p.indexformat, ret)
258 yield ret[-1]
265 yield ret[-1]
259 def __getitem__(self, key):
266 def __getitem__(self, key):
260 try:
267 try:
261 return self.p.map[key]
268 return self.p.map[key]
262 except KeyError:
269 except KeyError:
263 try:
270 try:
264 self.load(key)
271 self.load(key)
265 return self.p.map[key]
272 return self.p.map[key]
266 except KeyError:
273 except KeyError:
267 raise KeyError("node " + hex(key))
274 raise KeyError("node " + hex(key))
268 def __setitem__(self, key, val):
275 def __setitem__(self, key, val):
269 self.p.map[key] = val
276 self.p.map[key] = val
270 def __delitem__(self, key):
277 def __delitem__(self, key):
271 del self.p.map[key]
278 del self.p.map[key]
272
279
273 class RevlogError(Exception): pass
280 class RevlogError(Exception): pass
274
281
275 class revlog(object):
282 class revlog(object):
276 """
283 """
277 the underlying revision storage object
284 the underlying revision storage object
278
285
279 A revlog consists of two parts, an index and the revision data.
286 A revlog consists of two parts, an index and the revision data.
280
287
281 The index is a file with a fixed record size containing
288 The index is a file with a fixed record size containing
282 information on each revision, includings its nodeid (hash), the
289 information on each revision, includings its nodeid (hash), the
283 nodeids of its parents, the position and offset of its data within
290 nodeids of its parents, the position and offset of its data within
284 the data file, and the revision it's based on. Finally, each entry
291 the data file, and the revision it's based on. Finally, each entry
285 contains a linkrev entry that can serve as a pointer to external
292 contains a linkrev entry that can serve as a pointer to external
286 data.
293 data.
287
294
288 The revision data itself is a linear collection of data chunks.
295 The revision data itself is a linear collection of data chunks.
289 Each chunk represents a revision and is usually represented as a
296 Each chunk represents a revision and is usually represented as a
290 delta against the previous chunk. To bound lookup time, runs of
297 delta against the previous chunk. To bound lookup time, runs of
291 deltas are limited to about 2 times the length of the original
298 deltas are limited to about 2 times the length of the original
292 version data. This makes retrieval of a version proportional to
299 version data. This makes retrieval of a version proportional to
293 its size, or O(1) relative to the number of revisions.
300 its size, or O(1) relative to the number of revisions.
294
301
295 Both pieces of the revlog are written to in an append-only
302 Both pieces of the revlog are written to in an append-only
296 fashion, which means we never need to rewrite a file to insert or
303 fashion, which means we never need to rewrite a file to insert or
297 remove data, and can use some simple techniques to avoid the need
304 remove data, and can use some simple techniques to avoid the need
298 for locking while reading.
305 for locking while reading.
299 """
306 """
300 def __init__(self, opener, indexfile, datafile,
307 def __init__(self, opener, indexfile, datafile,
301 defversion=REVLOG_DEFAULT_VERSION):
308 defversion=REVLOG_DEFAULT_VERSION):
302 """
309 """
303 create a revlog object
310 create a revlog object
304
311
305 opener is a function that abstracts the file opening operation
312 opener is a function that abstracts the file opening operation
306 and can be used to implement COW semantics or the like.
313 and can be used to implement COW semantics or the like.
307 """
314 """
308 self.indexfile = indexfile
315 self.indexfile = indexfile
309 self.datafile = datafile
316 self.datafile = datafile
310 self.opener = opener
317 self.opener = opener
311
318
312 self.indexstat = None
319 self.indexstat = None
313 self.cache = None
320 self.cache = None
314 self.chunkcache = None
321 self.chunkcache = None
315 self.defversion = defversion
322 self.defversion = defversion
316 self.load()
323 self.load()
317
324
318 def load(self):
325 def load(self):
319 v = self.defversion
326 v = self.defversion
320 try:
327 try:
321 f = self.opener(self.indexfile)
328 f = self.opener(self.indexfile)
322 i = f.read(4)
329 i = f.read(4)
323 f.seek(0)
330 f.seek(0)
324 except IOError, inst:
331 except IOError, inst:
325 if inst.errno != errno.ENOENT:
332 if inst.errno != errno.ENOENT:
326 raise
333 raise
327 i = ""
334 i = ""
328 else:
335 else:
329 try:
336 try:
330 st = util.fstat(f)
337 st = util.fstat(f)
331 except AttributeError, inst:
338 except AttributeError, inst:
332 st = None
339 st = None
333 else:
340 else:
334 oldst = self.indexstat
341 oldst = self.indexstat
335 if (oldst and st.st_dev == oldst.st_dev
342 if (oldst and st.st_dev == oldst.st_dev
336 and st.st_ino == oldst.st_ino
343 and st.st_ino == oldst.st_ino
337 and st.st_mtime == oldst.st_mtime
344 and st.st_mtime == oldst.st_mtime
338 and st.st_ctime == oldst.st_ctime):
345 and st.st_ctime == oldst.st_ctime):
339 return
346 return
340 self.indexstat = st
347 self.indexstat = st
341 if len(i) > 0:
348 if len(i) > 0:
342 v = struct.unpack(versionformat, i)[0]
349 v = struct.unpack(versionformat, i)[0]
343 flags = v & ~0xFFFF
350 flags = v & ~0xFFFF
344 fmt = v & 0xFFFF
351 fmt = v & 0xFFFF
345 if fmt == REVLOGV0:
352 if fmt == REVLOGV0:
346 if flags:
353 if flags:
347 raise RevlogError(_("index %s invalid flags %x for format v0" %
354 raise RevlogError(_("index %s invalid flags %x for format v0" %
348 (self.indexfile, flags)))
355 (self.indexfile, flags)))
349 elif fmt == REVLOGNG:
356 elif fmt == REVLOGNG:
350 if flags & ~REVLOGNGINLINEDATA:
357 if flags & ~REVLOGNGINLINEDATA:
351 raise RevlogError(_("index %s invalid flags %x for revlogng" %
358 raise RevlogError(_("index %s invalid flags %x for revlogng" %
352 (self.indexfile, flags)))
359 (self.indexfile, flags)))
353 else:
360 else:
354 raise RevlogError(_("index %s invalid format %d" %
361 raise RevlogError(_("index %s invalid format %d" %
355 (self.indexfile, fmt)))
362 (self.indexfile, fmt)))
356 self.version = v
363 self.version = v
357 if v == REVLOGV0:
364 if v == REVLOGV0:
358 self.indexformat = indexformatv0
365 self.indexformat = indexformatv0
359 shaoffset = v0shaoffset
366 shaoffset = v0shaoffset
360 else:
367 else:
361 self.indexformat = indexformatng
368 self.indexformat = indexformatng
362 shaoffset = ngshaoffset
369 shaoffset = ngshaoffset
363
370
364 if i:
371 if i:
365 if not self.inlinedata() and st and st.st_size > 10000:
372 if (lazyparser.safe_to_use and not self.inlinedata() and
373 st and st.st_size > 10000):
366 # big index, let's parse it on demand
374 # big index, let's parse it on demand
367 parser = lazyparser(f, st.st_size, self.indexformat, shaoffset)
375 parser = lazyparser(f, st.st_size, self.indexformat, shaoffset)
368 self.index = lazyindex(parser)
376 self.index = lazyindex(parser)
369 self.nodemap = lazymap(parser)
377 self.nodemap = lazymap(parser)
370 else:
378 else:
371 i = f.read()
379 i = f.read()
372 self.parseindex(i)
380 self.parseindex(i)
373 if self.inlinedata():
381 if self.inlinedata():
374 # we've already got the entire data file read in, save it
382 # we've already got the entire data file read in, save it
375 # in the chunk data
383 # in the chunk data
376 self.chunkcache = (0, i)
384 self.chunkcache = (0, i)
377 if self.version != REVLOGV0:
385 if self.version != REVLOGV0:
378 e = list(self.index[0])
386 e = list(self.index[0])
379 type = self.ngtype(e[0])
387 type = self.ngtype(e[0])
380 e[0] = self.offset_type(0, type)
388 e[0] = self.offset_type(0, type)
381 self.index[0] = e
389 self.index[0] = e
382 else:
390 else:
383 self.nodemap = { nullid: -1}
391 self.nodemap = { nullid: -1}
384 self.index = []
392 self.index = []
385
393
386
394
387 def parseindex(self, data):
395 def parseindex(self, data):
388 s = struct.calcsize(self.indexformat)
396 s = struct.calcsize(self.indexformat)
389 l = len(data)
397 l = len(data)
390 self.index = []
398 self.index = []
391 self.nodemap = {nullid: -1}
399 self.nodemap = {nullid: -1}
392 inline = self.inlinedata()
400 inline = self.inlinedata()
393 off = 0
401 off = 0
394 n = 0
402 n = 0
395 while off < l:
403 while off < l:
396 e = struct.unpack(self.indexformat, data[off:off + s])
404 e = struct.unpack(self.indexformat, data[off:off + s])
397 self.index.append(e)
405 self.index.append(e)
398 self.nodemap[e[-1]] = n
406 self.nodemap[e[-1]] = n
399 n += 1
407 n += 1
400 off += s
408 off += s
401 if inline:
409 if inline:
402 off += e[1]
410 off += e[1]
403
411
404 def ngoffset(self, q):
412 def ngoffset(self, q):
405 if q & 0xFFFF:
413 if q & 0xFFFF:
406 raise RevlogError(_('%s: incompatible revision flag %x') %
414 raise RevlogError(_('%s: incompatible revision flag %x') %
407 (self.indexfile, q))
415 (self.indexfile, q))
408 return long(q >> 16)
416 return long(q >> 16)
409
417
410 def ngtype(self, q):
418 def ngtype(self, q):
411 return int(q & 0xFFFF)
419 return int(q & 0xFFFF)
412
420
413 def offset_type(self, offset, type):
421 def offset_type(self, offset, type):
414 return long(long(offset) << 16 | type)
422 return long(long(offset) << 16 | type)
415
423
416 def loadindex(self, start, end):
424 def loadindex(self, start, end):
417 """load a block of indexes all at once from the lazy parser"""
425 """load a block of indexes all at once from the lazy parser"""
418 if isinstance(self.index, lazyindex):
426 if isinstance(self.index, lazyindex):
419 self.index.p.loadindex(start, end)
427 self.index.p.loadindex(start, end)
420
428
421 def loadindexmap(self):
429 def loadindexmap(self):
422 """loads both the map and the index from the lazy parser"""
430 """loads both the map and the index from the lazy parser"""
423 if isinstance(self.index, lazyindex):
431 if isinstance(self.index, lazyindex):
424 p = self.index.p
432 p = self.index.p
425 p.loadindex()
433 p.loadindex()
426 self.nodemap = p.map
434 self.nodemap = p.map
427
435
428 def loadmap(self):
436 def loadmap(self):
429 """loads the map from the lazy parser"""
437 """loads the map from the lazy parser"""
430 if isinstance(self.nodemap, lazymap):
438 if isinstance(self.nodemap, lazymap):
431 self.nodemap.p.loadmap()
439 self.nodemap.p.loadmap()
432 self.nodemap = self.nodemap.p.map
440 self.nodemap = self.nodemap.p.map
433
441
434 def inlinedata(self): return self.version & REVLOGNGINLINEDATA
442 def inlinedata(self): return self.version & REVLOGNGINLINEDATA
435 def tip(self): return self.node(len(self.index) - 1)
443 def tip(self): return self.node(len(self.index) - 1)
436 def count(self): return len(self.index)
444 def count(self): return len(self.index)
437 def node(self, rev):
445 def node(self, rev):
438 return (rev < 0) and nullid or self.index[rev][-1]
446 return (rev < 0) and nullid or self.index[rev][-1]
439 def rev(self, node):
447 def rev(self, node):
440 try:
448 try:
441 return self.nodemap[node]
449 return self.nodemap[node]
442 except KeyError:
450 except KeyError:
443 raise RevlogError(_('%s: no node %s') % (self.indexfile, hex(node)))
451 raise RevlogError(_('%s: no node %s') % (self.indexfile, hex(node)))
444 def linkrev(self, node): return self.index[self.rev(node)][-4]
452 def linkrev(self, node): return self.index[self.rev(node)][-4]
445 def parents(self, node):
453 def parents(self, node):
446 if node == nullid: return (nullid, nullid)
454 if node == nullid: return (nullid, nullid)
447 r = self.rev(node)
455 r = self.rev(node)
448 d = self.index[r][-3:-1]
456 d = self.index[r][-3:-1]
449 if self.version == REVLOGV0:
457 if self.version == REVLOGV0:
450 return d
458 return d
451 return [ self.node(x) for x in d ]
459 return [ self.node(x) for x in d ]
452 def start(self, rev):
460 def start(self, rev):
453 if rev < 0:
461 if rev < 0:
454 return -1
462 return -1
455 if self.version != REVLOGV0:
463 if self.version != REVLOGV0:
456 return self.ngoffset(self.index[rev][0])
464 return self.ngoffset(self.index[rev][0])
457 return self.index[rev][0]
465 return self.index[rev][0]
458
466
459 def end(self, rev): return self.start(rev) + self.length(rev)
467 def end(self, rev): return self.start(rev) + self.length(rev)
460
468
461 def size(self, rev):
469 def size(self, rev):
462 """return the length of the uncompressed text for a given revision"""
470 """return the length of the uncompressed text for a given revision"""
463 l = -1
471 l = -1
464 if self.version != REVLOGV0:
472 if self.version != REVLOGV0:
465 l = self.index[rev][2]
473 l = self.index[rev][2]
466 if l >= 0:
474 if l >= 0:
467 return l
475 return l
468
476
469 t = self.revision(self.node(rev))
477 t = self.revision(self.node(rev))
470 return len(t)
478 return len(t)
471
479
472 # alternate implementation, The advantage to this code is it
480 # alternate implementation, The advantage to this code is it
473 # will be faster for a single revision. But, the results are not
481 # will be faster for a single revision. But, the results are not
474 # cached, so finding the size of every revision will be slower.
482 # cached, so finding the size of every revision will be slower.
475 """
483 """
476 if self.cache and self.cache[1] == rev:
484 if self.cache and self.cache[1] == rev:
477 return len(self.cache[2])
485 return len(self.cache[2])
478
486
479 base = self.base(rev)
487 base = self.base(rev)
480 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
488 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
481 base = self.cache[1]
489 base = self.cache[1]
482 text = self.cache[2]
490 text = self.cache[2]
483 else:
491 else:
484 text = self.revision(self.node(base))
492 text = self.revision(self.node(base))
485
493
486 l = len(text)
494 l = len(text)
487 for x in xrange(base + 1, rev + 1):
495 for x in xrange(base + 1, rev + 1):
488 l = mdiff.patchedsize(l, self.chunk(x))
496 l = mdiff.patchedsize(l, self.chunk(x))
489 return l
497 return l
490 """
498 """
491
499
492 def length(self, rev):
500 def length(self, rev):
493 if rev < 0:
501 if rev < 0:
494 return 0
502 return 0
495 else:
503 else:
496 return self.index[rev][1]
504 return self.index[rev][1]
497 def base(self, rev): return (rev < 0) and rev or self.index[rev][-5]
505 def base(self, rev): return (rev < 0) and rev or self.index[rev][-5]
498
506
499 def reachable(self, rev, stop=None):
507 def reachable(self, rev, stop=None):
500 reachable = {}
508 reachable = {}
501 visit = [rev]
509 visit = [rev]
502 reachable[rev] = 1
510 reachable[rev] = 1
503 if stop:
511 if stop:
504 stopn = self.rev(stop)
512 stopn = self.rev(stop)
505 else:
513 else:
506 stopn = 0
514 stopn = 0
507 while visit:
515 while visit:
508 n = visit.pop(0)
516 n = visit.pop(0)
509 if n == stop:
517 if n == stop:
510 continue
518 continue
511 if n == nullid:
519 if n == nullid:
512 continue
520 continue
513 for p in self.parents(n):
521 for p in self.parents(n):
514 if self.rev(p) < stopn:
522 if self.rev(p) < stopn:
515 continue
523 continue
516 if p not in reachable:
524 if p not in reachable:
517 reachable[p] = 1
525 reachable[p] = 1
518 visit.append(p)
526 visit.append(p)
519 return reachable
527 return reachable
520
528
521 def nodesbetween(self, roots=None, heads=None):
529 def nodesbetween(self, roots=None, heads=None):
522 """Return a tuple containing three elements. Elements 1 and 2 contain
530 """Return a tuple containing three elements. Elements 1 and 2 contain
523 a final list bases and heads after all the unreachable ones have been
531 a final list bases and heads after all the unreachable ones have been
524 pruned. Element 0 contains a topologically sorted list of all
532 pruned. Element 0 contains a topologically sorted list of all
525
533
526 nodes that satisfy these constraints:
534 nodes that satisfy these constraints:
527 1. All nodes must be descended from a node in roots (the nodes on
535 1. All nodes must be descended from a node in roots (the nodes on
528 roots are considered descended from themselves).
536 roots are considered descended from themselves).
529 2. All nodes must also be ancestors of a node in heads (the nodes in
537 2. All nodes must also be ancestors of a node in heads (the nodes in
530 heads are considered to be their own ancestors).
538 heads are considered to be their own ancestors).
531
539
532 If roots is unspecified, nullid is assumed as the only root.
540 If roots is unspecified, nullid is assumed as the only root.
533 If heads is unspecified, it is taken to be the output of the
541 If heads is unspecified, it is taken to be the output of the
534 heads method (i.e. a list of all nodes in the repository that
542 heads method (i.e. a list of all nodes in the repository that
535 have no children)."""
543 have no children)."""
536 nonodes = ([], [], [])
544 nonodes = ([], [], [])
537 if roots is not None:
545 if roots is not None:
538 roots = list(roots)
546 roots = list(roots)
539 if not roots:
547 if not roots:
540 return nonodes
548 return nonodes
541 lowestrev = min([self.rev(n) for n in roots])
549 lowestrev = min([self.rev(n) for n in roots])
542 else:
550 else:
543 roots = [nullid] # Everybody's a descendent of nullid
551 roots = [nullid] # Everybody's a descendent of nullid
544 lowestrev = -1
552 lowestrev = -1
545 if (lowestrev == -1) and (heads is None):
553 if (lowestrev == -1) and (heads is None):
546 # We want _all_ the nodes!
554 # We want _all_ the nodes!
547 return ([self.node(r) for r in xrange(0, self.count())],
555 return ([self.node(r) for r in xrange(0, self.count())],
548 [nullid], list(self.heads()))
556 [nullid], list(self.heads()))
549 if heads is None:
557 if heads is None:
550 # All nodes are ancestors, so the latest ancestor is the last
558 # All nodes are ancestors, so the latest ancestor is the last
551 # node.
559 # node.
552 highestrev = self.count() - 1
560 highestrev = self.count() - 1
553 # Set ancestors to None to signal that every node is an ancestor.
561 # Set ancestors to None to signal that every node is an ancestor.
554 ancestors = None
562 ancestors = None
555 # Set heads to an empty dictionary for later discovery of heads
563 # Set heads to an empty dictionary for later discovery of heads
556 heads = {}
564 heads = {}
557 else:
565 else:
558 heads = list(heads)
566 heads = list(heads)
559 if not heads:
567 if not heads:
560 return nonodes
568 return nonodes
561 ancestors = {}
569 ancestors = {}
562 # Start at the top and keep marking parents until we're done.
570 # Start at the top and keep marking parents until we're done.
563 nodestotag = heads[:]
571 nodestotag = heads[:]
564 # Turn heads into a dictionary so we can remove 'fake' heads.
572 # Turn heads into a dictionary so we can remove 'fake' heads.
565 # Also, later we will be using it to filter out the heads we can't
573 # Also, later we will be using it to filter out the heads we can't
566 # find from roots.
574 # find from roots.
567 heads = dict.fromkeys(heads, 0)
575 heads = dict.fromkeys(heads, 0)
568 # Remember where the top was so we can use it as a limit later.
576 # Remember where the top was so we can use it as a limit later.
569 highestrev = max([self.rev(n) for n in nodestotag])
577 highestrev = max([self.rev(n) for n in nodestotag])
570 while nodestotag:
578 while nodestotag:
571 # grab a node to tag
579 # grab a node to tag
572 n = nodestotag.pop()
580 n = nodestotag.pop()
573 # Never tag nullid
581 # Never tag nullid
574 if n == nullid:
582 if n == nullid:
575 continue
583 continue
576 # A node's revision number represents its place in a
584 # A node's revision number represents its place in a
577 # topologically sorted list of nodes.
585 # topologically sorted list of nodes.
578 r = self.rev(n)
586 r = self.rev(n)
579 if r >= lowestrev:
587 if r >= lowestrev:
580 if n not in ancestors:
588 if n not in ancestors:
581 # If we are possibly a descendent of one of the roots
589 # If we are possibly a descendent of one of the roots
582 # and we haven't already been marked as an ancestor
590 # and we haven't already been marked as an ancestor
583 ancestors[n] = 1 # Mark as ancestor
591 ancestors[n] = 1 # Mark as ancestor
584 # Add non-nullid parents to list of nodes to tag.
592 # Add non-nullid parents to list of nodes to tag.
585 nodestotag.extend([p for p in self.parents(n) if
593 nodestotag.extend([p for p in self.parents(n) if
586 p != nullid])
594 p != nullid])
587 elif n in heads: # We've seen it before, is it a fake head?
595 elif n in heads: # We've seen it before, is it a fake head?
588 # So it is, real heads should not be the ancestors of
596 # So it is, real heads should not be the ancestors of
589 # any other heads.
597 # any other heads.
590 heads.pop(n)
598 heads.pop(n)
591 if not ancestors:
599 if not ancestors:
592 return nonodes
600 return nonodes
593 # Now that we have our set of ancestors, we want to remove any
601 # Now that we have our set of ancestors, we want to remove any
594 # roots that are not ancestors.
602 # roots that are not ancestors.
595
603
596 # If one of the roots was nullid, everything is included anyway.
604 # If one of the roots was nullid, everything is included anyway.
597 if lowestrev > -1:
605 if lowestrev > -1:
598 # But, since we weren't, let's recompute the lowest rev to not
606 # But, since we weren't, let's recompute the lowest rev to not
599 # include roots that aren't ancestors.
607 # include roots that aren't ancestors.
600
608
601 # Filter out roots that aren't ancestors of heads
609 # Filter out roots that aren't ancestors of heads
602 roots = [n for n in roots if n in ancestors]
610 roots = [n for n in roots if n in ancestors]
603 # Recompute the lowest revision
611 # Recompute the lowest revision
604 if roots:
612 if roots:
605 lowestrev = min([self.rev(n) for n in roots])
613 lowestrev = min([self.rev(n) for n in roots])
606 else:
614 else:
607 # No more roots? Return empty list
615 # No more roots? Return empty list
608 return nonodes
616 return nonodes
609 else:
617 else:
610 # We are descending from nullid, and don't need to care about
618 # We are descending from nullid, and don't need to care about
611 # any other roots.
619 # any other roots.
612 lowestrev = -1
620 lowestrev = -1
613 roots = [nullid]
621 roots = [nullid]
614 # Transform our roots list into a 'set' (i.e. a dictionary where the
622 # Transform our roots list into a 'set' (i.e. a dictionary where the
615 # values don't matter.
623 # values don't matter.
616 descendents = dict.fromkeys(roots, 1)
624 descendents = dict.fromkeys(roots, 1)
617 # Also, keep the original roots so we can filter out roots that aren't
625 # Also, keep the original roots so we can filter out roots that aren't
618 # 'real' roots (i.e. are descended from other roots).
626 # 'real' roots (i.e. are descended from other roots).
619 roots = descendents.copy()
627 roots = descendents.copy()
620 # Our topologically sorted list of output nodes.
628 # Our topologically sorted list of output nodes.
621 orderedout = []
629 orderedout = []
622 # Don't start at nullid since we don't want nullid in our output list,
630 # Don't start at nullid since we don't want nullid in our output list,
623 # and if nullid shows up in descedents, empty parents will look like
631 # and if nullid shows up in descedents, empty parents will look like
624 # they're descendents.
632 # they're descendents.
625 for r in xrange(max(lowestrev, 0), highestrev + 1):
633 for r in xrange(max(lowestrev, 0), highestrev + 1):
626 n = self.node(r)
634 n = self.node(r)
627 isdescendent = False
635 isdescendent = False
628 if lowestrev == -1: # Everybody is a descendent of nullid
636 if lowestrev == -1: # Everybody is a descendent of nullid
629 isdescendent = True
637 isdescendent = True
630 elif n in descendents:
638 elif n in descendents:
631 # n is already a descendent
639 # n is already a descendent
632 isdescendent = True
640 isdescendent = True
633 # This check only needs to be done here because all the roots
641 # This check only needs to be done here because all the roots
634 # will start being marked is descendents before the loop.
642 # will start being marked is descendents before the loop.
635 if n in roots:
643 if n in roots:
636 # If n was a root, check if it's a 'real' root.
644 # If n was a root, check if it's a 'real' root.
637 p = tuple(self.parents(n))
645 p = tuple(self.parents(n))
638 # If any of its parents are descendents, it's not a root.
646 # If any of its parents are descendents, it's not a root.
639 if (p[0] in descendents) or (p[1] in descendents):
647 if (p[0] in descendents) or (p[1] in descendents):
640 roots.pop(n)
648 roots.pop(n)
641 else:
649 else:
642 p = tuple(self.parents(n))
650 p = tuple(self.parents(n))
643 # A node is a descendent if either of its parents are
651 # A node is a descendent if either of its parents are
644 # descendents. (We seeded the dependents list with the roots
652 # descendents. (We seeded the dependents list with the roots
645 # up there, remember?)
653 # up there, remember?)
646 if (p[0] in descendents) or (p[1] in descendents):
654 if (p[0] in descendents) or (p[1] in descendents):
647 descendents[n] = 1
655 descendents[n] = 1
648 isdescendent = True
656 isdescendent = True
649 if isdescendent and ((ancestors is None) or (n in ancestors)):
657 if isdescendent and ((ancestors is None) or (n in ancestors)):
650 # Only include nodes that are both descendents and ancestors.
658 # Only include nodes that are both descendents and ancestors.
651 orderedout.append(n)
659 orderedout.append(n)
652 if (ancestors is not None) and (n in heads):
660 if (ancestors is not None) and (n in heads):
653 # We're trying to figure out which heads are reachable
661 # We're trying to figure out which heads are reachable
654 # from roots.
662 # from roots.
655 # Mark this head as having been reached
663 # Mark this head as having been reached
656 heads[n] = 1
664 heads[n] = 1
657 elif ancestors is None:
665 elif ancestors is None:
658 # Otherwise, we're trying to discover the heads.
666 # Otherwise, we're trying to discover the heads.
659 # Assume this is a head because if it isn't, the next step
667 # Assume this is a head because if it isn't, the next step
660 # will eventually remove it.
668 # will eventually remove it.
661 heads[n] = 1
669 heads[n] = 1
662 # But, obviously its parents aren't.
670 # But, obviously its parents aren't.
663 for p in self.parents(n):
671 for p in self.parents(n):
664 heads.pop(p, None)
672 heads.pop(p, None)
665 heads = [n for n in heads.iterkeys() if heads[n] != 0]
673 heads = [n for n in heads.iterkeys() if heads[n] != 0]
666 roots = roots.keys()
674 roots = roots.keys()
667 assert orderedout
675 assert orderedout
668 assert roots
676 assert roots
669 assert heads
677 assert heads
670 return (orderedout, roots, heads)
678 return (orderedout, roots, heads)
671
679
672 def heads(self, start=None):
680 def heads(self, start=None):
673 """return the list of all nodes that have no children
681 """return the list of all nodes that have no children
674
682
675 if start is specified, only heads that are descendants of
683 if start is specified, only heads that are descendants of
676 start will be returned
684 start will be returned
677
685
678 """
686 """
679 if start is None:
687 if start is None:
680 start = nullid
688 start = nullid
681 reachable = {start: 1}
689 reachable = {start: 1}
682 heads = {start: 1}
690 heads = {start: 1}
683 startrev = self.rev(start)
691 startrev = self.rev(start)
684
692
685 for r in xrange(startrev + 1, self.count()):
693 for r in xrange(startrev + 1, self.count()):
686 n = self.node(r)
694 n = self.node(r)
687 for pn in self.parents(n):
695 for pn in self.parents(n):
688 if pn in reachable:
696 if pn in reachable:
689 reachable[n] = 1
697 reachable[n] = 1
690 heads[n] = 1
698 heads[n] = 1
691 if pn in heads:
699 if pn in heads:
692 del heads[pn]
700 del heads[pn]
693 return heads.keys()
701 return heads.keys()
694
702
695 def children(self, node):
703 def children(self, node):
696 """find the children of a given node"""
704 """find the children of a given node"""
697 c = []
705 c = []
698 p = self.rev(node)
706 p = self.rev(node)
699 for r in range(p + 1, self.count()):
707 for r in range(p + 1, self.count()):
700 n = self.node(r)
708 n = self.node(r)
701 for pn in self.parents(n):
709 for pn in self.parents(n):
702 if pn == node:
710 if pn == node:
703 c.append(n)
711 c.append(n)
704 continue
712 continue
705 elif pn == nullid:
713 elif pn == nullid:
706 continue
714 continue
707 return c
715 return c
708
716
709 def lookup(self, id):
717 def lookup(self, id):
710 """locate a node based on revision number or subset of hex nodeid"""
718 """locate a node based on revision number or subset of hex nodeid"""
711 try:
719 try:
712 rev = int(id)
720 rev = int(id)
713 if str(rev) != id: raise ValueError
721 if str(rev) != id: raise ValueError
714 if rev < 0: rev = self.count() + rev
722 if rev < 0: rev = self.count() + rev
715 if rev < 0 or rev >= self.count(): raise ValueError
723 if rev < 0 or rev >= self.count(): raise ValueError
716 return self.node(rev)
724 return self.node(rev)
717 except (ValueError, OverflowError):
725 except (ValueError, OverflowError):
718 c = []
726 c = []
719 for n in self.nodemap:
727 for n in self.nodemap:
720 if hex(n).startswith(id):
728 if hex(n).startswith(id):
721 c.append(n)
729 c.append(n)
722 if len(c) > 1: raise RevlogError(_("Ambiguous identifier"))
730 if len(c) > 1: raise RevlogError(_("Ambiguous identifier"))
723 if len(c) < 1: raise RevlogError(_("No match found"))
731 if len(c) < 1: raise RevlogError(_("No match found"))
724 return c[0]
732 return c[0]
725
733
726 return None
734 return None
727
735
728 def diff(self, a, b):
736 def diff(self, a, b):
729 """return a delta between two revisions"""
737 """return a delta between two revisions"""
730 return mdiff.textdiff(a, b)
738 return mdiff.textdiff(a, b)
731
739
732 def patches(self, t, pl):
740 def patches(self, t, pl):
733 """apply a list of patches to a string"""
741 """apply a list of patches to a string"""
734 return mdiff.patches(t, pl)
742 return mdiff.patches(t, pl)
735
743
736 def chunk(self, rev, df=None, cachelen=4096):
744 def chunk(self, rev, df=None, cachelen=4096):
737 start, length = self.start(rev), self.length(rev)
745 start, length = self.start(rev), self.length(rev)
738 inline = self.inlinedata()
746 inline = self.inlinedata()
739 if inline:
747 if inline:
740 start += (rev + 1) * struct.calcsize(self.indexformat)
748 start += (rev + 1) * struct.calcsize(self.indexformat)
741 end = start + length
749 end = start + length
742 def loadcache(df):
750 def loadcache(df):
743 cache_length = max(cachelen, length) # 4k
751 cache_length = max(cachelen, length) # 4k
744 if not df:
752 if not df:
745 if inline:
753 if inline:
746 df = self.opener(self.indexfile)
754 df = self.opener(self.indexfile)
747 else:
755 else:
748 df = self.opener(self.datafile)
756 df = self.opener(self.datafile)
749 df.seek(start)
757 df.seek(start)
750 self.chunkcache = (start, df.read(cache_length))
758 self.chunkcache = (start, df.read(cache_length))
751
759
752 if not self.chunkcache:
760 if not self.chunkcache:
753 loadcache(df)
761 loadcache(df)
754
762
755 cache_start = self.chunkcache[0]
763 cache_start = self.chunkcache[0]
756 cache_end = cache_start + len(self.chunkcache[1])
764 cache_end = cache_start + len(self.chunkcache[1])
757 if start >= cache_start and end <= cache_end:
765 if start >= cache_start and end <= cache_end:
758 # it is cached
766 # it is cached
759 offset = start - cache_start
767 offset = start - cache_start
760 else:
768 else:
761 loadcache(df)
769 loadcache(df)
762 offset = 0
770 offset = 0
763
771
764 #def checkchunk():
772 #def checkchunk():
765 # df = self.opener(self.datafile)
773 # df = self.opener(self.datafile)
766 # df.seek(start)
774 # df.seek(start)
767 # return df.read(length)
775 # return df.read(length)
768 #assert s == checkchunk()
776 #assert s == checkchunk()
769 return decompress(self.chunkcache[1][offset:offset + length])
777 return decompress(self.chunkcache[1][offset:offset + length])
770
778
771 def delta(self, node):
779 def delta(self, node):
772 """return or calculate a delta between a node and its predecessor"""
780 """return or calculate a delta between a node and its predecessor"""
773 r = self.rev(node)
781 r = self.rev(node)
774 return self.revdiff(r - 1, r)
782 return self.revdiff(r - 1, r)
775
783
776 def revdiff(self, rev1, rev2):
784 def revdiff(self, rev1, rev2):
777 """return or calculate a delta between two revisions"""
785 """return or calculate a delta between two revisions"""
778 b1 = self.base(rev1)
786 b1 = self.base(rev1)
779 b2 = self.base(rev2)
787 b2 = self.base(rev2)
780 if b1 == b2 and rev1 + 1 == rev2:
788 if b1 == b2 and rev1 + 1 == rev2:
781 return self.chunk(rev2)
789 return self.chunk(rev2)
782 else:
790 else:
783 return self.diff(self.revision(self.node(rev1)),
791 return self.diff(self.revision(self.node(rev1)),
784 self.revision(self.node(rev2)))
792 self.revision(self.node(rev2)))
785
793
786 def revision(self, node):
794 def revision(self, node):
787 """return an uncompressed revision of a given"""
795 """return an uncompressed revision of a given"""
788 if node == nullid: return ""
796 if node == nullid: return ""
789 if self.cache and self.cache[0] == node: return self.cache[2]
797 if self.cache and self.cache[0] == node: return self.cache[2]
790
798
791 # look up what we need to read
799 # look up what we need to read
792 text = None
800 text = None
793 rev = self.rev(node)
801 rev = self.rev(node)
794 base = self.base(rev)
802 base = self.base(rev)
795
803
796 if self.inlinedata():
804 if self.inlinedata():
797 # we probably have the whole chunk cached
805 # we probably have the whole chunk cached
798 df = None
806 df = None
799 else:
807 else:
800 df = self.opener(self.datafile)
808 df = self.opener(self.datafile)
801
809
802 # do we have useful data cached?
810 # do we have useful data cached?
803 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
811 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
804 base = self.cache[1]
812 base = self.cache[1]
805 text = self.cache[2]
813 text = self.cache[2]
806 self.loadindex(base, rev + 1)
814 self.loadindex(base, rev + 1)
807 else:
815 else:
808 self.loadindex(base, rev + 1)
816 self.loadindex(base, rev + 1)
809 text = self.chunk(base, df=df)
817 text = self.chunk(base, df=df)
810
818
811 bins = []
819 bins = []
812 for r in xrange(base + 1, rev + 1):
820 for r in xrange(base + 1, rev + 1):
813 bins.append(self.chunk(r, df=df))
821 bins.append(self.chunk(r, df=df))
814
822
815 text = self.patches(text, bins)
823 text = self.patches(text, bins)
816
824
817 p1, p2 = self.parents(node)
825 p1, p2 = self.parents(node)
818 if node != hash(text, p1, p2):
826 if node != hash(text, p1, p2):
819 raise RevlogError(_("integrity check failed on %s:%d")
827 raise RevlogError(_("integrity check failed on %s:%d")
820 % (self.datafile, rev))
828 % (self.datafile, rev))
821
829
822 self.cache = (node, rev, text)
830 self.cache = (node, rev, text)
823 return text
831 return text
824
832
825 def checkinlinesize(self, tr, fp=None):
833 def checkinlinesize(self, tr, fp=None):
826 if not self.inlinedata():
834 if not self.inlinedata():
827 return
835 return
828 if not fp:
836 if not fp:
829 fp = self.opener(self.indexfile, 'r')
837 fp = self.opener(self.indexfile, 'r')
830 fp.seek(0, 2)
838 fp.seek(0, 2)
831 size = fp.tell()
839 size = fp.tell()
832 if size < 131072:
840 if size < 131072:
833 return
841 return
834 trinfo = tr.find(self.indexfile)
842 trinfo = tr.find(self.indexfile)
835 if trinfo == None:
843 if trinfo == None:
836 raise RevlogError(_("%s not found in the transaction" %
844 raise RevlogError(_("%s not found in the transaction" %
837 self.indexfile))
845 self.indexfile))
838
846
839 trindex = trinfo[2]
847 trindex = trinfo[2]
840 dataoff = self.start(trindex)
848 dataoff = self.start(trindex)
841
849
842 tr.add(self.datafile, dataoff)
850 tr.add(self.datafile, dataoff)
843 df = self.opener(self.datafile, 'w')
851 df = self.opener(self.datafile, 'w')
844 calc = struct.calcsize(self.indexformat)
852 calc = struct.calcsize(self.indexformat)
845 for r in xrange(self.count()):
853 for r in xrange(self.count()):
846 start = self.start(r) + (r + 1) * calc
854 start = self.start(r) + (r + 1) * calc
847 length = self.length(r)
855 length = self.length(r)
848 fp.seek(start)
856 fp.seek(start)
849 d = fp.read(length)
857 d = fp.read(length)
850 df.write(d)
858 df.write(d)
851 fp.close()
859 fp.close()
852 df.close()
860 df.close()
853 fp = self.opener(self.indexfile, 'w', atomictemp=True)
861 fp = self.opener(self.indexfile, 'w', atomictemp=True)
854 self.version &= ~(REVLOGNGINLINEDATA)
862 self.version &= ~(REVLOGNGINLINEDATA)
855 if self.count():
863 if self.count():
856 x = self.index[0]
864 x = self.index[0]
857 e = struct.pack(self.indexformat, *x)[4:]
865 e = struct.pack(self.indexformat, *x)[4:]
858 l = struct.pack(versionformat, self.version)
866 l = struct.pack(versionformat, self.version)
859 fp.write(l)
867 fp.write(l)
860 fp.write(e)
868 fp.write(e)
861
869
862 for i in xrange(1, self.count()):
870 for i in xrange(1, self.count()):
863 x = self.index[i]
871 x = self.index[i]
864 e = struct.pack(self.indexformat, *x)
872 e = struct.pack(self.indexformat, *x)
865 fp.write(e)
873 fp.write(e)
866
874
867 # if we don't call rename, the temp file will never replace the
875 # if we don't call rename, the temp file will never replace the
868 # real index
876 # real index
869 fp.rename()
877 fp.rename()
870
878
871 tr.replace(self.indexfile, trindex * calc)
879 tr.replace(self.indexfile, trindex * calc)
872 self.chunkcache = None
880 self.chunkcache = None
873
881
874 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
882 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
875 """add a revision to the log
883 """add a revision to the log
876
884
877 text - the revision data to add
885 text - the revision data to add
878 transaction - the transaction object used for rollback
886 transaction - the transaction object used for rollback
879 link - the linkrev data to add
887 link - the linkrev data to add
880 p1, p2 - the parent nodeids of the revision
888 p1, p2 - the parent nodeids of the revision
881 d - an optional precomputed delta
889 d - an optional precomputed delta
882 """
890 """
883 if text is None: text = ""
891 if text is None: text = ""
884 if p1 is None: p1 = self.tip()
892 if p1 is None: p1 = self.tip()
885 if p2 is None: p2 = nullid
893 if p2 is None: p2 = nullid
886
894
887 node = hash(text, p1, p2)
895 node = hash(text, p1, p2)
888
896
889 if node in self.nodemap:
897 if node in self.nodemap:
890 return node
898 return node
891
899
892 n = self.count()
900 n = self.count()
893 t = n - 1
901 t = n - 1
894
902
895 if n:
903 if n:
896 base = self.base(t)
904 base = self.base(t)
897 start = self.start(base)
905 start = self.start(base)
898 end = self.end(t)
906 end = self.end(t)
899 if not d:
907 if not d:
900 prev = self.revision(self.tip())
908 prev = self.revision(self.tip())
901 d = self.diff(prev, str(text))
909 d = self.diff(prev, str(text))
902 data = compress(d)
910 data = compress(d)
903 l = len(data[1]) + len(data[0])
911 l = len(data[1]) + len(data[0])
904 dist = end - start + l
912 dist = end - start + l
905
913
906 # full versions are inserted when the needed deltas
914 # full versions are inserted when the needed deltas
907 # become comparable to the uncompressed text
915 # become comparable to the uncompressed text
908 if not n or dist > len(text) * 2:
916 if not n or dist > len(text) * 2:
909 data = compress(text)
917 data = compress(text)
910 l = len(data[1]) + len(data[0])
918 l = len(data[1]) + len(data[0])
911 base = n
919 base = n
912 else:
920 else:
913 base = self.base(t)
921 base = self.base(t)
914
922
915 offset = 0
923 offset = 0
916 if t >= 0:
924 if t >= 0:
917 offset = self.end(t)
925 offset = self.end(t)
918
926
919 if self.version == REVLOGV0:
927 if self.version == REVLOGV0:
920 e = (offset, l, base, link, p1, p2, node)
928 e = (offset, l, base, link, p1, p2, node)
921 else:
929 else:
922 e = (self.offset_type(offset, 0), l, len(text),
930 e = (self.offset_type(offset, 0), l, len(text),
923 base, link, self.rev(p1), self.rev(p2), node)
931 base, link, self.rev(p1), self.rev(p2), node)
924
932
925 self.index.append(e)
933 self.index.append(e)
926 self.nodemap[node] = n
934 self.nodemap[node] = n
927 entry = struct.pack(self.indexformat, *e)
935 entry = struct.pack(self.indexformat, *e)
928
936
929 if not self.inlinedata():
937 if not self.inlinedata():
930 transaction.add(self.datafile, offset)
938 transaction.add(self.datafile, offset)
931 transaction.add(self.indexfile, n * len(entry))
939 transaction.add(self.indexfile, n * len(entry))
932 f = self.opener(self.datafile, "a")
940 f = self.opener(self.datafile, "a")
933 if data[0]:
941 if data[0]:
934 f.write(data[0])
942 f.write(data[0])
935 f.write(data[1])
943 f.write(data[1])
936 f.close()
944 f.close()
937 f = self.opener(self.indexfile, "a")
945 f = self.opener(self.indexfile, "a")
938 else:
946 else:
939 f = self.opener(self.indexfile, "a+")
947 f = self.opener(self.indexfile, "a+")
940 f.seek(0, 2)
948 f.seek(0, 2)
941 transaction.add(self.indexfile, f.tell(), self.count() - 1)
949 transaction.add(self.indexfile, f.tell(), self.count() - 1)
942
950
943 if len(self.index) == 1 and self.version != REVLOGV0:
951 if len(self.index) == 1 and self.version != REVLOGV0:
944 l = struct.pack(versionformat, self.version)
952 l = struct.pack(versionformat, self.version)
945 f.write(l)
953 f.write(l)
946 entry = entry[4:]
954 entry = entry[4:]
947
955
948 f.write(entry)
956 f.write(entry)
949
957
950 if self.inlinedata():
958 if self.inlinedata():
951 f.write(data[0])
959 f.write(data[0])
952 f.write(data[1])
960 f.write(data[1])
953 self.checkinlinesize(transaction, f)
961 self.checkinlinesize(transaction, f)
954
962
955 self.cache = (node, n, text)
963 self.cache = (node, n, text)
956 return node
964 return node
957
965
958 def ancestor(self, a, b):
966 def ancestor(self, a, b):
959 """calculate the least common ancestor of nodes a and b"""
967 """calculate the least common ancestor of nodes a and b"""
960
968
961 # start with some short cuts for the linear cases
969 # start with some short cuts for the linear cases
962 if a == b:
970 if a == b:
963 return a
971 return a
964 ra = self.rev(a)
972 ra = self.rev(a)
965 rb = self.rev(b)
973 rb = self.rev(b)
966 if ra < rb:
974 if ra < rb:
967 last = b
975 last = b
968 first = a
976 first = a
969 else:
977 else:
970 last = a
978 last = a
971 first = b
979 first = b
972
980
973 # reachable won't include stop in the list, so we have to use a parent
981 # reachable won't include stop in the list, so we have to use a parent
974 reachable = self.reachable(last, stop=self.parents(first)[0])
982 reachable = self.reachable(last, stop=self.parents(first)[0])
975 if first in reachable:
983 if first in reachable:
976 return first
984 return first
977
985
978 # calculate the distance of every node from root
986 # calculate the distance of every node from root
979 dist = {nullid: 0}
987 dist = {nullid: 0}
980 for i in xrange(self.count()):
988 for i in xrange(self.count()):
981 n = self.node(i)
989 n = self.node(i)
982 p1, p2 = self.parents(n)
990 p1, p2 = self.parents(n)
983 dist[n] = max(dist[p1], dist[p2]) + 1
991 dist[n] = max(dist[p1], dist[p2]) + 1
984
992
985 # traverse ancestors in order of decreasing distance from root
993 # traverse ancestors in order of decreasing distance from root
986 def ancestors(node):
994 def ancestors(node):
987 # we store negative distances because heap returns smallest member
995 # we store negative distances because heap returns smallest member
988 h = [(-dist[node], node)]
996 h = [(-dist[node], node)]
989 seen = {}
997 seen = {}
990 while h:
998 while h:
991 d, n = heapq.heappop(h)
999 d, n = heapq.heappop(h)
992 if n not in seen:
1000 if n not in seen:
993 seen[n] = 1
1001 seen[n] = 1
994 yield (-d, n)
1002 yield (-d, n)
995 for p in self.parents(n):
1003 for p in self.parents(n):
996 heapq.heappush(h, (-dist[p], p))
1004 heapq.heappush(h, (-dist[p], p))
997
1005
998 def generations(node):
1006 def generations(node):
999 sg, s = None, {}
1007 sg, s = None, {}
1000 for g,n in ancestors(node):
1008 for g,n in ancestors(node):
1001 if g != sg:
1009 if g != sg:
1002 if sg:
1010 if sg:
1003 yield sg, s
1011 yield sg, s
1004 sg, s = g, {n:1}
1012 sg, s = g, {n:1}
1005 else:
1013 else:
1006 s[n] = 1
1014 s[n] = 1
1007 yield sg, s
1015 yield sg, s
1008
1016
1009 x = generations(a)
1017 x = generations(a)
1010 y = generations(b)
1018 y = generations(b)
1011 gx = x.next()
1019 gx = x.next()
1012 gy = y.next()
1020 gy = y.next()
1013
1021
1014 # increment each ancestor list until it is closer to root than
1022 # increment each ancestor list until it is closer to root than
1015 # the other, or they match
1023 # the other, or they match
1016 while 1:
1024 while 1:
1017 #print "ancestor gen %s %s" % (gx[0], gy[0])
1025 #print "ancestor gen %s %s" % (gx[0], gy[0])
1018 if gx[0] == gy[0]:
1026 if gx[0] == gy[0]:
1019 # find the intersection
1027 # find the intersection
1020 i = [ n for n in gx[1] if n in gy[1] ]
1028 i = [ n for n in gx[1] if n in gy[1] ]
1021 if i:
1029 if i:
1022 return i[0]
1030 return i[0]
1023 else:
1031 else:
1024 #print "next"
1032 #print "next"
1025 gy = y.next()
1033 gy = y.next()
1026 gx = x.next()
1034 gx = x.next()
1027 elif gx[0] < gy[0]:
1035 elif gx[0] < gy[0]:
1028 #print "next y"
1036 #print "next y"
1029 gy = y.next()
1037 gy = y.next()
1030 else:
1038 else:
1031 #print "next x"
1039 #print "next x"
1032 gx = x.next()
1040 gx = x.next()
1033
1041
1034 def group(self, nodelist, lookup, infocollect=None):
1042 def group(self, nodelist, lookup, infocollect=None):
1035 """calculate a delta group
1043 """calculate a delta group
1036
1044
1037 Given a list of changeset revs, return a set of deltas and
1045 Given a list of changeset revs, return a set of deltas and
1038 metadata corresponding to nodes. the first delta is
1046 metadata corresponding to nodes. the first delta is
1039 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1047 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1040 have this parent as it has all history before these
1048 have this parent as it has all history before these
1041 changesets. parent is parent[0]
1049 changesets. parent is parent[0]
1042 """
1050 """
1043 revs = [self.rev(n) for n in nodelist]
1051 revs = [self.rev(n) for n in nodelist]
1044
1052
1045 # if we don't have any revisions touched by these changesets, bail
1053 # if we don't have any revisions touched by these changesets, bail
1046 if not revs:
1054 if not revs:
1047 yield changegroup.closechunk()
1055 yield changegroup.closechunk()
1048 return
1056 return
1049
1057
1050 # add the parent of the first rev
1058 # add the parent of the first rev
1051 p = self.parents(self.node(revs[0]))[0]
1059 p = self.parents(self.node(revs[0]))[0]
1052 revs.insert(0, self.rev(p))
1060 revs.insert(0, self.rev(p))
1053
1061
1054 # build deltas
1062 # build deltas
1055 for d in xrange(0, len(revs) - 1):
1063 for d in xrange(0, len(revs) - 1):
1056 a, b = revs[d], revs[d + 1]
1064 a, b = revs[d], revs[d + 1]
1057 nb = self.node(b)
1065 nb = self.node(b)
1058
1066
1059 if infocollect is not None:
1067 if infocollect is not None:
1060 infocollect(nb)
1068 infocollect(nb)
1061
1069
1062 d = self.revdiff(a, b)
1070 d = self.revdiff(a, b)
1063 p = self.parents(nb)
1071 p = self.parents(nb)
1064 meta = nb + p[0] + p[1] + lookup(nb)
1072 meta = nb + p[0] + p[1] + lookup(nb)
1065 yield changegroup.genchunk("%s%s" % (meta, d))
1073 yield changegroup.genchunk("%s%s" % (meta, d))
1066
1074
1067 yield changegroup.closechunk()
1075 yield changegroup.closechunk()
1068
1076
1069 def addgroup(self, revs, linkmapper, transaction, unique=0):
1077 def addgroup(self, revs, linkmapper, transaction, unique=0):
1070 """
1078 """
1071 add a delta group
1079 add a delta group
1072
1080
1073 given a set of deltas, add them to the revision log. the
1081 given a set of deltas, add them to the revision log. the
1074 first delta is against its parent, which should be in our
1082 first delta is against its parent, which should be in our
1075 log, the rest are against the previous delta.
1083 log, the rest are against the previous delta.
1076 """
1084 """
1077
1085
1078 #track the base of the current delta log
1086 #track the base of the current delta log
1079 r = self.count()
1087 r = self.count()
1080 t = r - 1
1088 t = r - 1
1081 node = None
1089 node = None
1082
1090
1083 base = prev = -1
1091 base = prev = -1
1084 start = end = textlen = 0
1092 start = end = textlen = 0
1085 if r:
1093 if r:
1086 end = self.end(t)
1094 end = self.end(t)
1087
1095
1088 ifh = self.opener(self.indexfile, "a+")
1096 ifh = self.opener(self.indexfile, "a+")
1089 ifh.seek(0, 2)
1097 ifh.seek(0, 2)
1090 transaction.add(self.indexfile, ifh.tell(), self.count())
1098 transaction.add(self.indexfile, ifh.tell(), self.count())
1091 if self.inlinedata():
1099 if self.inlinedata():
1092 dfh = None
1100 dfh = None
1093 else:
1101 else:
1094 transaction.add(self.datafile, end)
1102 transaction.add(self.datafile, end)
1095 dfh = self.opener(self.datafile, "a")
1103 dfh = self.opener(self.datafile, "a")
1096
1104
1097 # loop through our set of deltas
1105 # loop through our set of deltas
1098 chain = None
1106 chain = None
1099 for chunk in revs:
1107 for chunk in revs:
1100 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1108 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1101 link = linkmapper(cs)
1109 link = linkmapper(cs)
1102 if node in self.nodemap:
1110 if node in self.nodemap:
1103 # this can happen if two branches make the same change
1111 # this can happen if two branches make the same change
1104 # if unique:
1112 # if unique:
1105 # raise RevlogError(_("already have %s") % hex(node[:4]))
1113 # raise RevlogError(_("already have %s") % hex(node[:4]))
1106 chain = node
1114 chain = node
1107 continue
1115 continue
1108 delta = chunk[80:]
1116 delta = chunk[80:]
1109
1117
1110 for p in (p1, p2):
1118 for p in (p1, p2):
1111 if not p in self.nodemap:
1119 if not p in self.nodemap:
1112 raise RevlogError(_("unknown parent %s") % short(p1))
1120 raise RevlogError(_("unknown parent %s") % short(p1))
1113
1121
1114 if not chain:
1122 if not chain:
1115 # retrieve the parent revision of the delta chain
1123 # retrieve the parent revision of the delta chain
1116 chain = p1
1124 chain = p1
1117 if not chain in self.nodemap:
1125 if not chain in self.nodemap:
1118 raise RevlogError(_("unknown base %s") % short(chain[:4]))
1126 raise RevlogError(_("unknown base %s") % short(chain[:4]))
1119
1127
1120 # full versions are inserted when the needed deltas become
1128 # full versions are inserted when the needed deltas become
1121 # comparable to the uncompressed text or when the previous
1129 # comparable to the uncompressed text or when the previous
1122 # version is not the one we have a delta against. We use
1130 # version is not the one we have a delta against. We use
1123 # the size of the previous full rev as a proxy for the
1131 # the size of the previous full rev as a proxy for the
1124 # current size.
1132 # current size.
1125
1133
1126 if chain == prev:
1134 if chain == prev:
1127 tempd = compress(delta)
1135 tempd = compress(delta)
1128 cdelta = tempd[0] + tempd[1]
1136 cdelta = tempd[0] + tempd[1]
1129 textlen = mdiff.patchedsize(textlen, delta)
1137 textlen = mdiff.patchedsize(textlen, delta)
1130
1138
1131 if chain != prev or (end - start + len(cdelta)) > textlen * 2:
1139 if chain != prev or (end - start + len(cdelta)) > textlen * 2:
1132 # flush our writes here so we can read it in revision
1140 # flush our writes here so we can read it in revision
1133 if dfh:
1141 if dfh:
1134 dfh.flush()
1142 dfh.flush()
1135 ifh.flush()
1143 ifh.flush()
1136 text = self.revision(chain)
1144 text = self.revision(chain)
1137 text = self.patches(text, [delta])
1145 text = self.patches(text, [delta])
1138 chk = self.addrevision(text, transaction, link, p1, p2)
1146 chk = self.addrevision(text, transaction, link, p1, p2)
1139 if chk != node:
1147 if chk != node:
1140 raise RevlogError(_("consistency error adding group"))
1148 raise RevlogError(_("consistency error adding group"))
1141 textlen = len(text)
1149 textlen = len(text)
1142 else:
1150 else:
1143 if self.version == REVLOGV0:
1151 if self.version == REVLOGV0:
1144 e = (end, len(cdelta), base, link, p1, p2, node)
1152 e = (end, len(cdelta), base, link, p1, p2, node)
1145 else:
1153 else:
1146 e = (self.offset_type(end, 0), len(cdelta), textlen, base,
1154 e = (self.offset_type(end, 0), len(cdelta), textlen, base,
1147 link, self.rev(p1), self.rev(p2), node)
1155 link, self.rev(p1), self.rev(p2), node)
1148 self.index.append(e)
1156 self.index.append(e)
1149 self.nodemap[node] = r
1157 self.nodemap[node] = r
1150 if self.inlinedata():
1158 if self.inlinedata():
1151 ifh.write(struct.pack(self.indexformat, *e))
1159 ifh.write(struct.pack(self.indexformat, *e))
1152 ifh.write(cdelta)
1160 ifh.write(cdelta)
1153 self.checkinlinesize(transaction, ifh)
1161 self.checkinlinesize(transaction, ifh)
1154 if not self.inlinedata():
1162 if not self.inlinedata():
1155 dfh = self.opener(self.datafile, "a")
1163 dfh = self.opener(self.datafile, "a")
1156 ifh = self.opener(self.indexfile, "a")
1164 ifh = self.opener(self.indexfile, "a")
1157 else:
1165 else:
1158 if not dfh:
1166 if not dfh:
1159 # addrevision switched from inline to conventional
1167 # addrevision switched from inline to conventional
1160 # reopen the index
1168 # reopen the index
1161 dfh = self.opener(self.datafile, "a")
1169 dfh = self.opener(self.datafile, "a")
1162 ifh = self.opener(self.indexfile, "a")
1170 ifh = self.opener(self.indexfile, "a")
1163 dfh.write(cdelta)
1171 dfh.write(cdelta)
1164 ifh.write(struct.pack(self.indexformat, *e))
1172 ifh.write(struct.pack(self.indexformat, *e))
1165
1173
1166 t, r, chain, prev = r, r + 1, node, node
1174 t, r, chain, prev = r, r + 1, node, node
1167 base = self.base(t)
1175 base = self.base(t)
1168 start = self.start(base)
1176 start = self.start(base)
1169 end = self.end(t)
1177 end = self.end(t)
1170
1178
1171 if node is None:
1179 if node is None:
1172 raise RevlogError(_("group to be added is empty"))
1180 raise RevlogError(_("group to be added is empty"))
1173 return node
1181 return node
1174
1182
1175 def strip(self, rev, minlink):
1183 def strip(self, rev, minlink):
1176 if self.count() == 0 or rev >= self.count():
1184 if self.count() == 0 or rev >= self.count():
1177 return
1185 return
1178
1186
1179 if isinstance(self.index, lazyindex):
1187 if isinstance(self.index, lazyindex):
1180 self.loadindexmap()
1188 self.loadindexmap()
1181
1189
1182 # When stripping away a revision, we need to make sure it
1190 # When stripping away a revision, we need to make sure it
1183 # does not actually belong to an older changeset.
1191 # does not actually belong to an older changeset.
1184 # The minlink parameter defines the oldest revision
1192 # The minlink parameter defines the oldest revision
1185 # we're allowed to strip away.
1193 # we're allowed to strip away.
1186 while minlink > self.index[rev][-4]:
1194 while minlink > self.index[rev][-4]:
1187 rev += 1
1195 rev += 1
1188 if rev >= self.count():
1196 if rev >= self.count():
1189 return
1197 return
1190
1198
1191 # first truncate the files on disk
1199 # first truncate the files on disk
1192 end = self.start(rev)
1200 end = self.start(rev)
1193 if not self.inlinedata():
1201 if not self.inlinedata():
1194 df = self.opener(self.datafile, "a")
1202 df = self.opener(self.datafile, "a")
1195 df.truncate(end)
1203 df.truncate(end)
1196 end = rev * struct.calcsize(self.indexformat)
1204 end = rev * struct.calcsize(self.indexformat)
1197 else:
1205 else:
1198 end += rev * struct.calcsize(self.indexformat)
1206 end += rev * struct.calcsize(self.indexformat)
1199
1207
1200 indexf = self.opener(self.indexfile, "a")
1208 indexf = self.opener(self.indexfile, "a")
1201 indexf.truncate(end)
1209 indexf.truncate(end)
1202
1210
1203 # then reset internal state in memory to forget those revisions
1211 # then reset internal state in memory to forget those revisions
1204 self.cache = None
1212 self.cache = None
1205 self.chunkcache = None
1213 self.chunkcache = None
1206 for x in xrange(rev, self.count()):
1214 for x in xrange(rev, self.count()):
1207 del self.nodemap[self.node(x)]
1215 del self.nodemap[self.node(x)]
1208
1216
1209 del self.index[rev:]
1217 del self.index[rev:]
1210
1218
1211 def checksize(self):
1219 def checksize(self):
1212 expected = 0
1220 expected = 0
1213 if self.count():
1221 if self.count():
1214 expected = self.end(self.count() - 1)
1222 expected = self.end(self.count() - 1)
1215
1223
1216 try:
1224 try:
1217 f = self.opener(self.datafile)
1225 f = self.opener(self.datafile)
1218 f.seek(0, 2)
1226 f.seek(0, 2)
1219 actual = f.tell()
1227 actual = f.tell()
1220 dd = actual - expected
1228 dd = actual - expected
1221 except IOError, inst:
1229 except IOError, inst:
1222 if inst.errno != errno.ENOENT:
1230 if inst.errno != errno.ENOENT:
1223 raise
1231 raise
1224 dd = 0
1232 dd = 0
1225
1233
1226 try:
1234 try:
1227 f = self.opener(self.indexfile)
1235 f = self.opener(self.indexfile)
1228 f.seek(0, 2)
1236 f.seek(0, 2)
1229 actual = f.tell()
1237 actual = f.tell()
1230 s = struct.calcsize(self.indexformat)
1238 s = struct.calcsize(self.indexformat)
1231 i = actual / s
1239 i = actual / s
1232 di = actual - (i * s)
1240 di = actual - (i * s)
1233 if self.inlinedata():
1241 if self.inlinedata():
1234 databytes = 0
1242 databytes = 0
1235 for r in xrange(self.count()):
1243 for r in xrange(self.count()):
1236 databytes += self.length(r)
1244 databytes += self.length(r)
1237 dd = 0
1245 dd = 0
1238 di = actual - self.count() * s - databytes
1246 di = actual - self.count() * s - databytes
1239 except IOError, inst:
1247 except IOError, inst:
1240 if inst.errno != errno.ENOENT:
1248 if inst.errno != errno.ENOENT:
1241 raise
1249 raise
1242 di = 0
1250 di = 0
1243
1251
1244 return (dd, di)
1252 return (dd, di)
1245
1253
1246
1254
@@ -1,880 +1,889 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5
5
6 This software may be used and distributed according to the terms
6 This software may be used and distributed according to the terms
7 of the GNU General Public License, incorporated herein by reference.
7 of the GNU General Public License, incorporated herein by reference.
8
8
9 This contains helper routines that are independent of the SCM core and hide
9 This contains helper routines that are independent of the SCM core and hide
10 platform-specific details from the core.
10 platform-specific details from the core.
11 """
11 """
12
12
13 import os, errno
13 import os, errno
14 from i18n import gettext as _
14 from i18n import gettext as _
15 from demandload import *
15 from demandload import *
16 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
16 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
17 demandload(globals(), "threading time")
17 demandload(globals(), "threading time")
18
18
19 class SignalInterrupt(Exception):
19 class SignalInterrupt(Exception):
20 """Exception raised on SIGTERM and SIGHUP."""
20 """Exception raised on SIGTERM and SIGHUP."""
21
21
22 def pipefilter(s, cmd):
22 def pipefilter(s, cmd):
23 '''filter string S through command CMD, returning its output'''
23 '''filter string S through command CMD, returning its output'''
24 (pout, pin) = popen2.popen2(cmd, -1, 'b')
24 (pout, pin) = popen2.popen2(cmd, -1, 'b')
25 def writer():
25 def writer():
26 try:
26 try:
27 pin.write(s)
27 pin.write(s)
28 pin.close()
28 pin.close()
29 except IOError, inst:
29 except IOError, inst:
30 if inst.errno != errno.EPIPE:
30 if inst.errno != errno.EPIPE:
31 raise
31 raise
32
32
33 # we should use select instead on UNIX, but this will work on most
33 # we should use select instead on UNIX, but this will work on most
34 # systems, including Windows
34 # systems, including Windows
35 w = threading.Thread(target=writer)
35 w = threading.Thread(target=writer)
36 w.start()
36 w.start()
37 f = pout.read()
37 f = pout.read()
38 pout.close()
38 pout.close()
39 w.join()
39 w.join()
40 return f
40 return f
41
41
42 def tempfilter(s, cmd):
42 def tempfilter(s, cmd):
43 '''filter string S through a pair of temporary files with CMD.
43 '''filter string S through a pair of temporary files with CMD.
44 CMD is used as a template to create the real command to be run,
44 CMD is used as a template to create the real command to be run,
45 with the strings INFILE and OUTFILE replaced by the real names of
45 with the strings INFILE and OUTFILE replaced by the real names of
46 the temporary files generated.'''
46 the temporary files generated.'''
47 inname, outname = None, None
47 inname, outname = None, None
48 try:
48 try:
49 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
49 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
50 fp = os.fdopen(infd, 'wb')
50 fp = os.fdopen(infd, 'wb')
51 fp.write(s)
51 fp.write(s)
52 fp.close()
52 fp.close()
53 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
53 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
54 os.close(outfd)
54 os.close(outfd)
55 cmd = cmd.replace('INFILE', inname)
55 cmd = cmd.replace('INFILE', inname)
56 cmd = cmd.replace('OUTFILE', outname)
56 cmd = cmd.replace('OUTFILE', outname)
57 code = os.system(cmd)
57 code = os.system(cmd)
58 if code: raise Abort(_("command '%s' failed: %s") %
58 if code: raise Abort(_("command '%s' failed: %s") %
59 (cmd, explain_exit(code)))
59 (cmd, explain_exit(code)))
60 return open(outname, 'rb').read()
60 return open(outname, 'rb').read()
61 finally:
61 finally:
62 try:
62 try:
63 if inname: os.unlink(inname)
63 if inname: os.unlink(inname)
64 except: pass
64 except: pass
65 try:
65 try:
66 if outname: os.unlink(outname)
66 if outname: os.unlink(outname)
67 except: pass
67 except: pass
68
68
69 filtertable = {
69 filtertable = {
70 'tempfile:': tempfilter,
70 'tempfile:': tempfilter,
71 'pipe:': pipefilter,
71 'pipe:': pipefilter,
72 }
72 }
73
73
74 def filter(s, cmd):
74 def filter(s, cmd):
75 "filter a string through a command that transforms its input to its output"
75 "filter a string through a command that transforms its input to its output"
76 for name, fn in filtertable.iteritems():
76 for name, fn in filtertable.iteritems():
77 if cmd.startswith(name):
77 if cmd.startswith(name):
78 return fn(s, cmd[len(name):].lstrip())
78 return fn(s, cmd[len(name):].lstrip())
79 return pipefilter(s, cmd)
79 return pipefilter(s, cmd)
80
80
81 def find_in_path(name, path, default=None):
81 def find_in_path(name, path, default=None):
82 '''find name in search path. path can be string (will be split
82 '''find name in search path. path can be string (will be split
83 with os.pathsep), or iterable thing that returns strings. if name
83 with os.pathsep), or iterable thing that returns strings. if name
84 found, return path to name. else return default.'''
84 found, return path to name. else return default.'''
85 if isinstance(path, str):
85 if isinstance(path, str):
86 path = path.split(os.pathsep)
86 path = path.split(os.pathsep)
87 for p in path:
87 for p in path:
88 p_name = os.path.join(p, name)
88 p_name = os.path.join(p, name)
89 if os.path.exists(p_name):
89 if os.path.exists(p_name):
90 return p_name
90 return p_name
91 return default
91 return default
92
92
93 def patch(strip, patchname, ui):
93 def patch(strip, patchname, ui):
94 """apply the patch <patchname> to the working directory.
94 """apply the patch <patchname> to the working directory.
95 a list of patched files is returned"""
95 a list of patched files is returned"""
96 patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
96 patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
97 fp = os.popen('"%s" -p%d < "%s"' % (patcher, strip, patchname))
97 fp = os.popen('"%s" -p%d < "%s"' % (patcher, strip, patchname))
98 files = {}
98 files = {}
99 for line in fp:
99 for line in fp:
100 line = line.rstrip()
100 line = line.rstrip()
101 ui.status("%s\n" % line)
101 ui.status("%s\n" % line)
102 if line.startswith('patching file '):
102 if line.startswith('patching file '):
103 pf = parse_patch_output(line)
103 pf = parse_patch_output(line)
104 files.setdefault(pf, 1)
104 files.setdefault(pf, 1)
105 code = fp.close()
105 code = fp.close()
106 if code:
106 if code:
107 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
107 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
108 return files.keys()
108 return files.keys()
109
109
110 def binary(s):
110 def binary(s):
111 """return true if a string is binary data using diff's heuristic"""
111 """return true if a string is binary data using diff's heuristic"""
112 if s and '\0' in s[:4096]:
112 if s and '\0' in s[:4096]:
113 return True
113 return True
114 return False
114 return False
115
115
116 def unique(g):
116 def unique(g):
117 """return the uniq elements of iterable g"""
117 """return the uniq elements of iterable g"""
118 seen = {}
118 seen = {}
119 for f in g:
119 for f in g:
120 if f not in seen:
120 if f not in seen:
121 seen[f] = 1
121 seen[f] = 1
122 yield f
122 yield f
123
123
124 class Abort(Exception):
124 class Abort(Exception):
125 """Raised if a command needs to print an error and exit."""
125 """Raised if a command needs to print an error and exit."""
126
126
127 def always(fn): return True
127 def always(fn): return True
128 def never(fn): return False
128 def never(fn): return False
129
129
130 def patkind(name, dflt_pat='glob'):
130 def patkind(name, dflt_pat='glob'):
131 """Split a string into an optional pattern kind prefix and the
131 """Split a string into an optional pattern kind prefix and the
132 actual pattern."""
132 actual pattern."""
133 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
133 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
134 if name.startswith(prefix + ':'): return name.split(':', 1)
134 if name.startswith(prefix + ':'): return name.split(':', 1)
135 return dflt_pat, name
135 return dflt_pat, name
136
136
137 def globre(pat, head='^', tail='$'):
137 def globre(pat, head='^', tail='$'):
138 "convert a glob pattern into a regexp"
138 "convert a glob pattern into a regexp"
139 i, n = 0, len(pat)
139 i, n = 0, len(pat)
140 res = ''
140 res = ''
141 group = False
141 group = False
142 def peek(): return i < n and pat[i]
142 def peek(): return i < n and pat[i]
143 while i < n:
143 while i < n:
144 c = pat[i]
144 c = pat[i]
145 i = i+1
145 i = i+1
146 if c == '*':
146 if c == '*':
147 if peek() == '*':
147 if peek() == '*':
148 i += 1
148 i += 1
149 res += '.*'
149 res += '.*'
150 else:
150 else:
151 res += '[^/]*'
151 res += '[^/]*'
152 elif c == '?':
152 elif c == '?':
153 res += '.'
153 res += '.'
154 elif c == '[':
154 elif c == '[':
155 j = i
155 j = i
156 if j < n and pat[j] in '!]':
156 if j < n and pat[j] in '!]':
157 j += 1
157 j += 1
158 while j < n and pat[j] != ']':
158 while j < n and pat[j] != ']':
159 j += 1
159 j += 1
160 if j >= n:
160 if j >= n:
161 res += '\\['
161 res += '\\['
162 else:
162 else:
163 stuff = pat[i:j].replace('\\','\\\\')
163 stuff = pat[i:j].replace('\\','\\\\')
164 i = j + 1
164 i = j + 1
165 if stuff[0] == '!':
165 if stuff[0] == '!':
166 stuff = '^' + stuff[1:]
166 stuff = '^' + stuff[1:]
167 elif stuff[0] == '^':
167 elif stuff[0] == '^':
168 stuff = '\\' + stuff
168 stuff = '\\' + stuff
169 res = '%s[%s]' % (res, stuff)
169 res = '%s[%s]' % (res, stuff)
170 elif c == '{':
170 elif c == '{':
171 group = True
171 group = True
172 res += '(?:'
172 res += '(?:'
173 elif c == '}' and group:
173 elif c == '}' and group:
174 res += ')'
174 res += ')'
175 group = False
175 group = False
176 elif c == ',' and group:
176 elif c == ',' and group:
177 res += '|'
177 res += '|'
178 elif c == '\\':
178 elif c == '\\':
179 p = peek()
179 p = peek()
180 if p:
180 if p:
181 i += 1
181 i += 1
182 res += re.escape(p)
182 res += re.escape(p)
183 else:
183 else:
184 res += re.escape(c)
184 res += re.escape(c)
185 else:
185 else:
186 res += re.escape(c)
186 res += re.escape(c)
187 return head + res + tail
187 return head + res + tail
188
188
189 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
189 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
190
190
191 def pathto(n1, n2):
191 def pathto(n1, n2):
192 '''return the relative path from one place to another.
192 '''return the relative path from one place to another.
193 this returns a path in the form used by the local filesystem, not hg.'''
193 this returns a path in the form used by the local filesystem, not hg.'''
194 if not n1: return localpath(n2)
194 if not n1: return localpath(n2)
195 a, b = n1.split('/'), n2.split('/')
195 a, b = n1.split('/'), n2.split('/')
196 a.reverse()
196 a.reverse()
197 b.reverse()
197 b.reverse()
198 while a and b and a[-1] == b[-1]:
198 while a and b and a[-1] == b[-1]:
199 a.pop()
199 a.pop()
200 b.pop()
200 b.pop()
201 b.reverse()
201 b.reverse()
202 return os.sep.join((['..'] * len(a)) + b)
202 return os.sep.join((['..'] * len(a)) + b)
203
203
204 def canonpath(root, cwd, myname):
204 def canonpath(root, cwd, myname):
205 """return the canonical path of myname, given cwd and root"""
205 """return the canonical path of myname, given cwd and root"""
206 if root == os.sep:
206 if root == os.sep:
207 rootsep = os.sep
207 rootsep = os.sep
208 else:
208 else:
209 rootsep = root + os.sep
209 rootsep = root + os.sep
210 name = myname
210 name = myname
211 if not os.path.isabs(name):
211 if not os.path.isabs(name):
212 name = os.path.join(root, cwd, name)
212 name = os.path.join(root, cwd, name)
213 name = os.path.normpath(name)
213 name = os.path.normpath(name)
214 if name.startswith(rootsep):
214 if name.startswith(rootsep):
215 name = name[len(rootsep):]
215 name = name[len(rootsep):]
216 audit_path(name)
216 audit_path(name)
217 return pconvert(name)
217 return pconvert(name)
218 elif name == root:
218 elif name == root:
219 return ''
219 return ''
220 else:
220 else:
221 # Determine whether `name' is in the hierarchy at or beneath `root',
221 # Determine whether `name' is in the hierarchy at or beneath `root',
222 # by iterating name=dirname(name) until that causes no change (can't
222 # by iterating name=dirname(name) until that causes no change (can't
223 # check name == '/', because that doesn't work on windows). For each
223 # check name == '/', because that doesn't work on windows). For each
224 # `name', compare dev/inode numbers. If they match, the list `rel'
224 # `name', compare dev/inode numbers. If they match, the list `rel'
225 # holds the reversed list of components making up the relative file
225 # holds the reversed list of components making up the relative file
226 # name we want.
226 # name we want.
227 root_st = os.stat(root)
227 root_st = os.stat(root)
228 rel = []
228 rel = []
229 while True:
229 while True:
230 try:
230 try:
231 name_st = os.stat(name)
231 name_st = os.stat(name)
232 except OSError:
232 except OSError:
233 break
233 break
234 if samestat(name_st, root_st):
234 if samestat(name_st, root_st):
235 rel.reverse()
235 rel.reverse()
236 name = os.path.join(*rel)
236 name = os.path.join(*rel)
237 audit_path(name)
237 audit_path(name)
238 return pconvert(name)
238 return pconvert(name)
239 dirname, basename = os.path.split(name)
239 dirname, basename = os.path.split(name)
240 rel.append(basename)
240 rel.append(basename)
241 if dirname == name:
241 if dirname == name:
242 break
242 break
243 name = dirname
243 name = dirname
244
244
245 raise Abort('%s not under root' % myname)
245 raise Abort('%s not under root' % myname)
246
246
247 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
247 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
248 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
248 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
249
249
250 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
250 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
251 if os.name == 'nt':
251 if os.name == 'nt':
252 dflt_pat = 'glob'
252 dflt_pat = 'glob'
253 else:
253 else:
254 dflt_pat = 'relpath'
254 dflt_pat = 'relpath'
255 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
255 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
256
256
257 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
257 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
258 """build a function to match a set of file patterns
258 """build a function to match a set of file patterns
259
259
260 arguments:
260 arguments:
261 canonroot - the canonical root of the tree you're matching against
261 canonroot - the canonical root of the tree you're matching against
262 cwd - the current working directory, if relevant
262 cwd - the current working directory, if relevant
263 names - patterns to find
263 names - patterns to find
264 inc - patterns to include
264 inc - patterns to include
265 exc - patterns to exclude
265 exc - patterns to exclude
266 head - a regex to prepend to patterns to control whether a match is rooted
266 head - a regex to prepend to patterns to control whether a match is rooted
267
267
268 a pattern is one of:
268 a pattern is one of:
269 'glob:<rooted glob>'
269 'glob:<rooted glob>'
270 're:<rooted regexp>'
270 're:<rooted regexp>'
271 'path:<rooted path>'
271 'path:<rooted path>'
272 'relglob:<relative glob>'
272 'relglob:<relative glob>'
273 'relpath:<relative path>'
273 'relpath:<relative path>'
274 'relre:<relative regexp>'
274 'relre:<relative regexp>'
275 '<rooted path or regexp>'
275 '<rooted path or regexp>'
276
276
277 returns:
277 returns:
278 a 3-tuple containing
278 a 3-tuple containing
279 - list of explicit non-pattern names passed in
279 - list of explicit non-pattern names passed in
280 - a bool match(filename) function
280 - a bool match(filename) function
281 - a bool indicating if any patterns were passed in
281 - a bool indicating if any patterns were passed in
282
282
283 todo:
283 todo:
284 make head regex a rooted bool
284 make head regex a rooted bool
285 """
285 """
286
286
287 def contains_glob(name):
287 def contains_glob(name):
288 for c in name:
288 for c in name:
289 if c in _globchars: return True
289 if c in _globchars: return True
290 return False
290 return False
291
291
292 def regex(kind, name, tail):
292 def regex(kind, name, tail):
293 '''convert a pattern into a regular expression'''
293 '''convert a pattern into a regular expression'''
294 if kind == 're':
294 if kind == 're':
295 return name
295 return name
296 elif kind == 'path':
296 elif kind == 'path':
297 return '^' + re.escape(name) + '(?:/|$)'
297 return '^' + re.escape(name) + '(?:/|$)'
298 elif kind == 'relglob':
298 elif kind == 'relglob':
299 return head + globre(name, '(?:|.*/)', tail)
299 return head + globre(name, '(?:|.*/)', tail)
300 elif kind == 'relpath':
300 elif kind == 'relpath':
301 return head + re.escape(name) + tail
301 return head + re.escape(name) + tail
302 elif kind == 'relre':
302 elif kind == 'relre':
303 if name.startswith('^'):
303 if name.startswith('^'):
304 return name
304 return name
305 return '.*' + name
305 return '.*' + name
306 return head + globre(name, '', tail)
306 return head + globre(name, '', tail)
307
307
308 def matchfn(pats, tail):
308 def matchfn(pats, tail):
309 """build a matching function from a set of patterns"""
309 """build a matching function from a set of patterns"""
310 if not pats:
310 if not pats:
311 return
311 return
312 matches = []
312 matches = []
313 for k, p in pats:
313 for k, p in pats:
314 try:
314 try:
315 pat = '(?:%s)' % regex(k, p, tail)
315 pat = '(?:%s)' % regex(k, p, tail)
316 matches.append(re.compile(pat).match)
316 matches.append(re.compile(pat).match)
317 except re.error:
317 except re.error:
318 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
318 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
319 else: raise Abort("invalid pattern (%s): %s" % (k, p))
319 else: raise Abort("invalid pattern (%s): %s" % (k, p))
320
320
321 def buildfn(text):
321 def buildfn(text):
322 for m in matches:
322 for m in matches:
323 r = m(text)
323 r = m(text)
324 if r:
324 if r:
325 return r
325 return r
326
326
327 return buildfn
327 return buildfn
328
328
329 def globprefix(pat):
329 def globprefix(pat):
330 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
330 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
331 root = []
331 root = []
332 for p in pat.split(os.sep):
332 for p in pat.split(os.sep):
333 if contains_glob(p): break
333 if contains_glob(p): break
334 root.append(p)
334 root.append(p)
335 return '/'.join(root)
335 return '/'.join(root)
336
336
337 pats = []
337 pats = []
338 files = []
338 files = []
339 roots = []
339 roots = []
340 for kind, name in [patkind(p, dflt_pat) for p in names]:
340 for kind, name in [patkind(p, dflt_pat) for p in names]:
341 if kind in ('glob', 'relpath'):
341 if kind in ('glob', 'relpath'):
342 name = canonpath(canonroot, cwd, name)
342 name = canonpath(canonroot, cwd, name)
343 if name == '':
343 if name == '':
344 kind, name = 'glob', '**'
344 kind, name = 'glob', '**'
345 if kind in ('glob', 'path', 're'):
345 if kind in ('glob', 'path', 're'):
346 pats.append((kind, name))
346 pats.append((kind, name))
347 if kind == 'glob':
347 if kind == 'glob':
348 root = globprefix(name)
348 root = globprefix(name)
349 if root: roots.append(root)
349 if root: roots.append(root)
350 elif kind == 'relpath':
350 elif kind == 'relpath':
351 files.append((kind, name))
351 files.append((kind, name))
352 roots.append(name)
352 roots.append(name)
353
353
354 patmatch = matchfn(pats, '$') or always
354 patmatch = matchfn(pats, '$') or always
355 filematch = matchfn(files, '(?:/|$)') or always
355 filematch = matchfn(files, '(?:/|$)') or always
356 incmatch = always
356 incmatch = always
357 if inc:
357 if inc:
358 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
358 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
359 excmatch = lambda fn: False
359 excmatch = lambda fn: False
360 if exc:
360 if exc:
361 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
361 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
362
362
363 return (roots,
363 return (roots,
364 lambda fn: (incmatch(fn) and not excmatch(fn) and
364 lambda fn: (incmatch(fn) and not excmatch(fn) and
365 (fn.endswith('/') or
365 (fn.endswith('/') or
366 (not pats and not files) or
366 (not pats and not files) or
367 (pats and patmatch(fn)) or
367 (pats and patmatch(fn)) or
368 (files and filematch(fn)))),
368 (files and filematch(fn)))),
369 (inc or exc or (pats and pats != [('glob', '**')])) and True)
369 (inc or exc or (pats and pats != [('glob', '**')])) and True)
370
370
371 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
371 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
372 '''enhanced shell command execution.
372 '''enhanced shell command execution.
373 run with environment maybe modified, maybe in different dir.
373 run with environment maybe modified, maybe in different dir.
374
374
375 if command fails and onerr is None, return status. if ui object,
375 if command fails and onerr is None, return status. if ui object,
376 print error message and return status, else raise onerr object as
376 print error message and return status, else raise onerr object as
377 exception.'''
377 exception.'''
378 oldenv = {}
378 oldenv = {}
379 for k in environ:
379 for k in environ:
380 oldenv[k] = os.environ.get(k)
380 oldenv[k] = os.environ.get(k)
381 if cwd is not None:
381 if cwd is not None:
382 oldcwd = os.getcwd()
382 oldcwd = os.getcwd()
383 try:
383 try:
384 for k, v in environ.iteritems():
384 for k, v in environ.iteritems():
385 os.environ[k] = str(v)
385 os.environ[k] = str(v)
386 if cwd is not None and oldcwd != cwd:
386 if cwd is not None and oldcwd != cwd:
387 os.chdir(cwd)
387 os.chdir(cwd)
388 rc = os.system(cmd)
388 rc = os.system(cmd)
389 if rc and onerr:
389 if rc and onerr:
390 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
390 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
391 explain_exit(rc)[0])
391 explain_exit(rc)[0])
392 if errprefix:
392 if errprefix:
393 errmsg = '%s: %s' % (errprefix, errmsg)
393 errmsg = '%s: %s' % (errprefix, errmsg)
394 try:
394 try:
395 onerr.warn(errmsg + '\n')
395 onerr.warn(errmsg + '\n')
396 except AttributeError:
396 except AttributeError:
397 raise onerr(errmsg)
397 raise onerr(errmsg)
398 return rc
398 return rc
399 finally:
399 finally:
400 for k, v in oldenv.iteritems():
400 for k, v in oldenv.iteritems():
401 if v is None:
401 if v is None:
402 del os.environ[k]
402 del os.environ[k]
403 else:
403 else:
404 os.environ[k] = v
404 os.environ[k] = v
405 if cwd is not None and oldcwd != cwd:
405 if cwd is not None and oldcwd != cwd:
406 os.chdir(oldcwd)
406 os.chdir(oldcwd)
407
407
408 def rename(src, dst):
408 def rename(src, dst):
409 """forcibly rename a file"""
409 """forcibly rename a file"""
410 try:
410 try:
411 os.rename(src, dst)
411 os.rename(src, dst)
412 except OSError, err:
412 except OSError, err:
413 # on windows, rename to existing file is not allowed, so we
413 # on windows, rename to existing file is not allowed, so we
414 # must delete destination first. but if file is open, unlink
414 # must delete destination first. but if file is open, unlink
415 # schedules it for delete but does not delete it. rename
415 # schedules it for delete but does not delete it. rename
416 # happens immediately even for open files, so we create
416 # happens immediately even for open files, so we create
417 # temporary file, delete it, rename destination to that name,
417 # temporary file, delete it, rename destination to that name,
418 # then delete that. then rename is safe to do.
418 # then delete that. then rename is safe to do.
419 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
419 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
420 os.close(fd)
420 os.close(fd)
421 os.unlink(temp)
421 os.unlink(temp)
422 os.rename(dst, temp)
422 os.rename(dst, temp)
423 os.unlink(temp)
423 os.unlink(temp)
424 os.rename(src, dst)
424 os.rename(src, dst)
425
425
426 def unlink(f):
426 def unlink(f):
427 """unlink and remove the directory if it is empty"""
427 """unlink and remove the directory if it is empty"""
428 os.unlink(f)
428 os.unlink(f)
429 # try removing directories that might now be empty
429 # try removing directories that might now be empty
430 try:
430 try:
431 os.removedirs(os.path.dirname(f))
431 os.removedirs(os.path.dirname(f))
432 except OSError:
432 except OSError:
433 pass
433 pass
434
434
435 def copyfiles(src, dst, hardlink=None):
435 def copyfiles(src, dst, hardlink=None):
436 """Copy a directory tree using hardlinks if possible"""
436 """Copy a directory tree using hardlinks if possible"""
437
437
438 if hardlink is None:
438 if hardlink is None:
439 hardlink = (os.stat(src).st_dev ==
439 hardlink = (os.stat(src).st_dev ==
440 os.stat(os.path.dirname(dst)).st_dev)
440 os.stat(os.path.dirname(dst)).st_dev)
441
441
442 if os.path.isdir(src):
442 if os.path.isdir(src):
443 os.mkdir(dst)
443 os.mkdir(dst)
444 for name in os.listdir(src):
444 for name in os.listdir(src):
445 srcname = os.path.join(src, name)
445 srcname = os.path.join(src, name)
446 dstname = os.path.join(dst, name)
446 dstname = os.path.join(dst, name)
447 copyfiles(srcname, dstname, hardlink)
447 copyfiles(srcname, dstname, hardlink)
448 else:
448 else:
449 if hardlink:
449 if hardlink:
450 try:
450 try:
451 os_link(src, dst)
451 os_link(src, dst)
452 except (IOError, OSError):
452 except (IOError, OSError):
453 hardlink = False
453 hardlink = False
454 shutil.copy(src, dst)
454 shutil.copy(src, dst)
455 else:
455 else:
456 shutil.copy(src, dst)
456 shutil.copy(src, dst)
457
457
458 def audit_path(path):
458 def audit_path(path):
459 """Abort if path contains dangerous components"""
459 """Abort if path contains dangerous components"""
460 parts = os.path.normcase(path).split(os.sep)
460 parts = os.path.normcase(path).split(os.sep)
461 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
461 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
462 or os.pardir in parts):
462 or os.pardir in parts):
463 raise Abort(_("path contains illegal component: %s\n") % path)
463 raise Abort(_("path contains illegal component: %s\n") % path)
464
464
465 def _makelock_file(info, pathname):
465 def _makelock_file(info, pathname):
466 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
466 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
467 os.write(ld, info)
467 os.write(ld, info)
468 os.close(ld)
468 os.close(ld)
469
469
470 def _readlock_file(pathname):
470 def _readlock_file(pathname):
471 return posixfile(pathname).read()
471 return posixfile(pathname).read()
472
472
473 def nlinks(pathname):
473 def nlinks(pathname):
474 """Return number of hardlinks for the given file."""
474 """Return number of hardlinks for the given file."""
475 return os.stat(pathname).st_nlink
475 return os.stat(pathname).st_nlink
476
476
477 if hasattr(os, 'link'):
477 if hasattr(os, 'link'):
478 os_link = os.link
478 os_link = os.link
479 else:
479 else:
480 def os_link(src, dst):
480 def os_link(src, dst):
481 raise OSError(0, _("Hardlinks not supported"))
481 raise OSError(0, _("Hardlinks not supported"))
482
482
483 def fstat(fp):
483 def fstat(fp):
484 '''stat file object that may not have fileno method.'''
484 '''stat file object that may not have fileno method.'''
485 try:
485 try:
486 return os.fstat(fp.fileno())
486 return os.fstat(fp.fileno())
487 except AttributeError:
487 except AttributeError:
488 return os.stat(fp.name)
488 return os.stat(fp.name)
489
489
490 posixfile = file
490 posixfile = file
491
491
492 def is_win_9x():
493 '''return true if run on windows 95, 98 or me.'''
494 try:
495 return sys.getwindowsversion()[3] == 1
496 except AttributeError:
497 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
498
492 # Platform specific variants
499 # Platform specific variants
493 if os.name == 'nt':
500 if os.name == 'nt':
494 demandload(globals(), "msvcrt")
501 demandload(globals(), "msvcrt")
495 nulldev = 'NUL:'
502 nulldev = 'NUL:'
496
503
497 class winstdout:
504 class winstdout:
498 '''stdout on windows misbehaves if sent through a pipe'''
505 '''stdout on windows misbehaves if sent through a pipe'''
499
506
500 def __init__(self, fp):
507 def __init__(self, fp):
501 self.fp = fp
508 self.fp = fp
502
509
503 def __getattr__(self, key):
510 def __getattr__(self, key):
504 return getattr(self.fp, key)
511 return getattr(self.fp, key)
505
512
506 def close(self):
513 def close(self):
507 try:
514 try:
508 self.fp.close()
515 self.fp.close()
509 except: pass
516 except: pass
510
517
511 def write(self, s):
518 def write(self, s):
512 try:
519 try:
513 return self.fp.write(s)
520 return self.fp.write(s)
514 except IOError, inst:
521 except IOError, inst:
515 if inst.errno != 0: raise
522 if inst.errno != 0: raise
516 self.close()
523 self.close()
517 raise IOError(errno.EPIPE, 'Broken pipe')
524 raise IOError(errno.EPIPE, 'Broken pipe')
518
525
519 sys.stdout = winstdout(sys.stdout)
526 sys.stdout = winstdout(sys.stdout)
520
527
521 def system_rcpath():
528 def system_rcpath():
522 try:
529 try:
523 return system_rcpath_win32()
530 return system_rcpath_win32()
524 except:
531 except:
525 return [r'c:\mercurial\mercurial.ini']
532 return [r'c:\mercurial\mercurial.ini']
526
533
527 def os_rcpath():
534 def os_rcpath():
528 '''return default os-specific hgrc search path'''
535 '''return default os-specific hgrc search path'''
529 return system_rcpath() + [os.path.join(os.path.expanduser('~'),
536 return system_rcpath() + [os.path.join(os.path.expanduser('~'),
530 'mercurial.ini')]
537 'mercurial.ini')]
531
538
532 def parse_patch_output(output_line):
539 def parse_patch_output(output_line):
533 """parses the output produced by patch and returns the file name"""
540 """parses the output produced by patch and returns the file name"""
534 pf = output_line[14:]
541 pf = output_line[14:]
535 if pf[0] == '`':
542 if pf[0] == '`':
536 pf = pf[1:-1] # Remove the quotes
543 pf = pf[1:-1] # Remove the quotes
537 return pf
544 return pf
538
545
539 def testpid(pid):
546 def testpid(pid):
540 '''return False if pid dead, True if running or not known'''
547 '''return False if pid dead, True if running or not known'''
541 return True
548 return True
542
549
543 def is_exec(f, last):
550 def is_exec(f, last):
544 return last
551 return last
545
552
546 def set_exec(f, mode):
553 def set_exec(f, mode):
547 pass
554 pass
548
555
549 def set_binary(fd):
556 def set_binary(fd):
550 msvcrt.setmode(fd.fileno(), os.O_BINARY)
557 msvcrt.setmode(fd.fileno(), os.O_BINARY)
551
558
552 def pconvert(path):
559 def pconvert(path):
553 return path.replace("\\", "/")
560 return path.replace("\\", "/")
554
561
555 def localpath(path):
562 def localpath(path):
556 return path.replace('/', '\\')
563 return path.replace('/', '\\')
557
564
558 def normpath(path):
565 def normpath(path):
559 return pconvert(os.path.normpath(path))
566 return pconvert(os.path.normpath(path))
560
567
561 makelock = _makelock_file
568 makelock = _makelock_file
562 readlock = _readlock_file
569 readlock = _readlock_file
563
570
564 def samestat(s1, s2):
571 def samestat(s1, s2):
565 return False
572 return False
566
573
567 def explain_exit(code):
574 def explain_exit(code):
568 return _("exited with status %d") % code, code
575 return _("exited with status %d") % code, code
569
576
570 try:
577 try:
571 # override functions with win32 versions if possible
578 # override functions with win32 versions if possible
572 from util_win32 import *
579 from util_win32 import *
580 if not is_win_9x():
581 posixfile = posixfile_nt
573 except ImportError:
582 except ImportError:
574 pass
583 pass
575
584
576 else:
585 else:
577 nulldev = '/dev/null'
586 nulldev = '/dev/null'
578
587
579 def rcfiles(path):
588 def rcfiles(path):
580 rcs = [os.path.join(path, 'hgrc')]
589 rcs = [os.path.join(path, 'hgrc')]
581 rcdir = os.path.join(path, 'hgrc.d')
590 rcdir = os.path.join(path, 'hgrc.d')
582 try:
591 try:
583 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
592 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
584 if f.endswith(".rc")])
593 if f.endswith(".rc")])
585 except OSError, inst: pass
594 except OSError, inst: pass
586 return rcs
595 return rcs
587
596
588 def os_rcpath():
597 def os_rcpath():
589 '''return default os-specific hgrc search path'''
598 '''return default os-specific hgrc search path'''
590 path = []
599 path = []
591 if len(sys.argv) > 0:
600 if len(sys.argv) > 0:
592 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
601 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
593 '/../etc/mercurial'))
602 '/../etc/mercurial'))
594 path.extend(rcfiles('/etc/mercurial'))
603 path.extend(rcfiles('/etc/mercurial'))
595 path.append(os.path.expanduser('~/.hgrc'))
604 path.append(os.path.expanduser('~/.hgrc'))
596 path = [os.path.normpath(f) for f in path]
605 path = [os.path.normpath(f) for f in path]
597 return path
606 return path
598
607
599 def parse_patch_output(output_line):
608 def parse_patch_output(output_line):
600 """parses the output produced by patch and returns the file name"""
609 """parses the output produced by patch and returns the file name"""
601 pf = output_line[14:]
610 pf = output_line[14:]
602 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
611 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
603 pf = pf[1:-1] # Remove the quotes
612 pf = pf[1:-1] # Remove the quotes
604 return pf
613 return pf
605
614
606 def is_exec(f, last):
615 def is_exec(f, last):
607 """check whether a file is executable"""
616 """check whether a file is executable"""
608 return (os.stat(f).st_mode & 0100 != 0)
617 return (os.stat(f).st_mode & 0100 != 0)
609
618
610 def set_exec(f, mode):
619 def set_exec(f, mode):
611 s = os.stat(f).st_mode
620 s = os.stat(f).st_mode
612 if (s & 0100 != 0) == mode:
621 if (s & 0100 != 0) == mode:
613 return
622 return
614 if mode:
623 if mode:
615 # Turn on +x for every +r bit when making a file executable
624 # Turn on +x for every +r bit when making a file executable
616 # and obey umask.
625 # and obey umask.
617 umask = os.umask(0)
626 umask = os.umask(0)
618 os.umask(umask)
627 os.umask(umask)
619 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
628 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
620 else:
629 else:
621 os.chmod(f, s & 0666)
630 os.chmod(f, s & 0666)
622
631
623 def set_binary(fd):
632 def set_binary(fd):
624 pass
633 pass
625
634
626 def pconvert(path):
635 def pconvert(path):
627 return path
636 return path
628
637
629 def localpath(path):
638 def localpath(path):
630 return path
639 return path
631
640
632 normpath = os.path.normpath
641 normpath = os.path.normpath
633 samestat = os.path.samestat
642 samestat = os.path.samestat
634
643
635 def makelock(info, pathname):
644 def makelock(info, pathname):
636 try:
645 try:
637 os.symlink(info, pathname)
646 os.symlink(info, pathname)
638 except OSError, why:
647 except OSError, why:
639 if why.errno == errno.EEXIST:
648 if why.errno == errno.EEXIST:
640 raise
649 raise
641 else:
650 else:
642 _makelock_file(info, pathname)
651 _makelock_file(info, pathname)
643
652
644 def readlock(pathname):
653 def readlock(pathname):
645 try:
654 try:
646 return os.readlink(pathname)
655 return os.readlink(pathname)
647 except OSError, why:
656 except OSError, why:
648 if why.errno == errno.EINVAL:
657 if why.errno == errno.EINVAL:
649 return _readlock_file(pathname)
658 return _readlock_file(pathname)
650 else:
659 else:
651 raise
660 raise
652
661
653 def testpid(pid):
662 def testpid(pid):
654 '''return False if pid dead, True if running or not sure'''
663 '''return False if pid dead, True if running or not sure'''
655 try:
664 try:
656 os.kill(pid, 0)
665 os.kill(pid, 0)
657 return True
666 return True
658 except OSError, inst:
667 except OSError, inst:
659 return inst.errno != errno.ESRCH
668 return inst.errno != errno.ESRCH
660
669
661 def explain_exit(code):
670 def explain_exit(code):
662 """return a 2-tuple (desc, code) describing a process's status"""
671 """return a 2-tuple (desc, code) describing a process's status"""
663 if os.WIFEXITED(code):
672 if os.WIFEXITED(code):
664 val = os.WEXITSTATUS(code)
673 val = os.WEXITSTATUS(code)
665 return _("exited with status %d") % val, val
674 return _("exited with status %d") % val, val
666 elif os.WIFSIGNALED(code):
675 elif os.WIFSIGNALED(code):
667 val = os.WTERMSIG(code)
676 val = os.WTERMSIG(code)
668 return _("killed by signal %d") % val, val
677 return _("killed by signal %d") % val, val
669 elif os.WIFSTOPPED(code):
678 elif os.WIFSTOPPED(code):
670 val = os.WSTOPSIG(code)
679 val = os.WSTOPSIG(code)
671 return _("stopped by signal %d") % val, val
680 return _("stopped by signal %d") % val, val
672 raise ValueError(_("invalid exit code"))
681 raise ValueError(_("invalid exit code"))
673
682
674 def opener(base, audit=True):
683 def opener(base, audit=True):
675 """
684 """
676 return a function that opens files relative to base
685 return a function that opens files relative to base
677
686
678 this function is used to hide the details of COW semantics and
687 this function is used to hide the details of COW semantics and
679 remote file access from higher level code.
688 remote file access from higher level code.
680 """
689 """
681 p = base
690 p = base
682 audit_p = audit
691 audit_p = audit
683
692
684 def mktempcopy(name):
693 def mktempcopy(name):
685 d, fn = os.path.split(name)
694 d, fn = os.path.split(name)
686 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
695 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
687 os.close(fd)
696 os.close(fd)
688 ofp = posixfile(temp, "wb")
697 ofp = posixfile(temp, "wb")
689 try:
698 try:
690 try:
699 try:
691 ifp = posixfile(name, "rb")
700 ifp = posixfile(name, "rb")
692 except IOError, inst:
701 except IOError, inst:
693 if not getattr(inst, 'filename', None):
702 if not getattr(inst, 'filename', None):
694 inst.filename = name
703 inst.filename = name
695 raise
704 raise
696 for chunk in filechunkiter(ifp):
705 for chunk in filechunkiter(ifp):
697 ofp.write(chunk)
706 ofp.write(chunk)
698 ifp.close()
707 ifp.close()
699 ofp.close()
708 ofp.close()
700 except:
709 except:
701 try: os.unlink(temp)
710 try: os.unlink(temp)
702 except: pass
711 except: pass
703 raise
712 raise
704 st = os.lstat(name)
713 st = os.lstat(name)
705 os.chmod(temp, st.st_mode)
714 os.chmod(temp, st.st_mode)
706 return temp
715 return temp
707
716
708 class atomictempfile(posixfile):
717 class atomictempfile(posixfile):
709 """the file will only be copied when rename is called"""
718 """the file will only be copied when rename is called"""
710 def __init__(self, name, mode):
719 def __init__(self, name, mode):
711 self.__name = name
720 self.__name = name
712 self.temp = mktempcopy(name)
721 self.temp = mktempcopy(name)
713 posixfile.__init__(self, self.temp, mode)
722 posixfile.__init__(self, self.temp, mode)
714 def rename(self):
723 def rename(self):
715 if not self.closed:
724 if not self.closed:
716 posixfile.close(self)
725 posixfile.close(self)
717 rename(self.temp, self.__name)
726 rename(self.temp, self.__name)
718 def __del__(self):
727 def __del__(self):
719 if not self.closed:
728 if not self.closed:
720 try:
729 try:
721 os.unlink(self.temp)
730 os.unlink(self.temp)
722 except: pass
731 except: pass
723 posixfile.close(self)
732 posixfile.close(self)
724
733
725 class atomicfile(atomictempfile):
734 class atomicfile(atomictempfile):
726 """the file will only be copied on close"""
735 """the file will only be copied on close"""
727 def __init__(self, name, mode):
736 def __init__(self, name, mode):
728 atomictempfile.__init__(self, name, mode)
737 atomictempfile.__init__(self, name, mode)
729 def close(self):
738 def close(self):
730 self.rename()
739 self.rename()
731 def __del__(self):
740 def __del__(self):
732 self.rename()
741 self.rename()
733
742
734 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
743 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
735 if audit_p:
744 if audit_p:
736 audit_path(path)
745 audit_path(path)
737 f = os.path.join(p, path)
746 f = os.path.join(p, path)
738
747
739 if not text:
748 if not text:
740 mode += "b" # for that other OS
749 mode += "b" # for that other OS
741
750
742 if mode[0] != "r":
751 if mode[0] != "r":
743 try:
752 try:
744 nlink = nlinks(f)
753 nlink = nlinks(f)
745 except OSError:
754 except OSError:
746 d = os.path.dirname(f)
755 d = os.path.dirname(f)
747 if not os.path.isdir(d):
756 if not os.path.isdir(d):
748 os.makedirs(d)
757 os.makedirs(d)
749 else:
758 else:
750 if atomic:
759 if atomic:
751 return atomicfile(f, mode)
760 return atomicfile(f, mode)
752 elif atomictemp:
761 elif atomictemp:
753 return atomictempfile(f, mode)
762 return atomictempfile(f, mode)
754 if nlink > 1:
763 if nlink > 1:
755 rename(mktempcopy(f), f)
764 rename(mktempcopy(f), f)
756 return posixfile(f, mode)
765 return posixfile(f, mode)
757
766
758 return o
767 return o
759
768
760 class chunkbuffer(object):
769 class chunkbuffer(object):
761 """Allow arbitrary sized chunks of data to be efficiently read from an
770 """Allow arbitrary sized chunks of data to be efficiently read from an
762 iterator over chunks of arbitrary size."""
771 iterator over chunks of arbitrary size."""
763
772
764 def __init__(self, in_iter, targetsize = 2**16):
773 def __init__(self, in_iter, targetsize = 2**16):
765 """in_iter is the iterator that's iterating over the input chunks.
774 """in_iter is the iterator that's iterating over the input chunks.
766 targetsize is how big a buffer to try to maintain."""
775 targetsize is how big a buffer to try to maintain."""
767 self.in_iter = iter(in_iter)
776 self.in_iter = iter(in_iter)
768 self.buf = ''
777 self.buf = ''
769 self.targetsize = int(targetsize)
778 self.targetsize = int(targetsize)
770 if self.targetsize <= 0:
779 if self.targetsize <= 0:
771 raise ValueError(_("targetsize must be greater than 0, was %d") %
780 raise ValueError(_("targetsize must be greater than 0, was %d") %
772 targetsize)
781 targetsize)
773 self.iterempty = False
782 self.iterempty = False
774
783
775 def fillbuf(self):
784 def fillbuf(self):
776 """Ignore target size; read every chunk from iterator until empty."""
785 """Ignore target size; read every chunk from iterator until empty."""
777 if not self.iterempty:
786 if not self.iterempty:
778 collector = cStringIO.StringIO()
787 collector = cStringIO.StringIO()
779 collector.write(self.buf)
788 collector.write(self.buf)
780 for ch in self.in_iter:
789 for ch in self.in_iter:
781 collector.write(ch)
790 collector.write(ch)
782 self.buf = collector.getvalue()
791 self.buf = collector.getvalue()
783 self.iterempty = True
792 self.iterempty = True
784
793
785 def read(self, l):
794 def read(self, l):
786 """Read L bytes of data from the iterator of chunks of data.
795 """Read L bytes of data from the iterator of chunks of data.
787 Returns less than L bytes if the iterator runs dry."""
796 Returns less than L bytes if the iterator runs dry."""
788 if l > len(self.buf) and not self.iterempty:
797 if l > len(self.buf) and not self.iterempty:
789 # Clamp to a multiple of self.targetsize
798 # Clamp to a multiple of self.targetsize
790 targetsize = self.targetsize * ((l // self.targetsize) + 1)
799 targetsize = self.targetsize * ((l // self.targetsize) + 1)
791 collector = cStringIO.StringIO()
800 collector = cStringIO.StringIO()
792 collector.write(self.buf)
801 collector.write(self.buf)
793 collected = len(self.buf)
802 collected = len(self.buf)
794 for chunk in self.in_iter:
803 for chunk in self.in_iter:
795 collector.write(chunk)
804 collector.write(chunk)
796 collected += len(chunk)
805 collected += len(chunk)
797 if collected >= targetsize:
806 if collected >= targetsize:
798 break
807 break
799 if collected < targetsize:
808 if collected < targetsize:
800 self.iterempty = True
809 self.iterempty = True
801 self.buf = collector.getvalue()
810 self.buf = collector.getvalue()
802 s, self.buf = self.buf[:l], buffer(self.buf, l)
811 s, self.buf = self.buf[:l], buffer(self.buf, l)
803 return s
812 return s
804
813
805 def filechunkiter(f, size = 65536):
814 def filechunkiter(f, size = 65536):
806 """Create a generator that produces all the data in the file size
815 """Create a generator that produces all the data in the file size
807 (default 65536) bytes at a time. Chunks may be less than size
816 (default 65536) bytes at a time. Chunks may be less than size
808 bytes if the chunk is the last chunk in the file, or the file is a
817 bytes if the chunk is the last chunk in the file, or the file is a
809 socket or some other type of file that sometimes reads less data
818 socket or some other type of file that sometimes reads less data
810 than is requested."""
819 than is requested."""
811 s = f.read(size)
820 s = f.read(size)
812 while len(s) > 0:
821 while len(s) > 0:
813 yield s
822 yield s
814 s = f.read(size)
823 s = f.read(size)
815
824
816 def makedate():
825 def makedate():
817 lt = time.localtime()
826 lt = time.localtime()
818 if lt[8] == 1 and time.daylight:
827 if lt[8] == 1 and time.daylight:
819 tz = time.altzone
828 tz = time.altzone
820 else:
829 else:
821 tz = time.timezone
830 tz = time.timezone
822 return time.mktime(lt), tz
831 return time.mktime(lt), tz
823
832
824 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
833 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
825 """represent a (unixtime, offset) tuple as a localized time.
834 """represent a (unixtime, offset) tuple as a localized time.
826 unixtime is seconds since the epoch, and offset is the time zone's
835 unixtime is seconds since the epoch, and offset is the time zone's
827 number of seconds away from UTC. if timezone is false, do not
836 number of seconds away from UTC. if timezone is false, do not
828 append time zone to string."""
837 append time zone to string."""
829 t, tz = date or makedate()
838 t, tz = date or makedate()
830 s = time.strftime(format, time.gmtime(float(t) - tz))
839 s = time.strftime(format, time.gmtime(float(t) - tz))
831 if timezone:
840 if timezone:
832 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
841 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
833 return s
842 return s
834
843
835 def shortuser(user):
844 def shortuser(user):
836 """Return a short representation of a user name or email address."""
845 """Return a short representation of a user name or email address."""
837 f = user.find('@')
846 f = user.find('@')
838 if f >= 0:
847 if f >= 0:
839 user = user[:f]
848 user = user[:f]
840 f = user.find('<')
849 f = user.find('<')
841 if f >= 0:
850 if f >= 0:
842 user = user[f+1:]
851 user = user[f+1:]
843 return user
852 return user
844
853
845 def walkrepos(path):
854 def walkrepos(path):
846 '''yield every hg repository under path, recursively.'''
855 '''yield every hg repository under path, recursively.'''
847 def errhandler(err):
856 def errhandler(err):
848 if err.filename == path:
857 if err.filename == path:
849 raise err
858 raise err
850
859
851 for root, dirs, files in os.walk(path, onerror=errhandler):
860 for root, dirs, files in os.walk(path, onerror=errhandler):
852 for d in dirs:
861 for d in dirs:
853 if d == '.hg':
862 if d == '.hg':
854 yield root
863 yield root
855 dirs[:] = []
864 dirs[:] = []
856 break
865 break
857
866
858 _rcpath = None
867 _rcpath = None
859
868
860 def rcpath():
869 def rcpath():
861 '''return hgrc search path. if env var HGRCPATH is set, use it.
870 '''return hgrc search path. if env var HGRCPATH is set, use it.
862 for each item in path, if directory, use files ending in .rc,
871 for each item in path, if directory, use files ending in .rc,
863 else use item.
872 else use item.
864 make HGRCPATH empty to only look in .hg/hgrc of current repo.
873 make HGRCPATH empty to only look in .hg/hgrc of current repo.
865 if no HGRCPATH, use default os-specific path.'''
874 if no HGRCPATH, use default os-specific path.'''
866 global _rcpath
875 global _rcpath
867 if _rcpath is None:
876 if _rcpath is None:
868 if 'HGRCPATH' in os.environ:
877 if 'HGRCPATH' in os.environ:
869 _rcpath = []
878 _rcpath = []
870 for p in os.environ['HGRCPATH'].split(os.pathsep):
879 for p in os.environ['HGRCPATH'].split(os.pathsep):
871 if not p: continue
880 if not p: continue
872 if os.path.isdir(p):
881 if os.path.isdir(p):
873 for f in os.listdir(p):
882 for f in os.listdir(p):
874 if f.endswith('.rc'):
883 if f.endswith('.rc'):
875 _rcpath.append(os.path.join(p, f))
884 _rcpath.append(os.path.join(p, f))
876 else:
885 else:
877 _rcpath.append(p)
886 _rcpath.append(p)
878 else:
887 else:
879 _rcpath = os_rcpath()
888 _rcpath = os_rcpath()
880 return _rcpath
889 return _rcpath
@@ -1,284 +1,284 b''
1 # util_win32.py - utility functions that use win32 API
1 # util_win32.py - utility functions that use win32 API
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of
6 # This software may be used and distributed according to the terms of
7 # the GNU General Public License, incorporated herein by reference.
7 # the GNU General Public License, incorporated herein by reference.
8
8
9 # Mark Hammond's win32all package allows better functionality on
9 # Mark Hammond's win32all package allows better functionality on
10 # Windows. this module overrides definitions in util.py. if not
10 # Windows. this module overrides definitions in util.py. if not
11 # available, import of this module will fail, and generic code will be
11 # available, import of this module will fail, and generic code will be
12 # used.
12 # used.
13
13
14 import win32api
14 import win32api
15
15
16 from demandload import *
16 from demandload import *
17 from i18n import gettext as _
17 from i18n import gettext as _
18 demandload(globals(), 'errno os pywintypes win32con win32file win32process')
18 demandload(globals(), 'errno os pywintypes win32con win32file win32process')
19 demandload(globals(), 'cStringIO winerror')
19 demandload(globals(), 'cStringIO winerror')
20
20
21 class WinError:
21 class WinError:
22 winerror_map = {
22 winerror_map = {
23 winerror.ERROR_ACCESS_DENIED: errno.EACCES,
23 winerror.ERROR_ACCESS_DENIED: errno.EACCES,
24 winerror.ERROR_ACCOUNT_DISABLED: errno.EACCES,
24 winerror.ERROR_ACCOUNT_DISABLED: errno.EACCES,
25 winerror.ERROR_ACCOUNT_RESTRICTION: errno.EACCES,
25 winerror.ERROR_ACCOUNT_RESTRICTION: errno.EACCES,
26 winerror.ERROR_ALREADY_ASSIGNED: errno.EBUSY,
26 winerror.ERROR_ALREADY_ASSIGNED: errno.EBUSY,
27 winerror.ERROR_ALREADY_EXISTS: errno.EEXIST,
27 winerror.ERROR_ALREADY_EXISTS: errno.EEXIST,
28 winerror.ERROR_ARITHMETIC_OVERFLOW: errno.ERANGE,
28 winerror.ERROR_ARITHMETIC_OVERFLOW: errno.ERANGE,
29 winerror.ERROR_BAD_COMMAND: errno.EIO,
29 winerror.ERROR_BAD_COMMAND: errno.EIO,
30 winerror.ERROR_BAD_DEVICE: errno.ENODEV,
30 winerror.ERROR_BAD_DEVICE: errno.ENODEV,
31 winerror.ERROR_BAD_DRIVER_LEVEL: errno.ENXIO,
31 winerror.ERROR_BAD_DRIVER_LEVEL: errno.ENXIO,
32 winerror.ERROR_BAD_EXE_FORMAT: errno.ENOEXEC,
32 winerror.ERROR_BAD_EXE_FORMAT: errno.ENOEXEC,
33 winerror.ERROR_BAD_FORMAT: errno.ENOEXEC,
33 winerror.ERROR_BAD_FORMAT: errno.ENOEXEC,
34 winerror.ERROR_BAD_LENGTH: errno.EINVAL,
34 winerror.ERROR_BAD_LENGTH: errno.EINVAL,
35 winerror.ERROR_BAD_PATHNAME: errno.ENOENT,
35 winerror.ERROR_BAD_PATHNAME: errno.ENOENT,
36 winerror.ERROR_BAD_PIPE: errno.EPIPE,
36 winerror.ERROR_BAD_PIPE: errno.EPIPE,
37 winerror.ERROR_BAD_UNIT: errno.ENODEV,
37 winerror.ERROR_BAD_UNIT: errno.ENODEV,
38 winerror.ERROR_BAD_USERNAME: errno.EINVAL,
38 winerror.ERROR_BAD_USERNAME: errno.EINVAL,
39 winerror.ERROR_BROKEN_PIPE: errno.EPIPE,
39 winerror.ERROR_BROKEN_PIPE: errno.EPIPE,
40 winerror.ERROR_BUFFER_OVERFLOW: errno.ENAMETOOLONG,
40 winerror.ERROR_BUFFER_OVERFLOW: errno.ENAMETOOLONG,
41 winerror.ERROR_BUSY: errno.EBUSY,
41 winerror.ERROR_BUSY: errno.EBUSY,
42 winerror.ERROR_BUSY_DRIVE: errno.EBUSY,
42 winerror.ERROR_BUSY_DRIVE: errno.EBUSY,
43 winerror.ERROR_CALL_NOT_IMPLEMENTED: errno.ENOSYS,
43 winerror.ERROR_CALL_NOT_IMPLEMENTED: errno.ENOSYS,
44 winerror.ERROR_CANNOT_MAKE: errno.EACCES,
44 winerror.ERROR_CANNOT_MAKE: errno.EACCES,
45 winerror.ERROR_CANTOPEN: errno.EIO,
45 winerror.ERROR_CANTOPEN: errno.EIO,
46 winerror.ERROR_CANTREAD: errno.EIO,
46 winerror.ERROR_CANTREAD: errno.EIO,
47 winerror.ERROR_CANTWRITE: errno.EIO,
47 winerror.ERROR_CANTWRITE: errno.EIO,
48 winerror.ERROR_CRC: errno.EIO,
48 winerror.ERROR_CRC: errno.EIO,
49 winerror.ERROR_CURRENT_DIRECTORY: errno.EACCES,
49 winerror.ERROR_CURRENT_DIRECTORY: errno.EACCES,
50 winerror.ERROR_DEVICE_IN_USE: errno.EBUSY,
50 winerror.ERROR_DEVICE_IN_USE: errno.EBUSY,
51 winerror.ERROR_DEV_NOT_EXIST: errno.ENODEV,
51 winerror.ERROR_DEV_NOT_EXIST: errno.ENODEV,
52 winerror.ERROR_DIRECTORY: errno.EINVAL,
52 winerror.ERROR_DIRECTORY: errno.EINVAL,
53 winerror.ERROR_DIR_NOT_EMPTY: errno.ENOTEMPTY,
53 winerror.ERROR_DIR_NOT_EMPTY: errno.ENOTEMPTY,
54 winerror.ERROR_DISK_CHANGE: errno.EIO,
54 winerror.ERROR_DISK_CHANGE: errno.EIO,
55 winerror.ERROR_DISK_FULL: errno.ENOSPC,
55 winerror.ERROR_DISK_FULL: errno.ENOSPC,
56 winerror.ERROR_DRIVE_LOCKED: errno.EBUSY,
56 winerror.ERROR_DRIVE_LOCKED: errno.EBUSY,
57 winerror.ERROR_ENVVAR_NOT_FOUND: errno.EINVAL,
57 winerror.ERROR_ENVVAR_NOT_FOUND: errno.EINVAL,
58 winerror.ERROR_EXE_MARKED_INVALID: errno.ENOEXEC,
58 winerror.ERROR_EXE_MARKED_INVALID: errno.ENOEXEC,
59 winerror.ERROR_FILENAME_EXCED_RANGE: errno.ENAMETOOLONG,
59 winerror.ERROR_FILENAME_EXCED_RANGE: errno.ENAMETOOLONG,
60 winerror.ERROR_FILE_EXISTS: errno.EEXIST,
60 winerror.ERROR_FILE_EXISTS: errno.EEXIST,
61 winerror.ERROR_FILE_INVALID: errno.ENODEV,
61 winerror.ERROR_FILE_INVALID: errno.ENODEV,
62 winerror.ERROR_FILE_NOT_FOUND: errno.ENOENT,
62 winerror.ERROR_FILE_NOT_FOUND: errno.ENOENT,
63 winerror.ERROR_GEN_FAILURE: errno.EIO,
63 winerror.ERROR_GEN_FAILURE: errno.EIO,
64 winerror.ERROR_HANDLE_DISK_FULL: errno.ENOSPC,
64 winerror.ERROR_HANDLE_DISK_FULL: errno.ENOSPC,
65 winerror.ERROR_INSUFFICIENT_BUFFER: errno.ENOMEM,
65 winerror.ERROR_INSUFFICIENT_BUFFER: errno.ENOMEM,
66 winerror.ERROR_INVALID_ACCESS: errno.EACCES,
66 winerror.ERROR_INVALID_ACCESS: errno.EACCES,
67 winerror.ERROR_INVALID_ADDRESS: errno.EFAULT,
67 winerror.ERROR_INVALID_ADDRESS: errno.EFAULT,
68 winerror.ERROR_INVALID_BLOCK: errno.EFAULT,
68 winerror.ERROR_INVALID_BLOCK: errno.EFAULT,
69 winerror.ERROR_INVALID_DATA: errno.EINVAL,
69 winerror.ERROR_INVALID_DATA: errno.EINVAL,
70 winerror.ERROR_INVALID_DRIVE: errno.ENODEV,
70 winerror.ERROR_INVALID_DRIVE: errno.ENODEV,
71 winerror.ERROR_INVALID_EXE_SIGNATURE: errno.ENOEXEC,
71 winerror.ERROR_INVALID_EXE_SIGNATURE: errno.ENOEXEC,
72 winerror.ERROR_INVALID_FLAGS: errno.EINVAL,
72 winerror.ERROR_INVALID_FLAGS: errno.EINVAL,
73 winerror.ERROR_INVALID_FUNCTION: errno.ENOSYS,
73 winerror.ERROR_INVALID_FUNCTION: errno.ENOSYS,
74 winerror.ERROR_INVALID_HANDLE: errno.EBADF,
74 winerror.ERROR_INVALID_HANDLE: errno.EBADF,
75 winerror.ERROR_INVALID_LOGON_HOURS: errno.EACCES,
75 winerror.ERROR_INVALID_LOGON_HOURS: errno.EACCES,
76 winerror.ERROR_INVALID_NAME: errno.EINVAL,
76 winerror.ERROR_INVALID_NAME: errno.EINVAL,
77 winerror.ERROR_INVALID_OWNER: errno.EINVAL,
77 winerror.ERROR_INVALID_OWNER: errno.EINVAL,
78 winerror.ERROR_INVALID_PARAMETER: errno.EINVAL,
78 winerror.ERROR_INVALID_PARAMETER: errno.EINVAL,
79 winerror.ERROR_INVALID_PASSWORD: errno.EPERM,
79 winerror.ERROR_INVALID_PASSWORD: errno.EPERM,
80 winerror.ERROR_INVALID_PRIMARY_GROUP: errno.EINVAL,
80 winerror.ERROR_INVALID_PRIMARY_GROUP: errno.EINVAL,
81 winerror.ERROR_INVALID_SIGNAL_NUMBER: errno.EINVAL,
81 winerror.ERROR_INVALID_SIGNAL_NUMBER: errno.EINVAL,
82 winerror.ERROR_INVALID_TARGET_HANDLE: errno.EIO,
82 winerror.ERROR_INVALID_TARGET_HANDLE: errno.EIO,
83 winerror.ERROR_INVALID_WORKSTATION: errno.EACCES,
83 winerror.ERROR_INVALID_WORKSTATION: errno.EACCES,
84 winerror.ERROR_IO_DEVICE: errno.EIO,
84 winerror.ERROR_IO_DEVICE: errno.EIO,
85 winerror.ERROR_IO_INCOMPLETE: errno.EINTR,
85 winerror.ERROR_IO_INCOMPLETE: errno.EINTR,
86 winerror.ERROR_LOCKED: errno.EBUSY,
86 winerror.ERROR_LOCKED: errno.EBUSY,
87 winerror.ERROR_LOCK_VIOLATION: errno.EACCES,
87 winerror.ERROR_LOCK_VIOLATION: errno.EACCES,
88 winerror.ERROR_LOGON_FAILURE: errno.EACCES,
88 winerror.ERROR_LOGON_FAILURE: errno.EACCES,
89 winerror.ERROR_MAPPED_ALIGNMENT: errno.EINVAL,
89 winerror.ERROR_MAPPED_ALIGNMENT: errno.EINVAL,
90 winerror.ERROR_META_EXPANSION_TOO_LONG: errno.E2BIG,
90 winerror.ERROR_META_EXPANSION_TOO_LONG: errno.E2BIG,
91 winerror.ERROR_MORE_DATA: errno.EPIPE,
91 winerror.ERROR_MORE_DATA: errno.EPIPE,
92 winerror.ERROR_NEGATIVE_SEEK: errno.ESPIPE,
92 winerror.ERROR_NEGATIVE_SEEK: errno.ESPIPE,
93 winerror.ERROR_NOACCESS: errno.EFAULT,
93 winerror.ERROR_NOACCESS: errno.EFAULT,
94 winerror.ERROR_NONE_MAPPED: errno.EINVAL,
94 winerror.ERROR_NONE_MAPPED: errno.EINVAL,
95 winerror.ERROR_NOT_ENOUGH_MEMORY: errno.ENOMEM,
95 winerror.ERROR_NOT_ENOUGH_MEMORY: errno.ENOMEM,
96 winerror.ERROR_NOT_READY: errno.EAGAIN,
96 winerror.ERROR_NOT_READY: errno.EAGAIN,
97 winerror.ERROR_NOT_SAME_DEVICE: errno.EXDEV,
97 winerror.ERROR_NOT_SAME_DEVICE: errno.EXDEV,
98 winerror.ERROR_NO_DATA: errno.EPIPE,
98 winerror.ERROR_NO_DATA: errno.EPIPE,
99 winerror.ERROR_NO_MORE_SEARCH_HANDLES: errno.EIO,
99 winerror.ERROR_NO_MORE_SEARCH_HANDLES: errno.EIO,
100 winerror.ERROR_NO_PROC_SLOTS: errno.EAGAIN,
100 winerror.ERROR_NO_PROC_SLOTS: errno.EAGAIN,
101 winerror.ERROR_NO_SUCH_PRIVILEGE: errno.EACCES,
101 winerror.ERROR_NO_SUCH_PRIVILEGE: errno.EACCES,
102 winerror.ERROR_OPEN_FAILED: errno.EIO,
102 winerror.ERROR_OPEN_FAILED: errno.EIO,
103 winerror.ERROR_OPEN_FILES: errno.EBUSY,
103 winerror.ERROR_OPEN_FILES: errno.EBUSY,
104 winerror.ERROR_OPERATION_ABORTED: errno.EINTR,
104 winerror.ERROR_OPERATION_ABORTED: errno.EINTR,
105 winerror.ERROR_OUTOFMEMORY: errno.ENOMEM,
105 winerror.ERROR_OUTOFMEMORY: errno.ENOMEM,
106 winerror.ERROR_PASSWORD_EXPIRED: errno.EACCES,
106 winerror.ERROR_PASSWORD_EXPIRED: errno.EACCES,
107 winerror.ERROR_PATH_BUSY: errno.EBUSY,
107 winerror.ERROR_PATH_BUSY: errno.EBUSY,
108 winerror.ERROR_PATH_NOT_FOUND: errno.ENOENT,
108 winerror.ERROR_PATH_NOT_FOUND: errno.ENOENT,
109 winerror.ERROR_PIPE_BUSY: errno.EBUSY,
109 winerror.ERROR_PIPE_BUSY: errno.EBUSY,
110 winerror.ERROR_PIPE_CONNECTED: errno.EPIPE,
110 winerror.ERROR_PIPE_CONNECTED: errno.EPIPE,
111 winerror.ERROR_PIPE_LISTENING: errno.EPIPE,
111 winerror.ERROR_PIPE_LISTENING: errno.EPIPE,
112 winerror.ERROR_PIPE_NOT_CONNECTED: errno.EPIPE,
112 winerror.ERROR_PIPE_NOT_CONNECTED: errno.EPIPE,
113 winerror.ERROR_PRIVILEGE_NOT_HELD: errno.EACCES,
113 winerror.ERROR_PRIVILEGE_NOT_HELD: errno.EACCES,
114 winerror.ERROR_READ_FAULT: errno.EIO,
114 winerror.ERROR_READ_FAULT: errno.EIO,
115 winerror.ERROR_SEEK: errno.EIO,
115 winerror.ERROR_SEEK: errno.EIO,
116 winerror.ERROR_SEEK_ON_DEVICE: errno.ESPIPE,
116 winerror.ERROR_SEEK_ON_DEVICE: errno.ESPIPE,
117 winerror.ERROR_SHARING_BUFFER_EXCEEDED: errno.ENFILE,
117 winerror.ERROR_SHARING_BUFFER_EXCEEDED: errno.ENFILE,
118 winerror.ERROR_SHARING_VIOLATION: errno.EACCES,
118 winerror.ERROR_SHARING_VIOLATION: errno.EACCES,
119 winerror.ERROR_STACK_OVERFLOW: errno.ENOMEM,
119 winerror.ERROR_STACK_OVERFLOW: errno.ENOMEM,
120 winerror.ERROR_SWAPERROR: errno.ENOENT,
120 winerror.ERROR_SWAPERROR: errno.ENOENT,
121 winerror.ERROR_TOO_MANY_MODULES: errno.EMFILE,
121 winerror.ERROR_TOO_MANY_MODULES: errno.EMFILE,
122 winerror.ERROR_TOO_MANY_OPEN_FILES: errno.EMFILE,
122 winerror.ERROR_TOO_MANY_OPEN_FILES: errno.EMFILE,
123 winerror.ERROR_UNRECOGNIZED_MEDIA: errno.ENXIO,
123 winerror.ERROR_UNRECOGNIZED_MEDIA: errno.ENXIO,
124 winerror.ERROR_UNRECOGNIZED_VOLUME: errno.ENODEV,
124 winerror.ERROR_UNRECOGNIZED_VOLUME: errno.ENODEV,
125 winerror.ERROR_WAIT_NO_CHILDREN: errno.ECHILD,
125 winerror.ERROR_WAIT_NO_CHILDREN: errno.ECHILD,
126 winerror.ERROR_WRITE_FAULT: errno.EIO,
126 winerror.ERROR_WRITE_FAULT: errno.EIO,
127 winerror.ERROR_WRITE_PROTECT: errno.EROFS,
127 winerror.ERROR_WRITE_PROTECT: errno.EROFS,
128 }
128 }
129
129
130 def __init__(self, err):
130 def __init__(self, err):
131 self.win_errno, self.win_function, self.win_strerror = err
131 self.win_errno, self.win_function, self.win_strerror = err
132 if self.win_strerror.endswith('.'):
132 if self.win_strerror.endswith('.'):
133 self.win_strerror = self.win_strerror[:-1]
133 self.win_strerror = self.win_strerror[:-1]
134
134
135 class WinIOError(WinError, IOError):
135 class WinIOError(WinError, IOError):
136 def __init__(self, err, filename=None):
136 def __init__(self, err, filename=None):
137 WinError.__init__(self, err)
137 WinError.__init__(self, err)
138 IOError.__init__(self, self.winerror_map.get(self.win_errno, 0),
138 IOError.__init__(self, self.winerror_map.get(self.win_errno, 0),
139 self.win_strerror)
139 self.win_strerror)
140 self.filename = filename
140 self.filename = filename
141
141
142 class WinOSError(WinError, OSError):
142 class WinOSError(WinError, OSError):
143 def __init__(self, err):
143 def __init__(self, err):
144 WinError.__init__(self, err)
144 WinError.__init__(self, err)
145 OSError.__init__(self, self.winerror_map.get(self.win_errno, 0),
145 OSError.__init__(self, self.winerror_map.get(self.win_errno, 0),
146 self.win_strerror)
146 self.win_strerror)
147
147
148 def os_link(src, dst):
148 def os_link(src, dst):
149 # NB will only succeed on NTFS
149 # NB will only succeed on NTFS
150 try:
150 try:
151 win32file.CreateHardLink(dst, src)
151 win32file.CreateHardLink(dst, src)
152 except pywintypes.error, details:
152 except pywintypes.error, details:
153 raise WinOSError(details)
153 raise WinOSError(details)
154
154
155 def nlinks(pathname):
155 def nlinks(pathname):
156 """Return number of hardlinks for the given file."""
156 """Return number of hardlinks for the given file."""
157 try:
157 try:
158 fh = win32file.CreateFile(pathname,
158 fh = win32file.CreateFile(pathname,
159 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
159 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
160 None, win32file.OPEN_EXISTING, 0, None)
160 None, win32file.OPEN_EXISTING, 0, None)
161 res = win32file.GetFileInformationByHandle(fh)
161 res = win32file.GetFileInformationByHandle(fh)
162 fh.Close()
162 fh.Close()
163 return res[7]
163 return res[7]
164 except pywintypes.error:
164 except pywintypes.error:
165 return os.stat(pathname).st_nlink
165 return os.stat(pathname).st_nlink
166
166
167 def testpid(pid):
167 def testpid(pid):
168 '''return True if pid is still running or unable to
168 '''return True if pid is still running or unable to
169 determine, False otherwise'''
169 determine, False otherwise'''
170 try:
170 try:
171 handle = win32api.OpenProcess(
171 handle = win32api.OpenProcess(
172 win32con.PROCESS_QUERY_INFORMATION, False, pid)
172 win32con.PROCESS_QUERY_INFORMATION, False, pid)
173 if handle:
173 if handle:
174 status = win32process.GetExitCodeProcess(handle)
174 status = win32process.GetExitCodeProcess(handle)
175 return status == win32con.STILL_ACTIVE
175 return status == win32con.STILL_ACTIVE
176 except pywintypes.error, details:
176 except pywintypes.error, details:
177 return details[0] != winerror.ERROR_INVALID_PARAMETER
177 return details[0] != winerror.ERROR_INVALID_PARAMETER
178 return True
178 return True
179
179
180 def system_rcpath_win32():
180 def system_rcpath_win32():
181 '''return default os-specific hgrc search path'''
181 '''return default os-specific hgrc search path'''
182 proc = win32api.GetCurrentProcess()
182 proc = win32api.GetCurrentProcess()
183 filename = win32process.GetModuleFileNameEx(proc, 0)
183 filename = win32process.GetModuleFileNameEx(proc, 0)
184 return [os.path.join(os.path.dirname(filename), 'mercurial.ini')]
184 return [os.path.join(os.path.dirname(filename), 'mercurial.ini')]
185
185
186 class posixfile(object):
186 class posixfile_nt(object):
187 '''file object with posix-like semantics. on windows, normal
187 '''file object with posix-like semantics. on windows, normal
188 files can not be deleted or renamed if they are open. must open
188 files can not be deleted or renamed if they are open. must open
189 with win32file.FILE_SHARE_DELETE. this flag does not exist on
189 with win32file.FILE_SHARE_DELETE. this flag does not exist on
190 windows <= nt.'''
190 windows < nt, so do not use this class there.'''
191
191
192 # tried to use win32file._open_osfhandle to pass fd to os.fdopen,
192 # tried to use win32file._open_osfhandle to pass fd to os.fdopen,
193 # but does not work at all. wrap win32 file api instead.
193 # but does not work at all. wrap win32 file api instead.
194
194
195 def __init__(self, name, mode='rb'):
195 def __init__(self, name, mode='rb'):
196 access = 0
196 access = 0
197 if 'r' in mode or '+' in mode:
197 if 'r' in mode or '+' in mode:
198 access |= win32file.GENERIC_READ
198 access |= win32file.GENERIC_READ
199 if 'w' in mode or 'a' in mode:
199 if 'w' in mode or 'a' in mode:
200 access |= win32file.GENERIC_WRITE
200 access |= win32file.GENERIC_WRITE
201 if 'r' in mode:
201 if 'r' in mode:
202 creation = win32file.OPEN_EXISTING
202 creation = win32file.OPEN_EXISTING
203 elif 'a' in mode:
203 elif 'a' in mode:
204 creation = win32file.OPEN_ALWAYS
204 creation = win32file.OPEN_ALWAYS
205 else:
205 else:
206 creation = win32file.CREATE_ALWAYS
206 creation = win32file.CREATE_ALWAYS
207 try:
207 try:
208 self.handle = win32file.CreateFile(name,
208 self.handle = win32file.CreateFile(name,
209 access,
209 access,
210 win32file.FILE_SHARE_READ |
210 win32file.FILE_SHARE_READ |
211 win32file.FILE_SHARE_WRITE |
211 win32file.FILE_SHARE_WRITE |
212 win32file.FILE_SHARE_DELETE,
212 win32file.FILE_SHARE_DELETE,
213 None,
213 None,
214 creation,
214 creation,
215 win32file.FILE_ATTRIBUTE_NORMAL,
215 win32file.FILE_ATTRIBUTE_NORMAL,
216 0)
216 0)
217 except pywintypes.error, err:
217 except pywintypes.error, err:
218 raise WinIOError(err, name)
218 raise WinIOError(err, name)
219 self.closed = False
219 self.closed = False
220 self.name = name
220 self.name = name
221 self.mode = mode
221 self.mode = mode
222
222
223 def __iter__(self):
223 def __iter__(self):
224 for line in self.read().splitlines(True):
224 for line in self.read().splitlines(True):
225 yield line
225 yield line
226
226
227 def read(self, count=-1):
227 def read(self, count=-1):
228 try:
228 try:
229 cs = cStringIO.StringIO()
229 cs = cStringIO.StringIO()
230 while count:
230 while count:
231 wincount = int(count)
231 wincount = int(count)
232 if wincount == -1:
232 if wincount == -1:
233 wincount = 1048576
233 wincount = 1048576
234 val, data = win32file.ReadFile(self.handle, wincount)
234 val, data = win32file.ReadFile(self.handle, wincount)
235 if not data: break
235 if not data: break
236 cs.write(data)
236 cs.write(data)
237 if count != -1:
237 if count != -1:
238 count -= len(data)
238 count -= len(data)
239 return cs.getvalue()
239 return cs.getvalue()
240 except pywintypes.error, err:
240 except pywintypes.error, err:
241 raise WinIOError(err)
241 raise WinIOError(err)
242
242
243 def write(self, data):
243 def write(self, data):
244 try:
244 try:
245 if 'a' in self.mode:
245 if 'a' in self.mode:
246 win32file.SetFilePointer(self.handle, 0, win32file.FILE_END)
246 win32file.SetFilePointer(self.handle, 0, win32file.FILE_END)
247 nwrit = 0
247 nwrit = 0
248 while nwrit < len(data):
248 while nwrit < len(data):
249 val, nwrit = win32file.WriteFile(self.handle, data)
249 val, nwrit = win32file.WriteFile(self.handle, data)
250 data = data[nwrit:]
250 data = data[nwrit:]
251 except pywintypes.error, err:
251 except pywintypes.error, err:
252 raise WinIOError(err)
252 raise WinIOError(err)
253
253
254 def seek(self, pos, whence=0):
254 def seek(self, pos, whence=0):
255 try:
255 try:
256 win32file.SetFilePointer(self.handle, int(pos), whence)
256 win32file.SetFilePointer(self.handle, int(pos), whence)
257 except pywintypes.error, err:
257 except pywintypes.error, err:
258 raise WinIOError(err)
258 raise WinIOError(err)
259
259
260 def tell(self):
260 def tell(self):
261 try:
261 try:
262 return win32file.SetFilePointer(self.handle, 0,
262 return win32file.SetFilePointer(self.handle, 0,
263 win32file.FILE_CURRENT)
263 win32file.FILE_CURRENT)
264 except pywintypes.error, err:
264 except pywintypes.error, err:
265 raise WinIOError(err)
265 raise WinIOError(err)
266
266
267 def close(self):
267 def close(self):
268 if not self.closed:
268 if not self.closed:
269 self.handle = None
269 self.handle = None
270 self.closed = True
270 self.closed = True
271
271
272 def flush(self):
272 def flush(self):
273 try:
273 try:
274 win32file.FlushFileBuffers(self.handle)
274 win32file.FlushFileBuffers(self.handle)
275 except pywintypes.error, err:
275 except pywintypes.error, err:
276 raise WinIOError(err)
276 raise WinIOError(err)
277
277
278 def truncate(self, pos=0):
278 def truncate(self, pos=0):
279 try:
279 try:
280 win32file.SetFilePointer(self.handle, int(pos),
280 win32file.SetFilePointer(self.handle, int(pos),
281 win32file.FILE_BEGIN)
281 win32file.FILE_BEGIN)
282 win32file.SetEndOfFile(self.handle)
282 win32file.SetEndOfFile(self.handle)
283 except pywintypes.error, err:
283 except pywintypes.error, err:
284 raise WinIOError(err)
284 raise WinIOError(err)
General Comments 0
You need to be logged in to leave comments. Login now