##// END OF EJS Templates
put license and copyright info into comment blocks
Martin Geisler -
r8226:8b2cd04a default
parent child Browse files
Show More
@@ -1,298 +1,296 b''
1 """
2 bundlerepo.py - repository class for viewing uncompressed bundles
3
4 This provides a read-only repository interface to bundles as if
5 they were part of the actual repository.
6
7 Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
8
9 This software may be used and distributed according to the terms of the
10 GNU General Public License version 2, incorporated herein by reference.
11 """
1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 #
3 # This provides a read-only repository interface to bundles as if
4 # they were part of the actual repository.
5 #
6 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
7 #
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2, incorporated herein by reference.
12 10
13 11 from node import nullid
14 12 from i18n import _
15 13 import changegroup, util, os, struct, bz2, zlib, tempfile, shutil, mdiff
16 14 import localrepo, changelog, manifest, filelog, revlog, error
17 15
18 16 class bundlerevlog(revlog.revlog):
19 17 def __init__(self, opener, indexfile, bundlefile,
20 18 linkmapper=None):
21 19 # How it works:
22 20 # to retrieve a revision, we need to know the offset of
23 21 # the revision in the bundlefile (an opened file).
24 22 #
25 23 # We store this offset in the index (start), to differentiate a
26 24 # rev in the bundle and from a rev in the revlog, we check
27 25 # len(index[r]). If the tuple is bigger than 7, it is a bundle
28 26 # (it is bigger since we store the node to which the delta is)
29 27 #
30 28 revlog.revlog.__init__(self, opener, indexfile)
31 29 self.bundlefile = bundlefile
32 30 self.basemap = {}
33 31 def chunkpositer():
34 32 for chunk in changegroup.chunkiter(bundlefile):
35 33 pos = bundlefile.tell()
36 34 yield chunk, pos - len(chunk)
37 35 n = len(self)
38 36 prev = None
39 37 for chunk, start in chunkpositer():
40 38 size = len(chunk)
41 39 if size < 80:
42 40 raise util.Abort(_("invalid changegroup"))
43 41 start += 80
44 42 size -= 80
45 43 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
46 44 if node in self.nodemap:
47 45 prev = node
48 46 continue
49 47 for p in (p1, p2):
50 48 if not p in self.nodemap:
51 49 raise error.LookupError(p1, self.indexfile,
52 50 _("unknown parent"))
53 51 if linkmapper is None:
54 52 link = n
55 53 else:
56 54 link = linkmapper(cs)
57 55
58 56 if not prev:
59 57 prev = p1
60 58 # start, size, full unc. size, base (unused), link, p1, p2, node
61 59 e = (revlog.offset_type(start, 0), size, -1, -1, link,
62 60 self.rev(p1), self.rev(p2), node)
63 61 self.basemap[n] = prev
64 62 self.index.insert(-1, e)
65 63 self.nodemap[node] = n
66 64 prev = node
67 65 n += 1
68 66
69 67 def bundle(self, rev):
70 68 """is rev from the bundle"""
71 69 if rev < 0:
72 70 return False
73 71 return rev in self.basemap
74 72 def bundlebase(self, rev): return self.basemap[rev]
75 73 def chunk(self, rev, df=None, cachelen=4096):
76 74 # Warning: in case of bundle, the diff is against bundlebase,
77 75 # not against rev - 1
78 76 # XXX: could use some caching
79 77 if not self.bundle(rev):
80 78 return revlog.revlog.chunk(self, rev, df)
81 79 self.bundlefile.seek(self.start(rev))
82 80 return self.bundlefile.read(self.length(rev))
83 81
84 82 def revdiff(self, rev1, rev2):
85 83 """return or calculate a delta between two revisions"""
86 84 if self.bundle(rev1) and self.bundle(rev2):
87 85 # hot path for bundle
88 86 revb = self.rev(self.bundlebase(rev2))
89 87 if revb == rev1:
90 88 return self.chunk(rev2)
91 89 elif not self.bundle(rev1) and not self.bundle(rev2):
92 90 return revlog.revlog.revdiff(self, rev1, rev2)
93 91
94 92 return mdiff.textdiff(self.revision(self.node(rev1)),
95 93 self.revision(self.node(rev2)))
96 94
97 95 def revision(self, node):
98 96 """return an uncompressed revision of a given"""
99 97 if node == nullid: return ""
100 98
101 99 text = None
102 100 chain = []
103 101 iter_node = node
104 102 rev = self.rev(iter_node)
105 103 # reconstruct the revision if it is from a changegroup
106 104 while self.bundle(rev):
107 105 if self._cache and self._cache[0] == iter_node:
108 106 text = self._cache[2]
109 107 break
110 108 chain.append(rev)
111 109 iter_node = self.bundlebase(rev)
112 110 rev = self.rev(iter_node)
113 111 if text is None:
114 112 text = revlog.revlog.revision(self, iter_node)
115 113
116 114 while chain:
117 115 delta = self.chunk(chain.pop())
118 116 text = mdiff.patches(text, [delta])
119 117
120 118 p1, p2 = self.parents(node)
121 119 if node != revlog.hash(text, p1, p2):
122 120 raise error.RevlogError(_("integrity check failed on %s:%d")
123 121 % (self.datafile, self.rev(node)))
124 122
125 123 self._cache = (node, self.rev(node), text)
126 124 return text
127 125
128 126 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
129 127 raise NotImplementedError
130 128 def addgroup(self, revs, linkmapper, transaction):
131 129 raise NotImplementedError
132 130 def strip(self, rev, minlink):
133 131 raise NotImplementedError
134 132 def checksize(self):
135 133 raise NotImplementedError
136 134
137 135 class bundlechangelog(bundlerevlog, changelog.changelog):
138 136 def __init__(self, opener, bundlefile):
139 137 changelog.changelog.__init__(self, opener)
140 138 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile)
141 139
142 140 class bundlemanifest(bundlerevlog, manifest.manifest):
143 141 def __init__(self, opener, bundlefile, linkmapper):
144 142 manifest.manifest.__init__(self, opener)
145 143 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
146 144 linkmapper)
147 145
148 146 class bundlefilelog(bundlerevlog, filelog.filelog):
149 147 def __init__(self, opener, path, bundlefile, linkmapper):
150 148 filelog.filelog.__init__(self, opener, path)
151 149 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
152 150 linkmapper)
153 151
154 152 class bundlerepository(localrepo.localrepository):
155 153 def __init__(self, ui, path, bundlename):
156 154 self._tempparent = None
157 155 try:
158 156 localrepo.localrepository.__init__(self, ui, path)
159 157 except error.RepoError:
160 158 self._tempparent = tempfile.mkdtemp()
161 159 localrepo.instance(ui,self._tempparent,1)
162 160 localrepo.localrepository.__init__(self, ui, self._tempparent)
163 161
164 162 if path:
165 163 self._url = 'bundle:' + path + '+' + bundlename
166 164 else:
167 165 self._url = 'bundle:' + bundlename
168 166
169 167 self.tempfile = None
170 168 self.bundlefile = open(bundlename, "rb")
171 169 header = self.bundlefile.read(6)
172 170 if not header.startswith("HG"):
173 171 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
174 172 elif not header.startswith("HG10"):
175 173 raise util.Abort(_("%s: unknown bundle version") % bundlename)
176 174 elif (header == "HG10BZ") or (header == "HG10GZ"):
177 175 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
178 176 suffix=".hg10un", dir=self.path)
179 177 self.tempfile = temp
180 178 fptemp = os.fdopen(fdtemp, 'wb')
181 179 def generator(f):
182 180 if header == "HG10BZ":
183 181 zd = bz2.BZ2Decompressor()
184 182 zd.decompress("BZ")
185 183 elif header == "HG10GZ":
186 184 zd = zlib.decompressobj()
187 185 for chunk in f:
188 186 yield zd.decompress(chunk)
189 187 gen = generator(util.filechunkiter(self.bundlefile, 4096))
190 188
191 189 try:
192 190 fptemp.write("HG10UN")
193 191 for chunk in gen:
194 192 fptemp.write(chunk)
195 193 finally:
196 194 fptemp.close()
197 195 self.bundlefile.close()
198 196
199 197 self.bundlefile = open(self.tempfile, "rb")
200 198 # seek right after the header
201 199 self.bundlefile.seek(6)
202 200 elif header == "HG10UN":
203 201 # nothing to do
204 202 pass
205 203 else:
206 204 raise util.Abort(_("%s: unknown bundle compression type")
207 205 % bundlename)
208 206 # dict with the mapping 'filename' -> position in the bundle
209 207 self.bundlefilespos = {}
210 208
211 209 def __getattr__(self, name):
212 210 if name == 'changelog':
213 211 self.changelog = bundlechangelog(self.sopener, self.bundlefile)
214 212 self.manstart = self.bundlefile.tell()
215 213 return self.changelog
216 214 elif name == 'manifest':
217 215 self.bundlefile.seek(self.manstart)
218 216 self.manifest = bundlemanifest(self.sopener, self.bundlefile,
219 217 self.changelog.rev)
220 218 self.filestart = self.bundlefile.tell()
221 219 return self.manifest
222 220 elif name == 'manstart':
223 221 self.changelog
224 222 return self.manstart
225 223 elif name == 'filestart':
226 224 self.manifest
227 225 return self.filestart
228 226 else:
229 227 return localrepo.localrepository.__getattr__(self, name)
230 228
231 229 def url(self):
232 230 return self._url
233 231
234 232 def file(self, f):
235 233 if not self.bundlefilespos:
236 234 self.bundlefile.seek(self.filestart)
237 235 while 1:
238 236 chunk = changegroup.getchunk(self.bundlefile)
239 237 if not chunk:
240 238 break
241 239 self.bundlefilespos[chunk] = self.bundlefile.tell()
242 240 for c in changegroup.chunkiter(self.bundlefile):
243 241 pass
244 242
245 243 if f[0] == '/':
246 244 f = f[1:]
247 245 if f in self.bundlefilespos:
248 246 self.bundlefile.seek(self.bundlefilespos[f])
249 247 return bundlefilelog(self.sopener, f, self.bundlefile,
250 248 self.changelog.rev)
251 249 else:
252 250 return filelog.filelog(self.sopener, f)
253 251
254 252 def close(self):
255 253 """Close assigned bundle file immediately."""
256 254 self.bundlefile.close()
257 255
258 256 def __del__(self):
259 257 bundlefile = getattr(self, 'bundlefile', None)
260 258 if bundlefile and not bundlefile.closed:
261 259 bundlefile.close()
262 260 tempfile = getattr(self, 'tempfile', None)
263 261 if tempfile is not None:
264 262 os.unlink(tempfile)
265 263 if self._tempparent:
266 264 shutil.rmtree(self._tempparent, True)
267 265
268 266 def cancopy(self):
269 267 return False
270 268
271 269 def getcwd(self):
272 270 return os.getcwd() # always outside the repo
273 271
274 272 def instance(ui, path, create):
275 273 if create:
276 274 raise util.Abort(_('cannot create new bundle repository'))
277 275 parentpath = ui.config("bundle", "mainreporoot", "")
278 276 if parentpath:
279 277 # Try to make the full path relative so we get a nice, short URL.
280 278 # In particular, we don't want temp dir names in test outputs.
281 279 cwd = os.getcwd()
282 280 if parentpath == cwd:
283 281 parentpath = ''
284 282 else:
285 283 cwd = os.path.join(cwd,'')
286 284 if parentpath.startswith(cwd):
287 285 parentpath = parentpath[len(cwd):]
288 286 path = util.drop_scheme('file', path)
289 287 if path.startswith('bundle:'):
290 288 path = util.drop_scheme('bundle', path)
291 289 s = path.split("+", 1)
292 290 if len(s) == 1:
293 291 repopath, bundlename = parentpath, s[0]
294 292 else:
295 293 repopath, bundlename = s
296 294 else:
297 295 repopath, bundlename = parentpath, path
298 296 return bundlerepository(ui, repopath, bundlename)
@@ -1,141 +1,139 b''
1 """
2 changegroup.py - Mercurial changegroup manipulation functions
3
4 Copyright 2006 Matt Mackall <mpm@selenic.com>
5
6 This software may be used and distributed according to the terms of the
7 GNU General Public License version 2, incorporated herein by reference.
8 """
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
9 7
10 8 from i18n import _
11 9 import struct, os, bz2, zlib, util, tempfile
12 10
13 11 def getchunk(source):
14 12 """get a chunk from a changegroup"""
15 13 d = source.read(4)
16 14 if not d:
17 15 return ""
18 16 l = struct.unpack(">l", d)[0]
19 17 if l <= 4:
20 18 return ""
21 19 d = source.read(l - 4)
22 20 if len(d) < l - 4:
23 21 raise util.Abort(_("premature EOF reading chunk"
24 22 " (got %d bytes, expected %d)")
25 23 % (len(d), l - 4))
26 24 return d
27 25
28 26 def chunkiter(source):
29 27 """iterate through the chunks in source"""
30 28 while 1:
31 29 c = getchunk(source)
32 30 if not c:
33 31 break
34 32 yield c
35 33
36 34 def chunkheader(length):
37 35 """build a changegroup chunk header"""
38 36 return struct.pack(">l", length + 4)
39 37
40 38 def closechunk():
41 39 return struct.pack(">l", 0)
42 40
43 41 class nocompress(object):
44 42 def compress(self, x):
45 43 return x
46 44 def flush(self):
47 45 return ""
48 46
49 47 bundletypes = {
50 48 "": ("", nocompress),
51 49 "HG10UN": ("HG10UN", nocompress),
52 50 "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()),
53 51 "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
54 52 }
55 53
56 54 # hgweb uses this list to communicate it's preferred type
57 55 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
58 56
59 57 def writebundle(cg, filename, bundletype):
60 58 """Write a bundle file and return its filename.
61 59
62 60 Existing files will not be overwritten.
63 61 If no filename is specified, a temporary file is created.
64 62 bz2 compression can be turned off.
65 63 The bundle file will be deleted in case of errors.
66 64 """
67 65
68 66 fh = None
69 67 cleanup = None
70 68 try:
71 69 if filename:
72 70 fh = open(filename, "wb")
73 71 else:
74 72 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
75 73 fh = os.fdopen(fd, "wb")
76 74 cleanup = filename
77 75
78 76 header, compressor = bundletypes[bundletype]
79 77 fh.write(header)
80 78 z = compressor()
81 79
82 80 # parse the changegroup data, otherwise we will block
83 81 # in case of sshrepo because we don't know the end of the stream
84 82
85 83 # an empty chunkiter is the end of the changegroup
86 84 # a changegroup has at least 2 chunkiters (changelog and manifest).
87 85 # after that, an empty chunkiter is the end of the changegroup
88 86 empty = False
89 87 count = 0
90 88 while not empty or count <= 2:
91 89 empty = True
92 90 count += 1
93 91 for chunk in chunkiter(cg):
94 92 empty = False
95 93 fh.write(z.compress(chunkheader(len(chunk))))
96 94 pos = 0
97 95 while pos < len(chunk):
98 96 next = pos + 2**20
99 97 fh.write(z.compress(chunk[pos:next]))
100 98 pos = next
101 99 fh.write(z.compress(closechunk()))
102 100 fh.write(z.flush())
103 101 cleanup = None
104 102 return filename
105 103 finally:
106 104 if fh is not None:
107 105 fh.close()
108 106 if cleanup is not None:
109 107 os.unlink(cleanup)
110 108
111 109 def unbundle(header, fh):
112 110 if header == 'HG10UN':
113 111 return fh
114 112 elif not header.startswith('HG'):
115 113 # old client with uncompressed bundle
116 114 def generator(f):
117 115 yield header
118 116 for chunk in f:
119 117 yield chunk
120 118 elif header == 'HG10GZ':
121 119 def generator(f):
122 120 zd = zlib.decompressobj()
123 121 for chunk in f:
124 122 yield zd.decompress(chunk)
125 123 elif header == 'HG10BZ':
126 124 def generator(f):
127 125 zd = bz2.BZ2Decompressor()
128 126 zd.decompress("BZ")
129 127 for chunk in util.filechunkiter(f, 4096):
130 128 yield zd.decompress(chunk)
131 129 return util.chunkbuffer(generator(fh))
132 130
133 131 def readbundle(fh, fname):
134 132 header = fh.read(6)
135 133 if not header.startswith('HG'):
136 134 raise util.Abort(_('%s: not a Mercurial bundle file') % fname)
137 135 if not header.startswith('HG10'):
138 136 raise util.Abort(_('%s: unknown bundle version') % fname)
139 137 elif header not in bundletypes:
140 138 raise util.Abort(_('%s: unknown bundle compression type') % fname)
141 139 return unbundle(header, fh)
@@ -1,585 +1,583 b''
1 """
2 dirstate.py - working directory tracking for mercurial
3
4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5
6 This software may be used and distributed according to the terms of the
7 GNU General Public License version 2, incorporated herein by reference.
8 """
1 # dirstate.py - working directory tracking for mercurial
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
9 7
10 8 from node import nullid
11 9 from i18n import _
12 10 import struct, os, stat, util, errno, ignore
13 11 import cStringIO, osutil, sys, parsers
14 12
15 13 _unknown = ('?', 0, 0, 0)
16 14 _format = ">cllll"
17 15
18 16 def _finddirs(path):
19 17 pos = path.rfind('/')
20 18 while pos != -1:
21 19 yield path[:pos]
22 20 pos = path.rfind('/', 0, pos)
23 21
24 22 def _incdirs(dirs, path):
25 23 for base in _finddirs(path):
26 24 if base in dirs:
27 25 dirs[base] += 1
28 26 return
29 27 dirs[base] = 1
30 28
31 29 def _decdirs(dirs, path):
32 30 for base in _finddirs(path):
33 31 if dirs[base] > 1:
34 32 dirs[base] -= 1
35 33 return
36 34 del dirs[base]
37 35
38 36 class dirstate(object):
39 37
40 38 def __init__(self, opener, ui, root):
41 39 self._opener = opener
42 40 self._root = root
43 41 self._rootdir = os.path.join(root, '')
44 42 self._dirty = False
45 43 self._dirtypl = False
46 44 self._ui = ui
47 45
48 46 def __getattr__(self, name):
49 47 if name == '_map':
50 48 self._read()
51 49 return self._map
52 50 elif name == '_copymap':
53 51 self._read()
54 52 return self._copymap
55 53 elif name == '_foldmap':
56 54 _foldmap = {}
57 55 for name in self._map:
58 56 norm = os.path.normcase(name)
59 57 _foldmap[norm] = name
60 58 self._foldmap = _foldmap
61 59 return self._foldmap
62 60 elif name == '_branch':
63 61 try:
64 62 self._branch = (self._opener("branch").read().strip()
65 63 or "default")
66 64 except IOError:
67 65 self._branch = "default"
68 66 return self._branch
69 67 elif name == '_pl':
70 68 self._pl = [nullid, nullid]
71 69 try:
72 70 st = self._opener("dirstate").read(40)
73 71 if len(st) == 40:
74 72 self._pl = st[:20], st[20:40]
75 73 except IOError, err:
76 74 if err.errno != errno.ENOENT: raise
77 75 return self._pl
78 76 elif name == '_dirs':
79 77 dirs = {}
80 78 for f,s in self._map.iteritems():
81 79 if s[0] != 'r':
82 80 _incdirs(dirs, f)
83 81 self._dirs = dirs
84 82 return self._dirs
85 83 elif name == '_ignore':
86 84 files = [self._join('.hgignore')]
87 85 for name, path in self._ui.configitems("ui"):
88 86 if name == 'ignore' or name.startswith('ignore.'):
89 87 files.append(os.path.expanduser(path))
90 88 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
91 89 return self._ignore
92 90 elif name == '_slash':
93 91 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
94 92 return self._slash
95 93 elif name == '_checklink':
96 94 self._checklink = util.checklink(self._root)
97 95 return self._checklink
98 96 elif name == '_checkexec':
99 97 self._checkexec = util.checkexec(self._root)
100 98 return self._checkexec
101 99 elif name == '_checkcase':
102 100 self._checkcase = not util.checkcase(self._join('.hg'))
103 101 return self._checkcase
104 102 elif name == 'normalize':
105 103 if self._checkcase:
106 104 self.normalize = self._normalize
107 105 else:
108 106 self.normalize = lambda x, y=False: x
109 107 return self.normalize
110 108 else:
111 109 raise AttributeError(name)
112 110
113 111 def _join(self, f):
114 112 # much faster than os.path.join()
115 113 # it's safe because f is always a relative path
116 114 return self._rootdir + f
117 115
118 116 def flagfunc(self, fallback):
119 117 if self._checklink:
120 118 if self._checkexec:
121 119 def f(x):
122 120 p = self._join(x)
123 121 if os.path.islink(p):
124 122 return 'l'
125 123 if util.is_exec(p):
126 124 return 'x'
127 125 return ''
128 126 return f
129 127 def f(x):
130 128 if os.path.islink(self._join(x)):
131 129 return 'l'
132 130 if 'x' in fallback(x):
133 131 return 'x'
134 132 return ''
135 133 return f
136 134 if self._checkexec:
137 135 def f(x):
138 136 if 'l' in fallback(x):
139 137 return 'l'
140 138 if util.is_exec(self._join(x)):
141 139 return 'x'
142 140 return ''
143 141 return f
144 142 return fallback
145 143
146 144 def getcwd(self):
147 145 cwd = os.getcwd()
148 146 if cwd == self._root: return ''
149 147 # self._root ends with a path separator if self._root is '/' or 'C:\'
150 148 rootsep = self._root
151 149 if not util.endswithsep(rootsep):
152 150 rootsep += os.sep
153 151 if cwd.startswith(rootsep):
154 152 return cwd[len(rootsep):]
155 153 else:
156 154 # we're outside the repo. return an absolute path.
157 155 return cwd
158 156
159 157 def pathto(self, f, cwd=None):
160 158 if cwd is None:
161 159 cwd = self.getcwd()
162 160 path = util.pathto(self._root, cwd, f)
163 161 if self._slash:
164 162 return util.normpath(path)
165 163 return path
166 164
167 165 def __getitem__(self, key):
168 166 ''' current states:
169 167 n normal
170 168 m needs merging
171 169 r marked for removal
172 170 a marked for addition
173 171 ? not tracked'''
174 172 return self._map.get(key, ("?",))[0]
175 173
176 174 def __contains__(self, key):
177 175 return key in self._map
178 176
179 177 def __iter__(self):
180 178 for x in sorted(self._map):
181 179 yield x
182 180
183 181 def parents(self):
184 182 return self._pl
185 183
186 184 def branch(self):
187 185 return self._branch
188 186
189 187 def setparents(self, p1, p2=nullid):
190 188 self._dirty = self._dirtypl = True
191 189 self._pl = p1, p2
192 190
193 191 def setbranch(self, branch):
194 192 self._branch = branch
195 193 self._opener("branch", "w").write(branch + '\n')
196 194
197 195 def _read(self):
198 196 self._map = {}
199 197 self._copymap = {}
200 198 try:
201 199 st = self._opener("dirstate").read()
202 200 except IOError, err:
203 201 if err.errno != errno.ENOENT: raise
204 202 return
205 203 if not st:
206 204 return
207 205
208 206 p = parsers.parse_dirstate(self._map, self._copymap, st)
209 207 if not self._dirtypl:
210 208 self._pl = p
211 209
212 210 def invalidate(self):
213 211 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
214 212 if a in self.__dict__:
215 213 delattr(self, a)
216 214 self._dirty = False
217 215
218 216 def copy(self, source, dest):
219 217 """Mark dest as a copy of source. Unmark dest if source is None.
220 218 """
221 219 if source == dest:
222 220 return
223 221 self._dirty = True
224 222 if source is not None:
225 223 self._copymap[dest] = source
226 224 elif dest in self._copymap:
227 225 del self._copymap[dest]
228 226
229 227 def copied(self, file):
230 228 return self._copymap.get(file, None)
231 229
232 230 def copies(self):
233 231 return self._copymap
234 232
235 233 def _droppath(self, f):
236 234 if self[f] not in "?r" and "_dirs" in self.__dict__:
237 235 _decdirs(self._dirs, f)
238 236
239 237 def _addpath(self, f, check=False):
240 238 oldstate = self[f]
241 239 if check or oldstate == "r":
242 240 if '\r' in f or '\n' in f:
243 241 raise util.Abort(
244 242 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
245 243 if f in self._dirs:
246 244 raise util.Abort(_('directory %r already in dirstate') % f)
247 245 # shadows
248 246 for d in _finddirs(f):
249 247 if d in self._dirs:
250 248 break
251 249 if d in self._map and self[d] != 'r':
252 250 raise util.Abort(
253 251 _('file %r in dirstate clashes with %r') % (d, f))
254 252 if oldstate in "?r" and "_dirs" in self.__dict__:
255 253 _incdirs(self._dirs, f)
256 254
257 255 def normal(self, f):
258 256 'mark a file normal and clean'
259 257 self._dirty = True
260 258 self._addpath(f)
261 259 s = os.lstat(self._join(f))
262 260 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
263 261 if f in self._copymap:
264 262 del self._copymap[f]
265 263
266 264 def normallookup(self, f):
267 265 'mark a file normal, but possibly dirty'
268 266 if self._pl[1] != nullid and f in self._map:
269 267 # if there is a merge going on and the file was either
270 268 # in state 'm' or dirty before being removed, restore that state.
271 269 entry = self._map[f]
272 270 if entry[0] == 'r' and entry[2] in (-1, -2):
273 271 source = self._copymap.get(f)
274 272 if entry[2] == -1:
275 273 self.merge(f)
276 274 elif entry[2] == -2:
277 275 self.normaldirty(f)
278 276 if source:
279 277 self.copy(source, f)
280 278 return
281 279 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
282 280 return
283 281 self._dirty = True
284 282 self._addpath(f)
285 283 self._map[f] = ('n', 0, -1, -1)
286 284 if f in self._copymap:
287 285 del self._copymap[f]
288 286
289 287 def normaldirty(self, f):
290 288 'mark a file normal, but dirty'
291 289 self._dirty = True
292 290 self._addpath(f)
293 291 self._map[f] = ('n', 0, -2, -1)
294 292 if f in self._copymap:
295 293 del self._copymap[f]
296 294
297 295 def add(self, f):
298 296 'mark a file added'
299 297 self._dirty = True
300 298 self._addpath(f, True)
301 299 self._map[f] = ('a', 0, -1, -1)
302 300 if f in self._copymap:
303 301 del self._copymap[f]
304 302
305 303 def remove(self, f):
306 304 'mark a file removed'
307 305 self._dirty = True
308 306 self._droppath(f)
309 307 size = 0
310 308 if self._pl[1] != nullid and f in self._map:
311 309 entry = self._map[f]
312 310 if entry[0] == 'm':
313 311 size = -1
314 312 elif entry[0] == 'n' and entry[2] == -2:
315 313 size = -2
316 314 self._map[f] = ('r', 0, size, 0)
317 315 if size == 0 and f in self._copymap:
318 316 del self._copymap[f]
319 317
320 318 def merge(self, f):
321 319 'mark a file merged'
322 320 self._dirty = True
323 321 s = os.lstat(self._join(f))
324 322 self._addpath(f)
325 323 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
326 324 if f in self._copymap:
327 325 del self._copymap[f]
328 326
329 327 def forget(self, f):
330 328 'forget a file'
331 329 self._dirty = True
332 330 try:
333 331 self._droppath(f)
334 332 del self._map[f]
335 333 except KeyError:
336 334 self._ui.warn(_("not in dirstate: %s\n") % f)
337 335
338 336 def _normalize(self, path, knownpath=False):
339 337 norm_path = os.path.normcase(path)
340 338 fold_path = self._foldmap.get(norm_path, None)
341 339 if fold_path is None:
342 340 if knownpath or not os.path.exists(os.path.join(self._root, path)):
343 341 fold_path = path
344 342 else:
345 343 fold_path = self._foldmap.setdefault(norm_path,
346 344 util.fspath(path, self._root))
347 345 return fold_path
348 346
349 347 def clear(self):
350 348 self._map = {}
351 349 if "_dirs" in self.__dict__:
352 350 delattr(self, "_dirs");
353 351 self._copymap = {}
354 352 self._pl = [nullid, nullid]
355 353 self._dirty = True
356 354
357 355 def rebuild(self, parent, files):
358 356 self.clear()
359 357 for f in files:
360 358 if 'x' in files.flags(f):
361 359 self._map[f] = ('n', 0777, -1, 0)
362 360 else:
363 361 self._map[f] = ('n', 0666, -1, 0)
364 362 self._pl = (parent, nullid)
365 363 self._dirty = True
366 364
367 365 def write(self):
368 366 if not self._dirty:
369 367 return
370 368 st = self._opener("dirstate", "w", atomictemp=True)
371 369
372 370 try:
373 371 gran = int(self._ui.config('dirstate', 'granularity', 1))
374 372 except ValueError:
375 373 gran = 1
376 374 limit = sys.maxint
377 375 if gran > 0:
378 376 limit = util.fstat(st).st_mtime - gran
379 377
380 378 cs = cStringIO.StringIO()
381 379 copymap = self._copymap
382 380 pack = struct.pack
383 381 write = cs.write
384 382 write("".join(self._pl))
385 383 for f, e in self._map.iteritems():
386 384 if f in copymap:
387 385 f = "%s\0%s" % (f, copymap[f])
388 386 if e[3] > limit and e[0] == 'n':
389 387 e = (e[0], 0, -1, -1)
390 388 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
391 389 write(e)
392 390 write(f)
393 391 st.write(cs.getvalue())
394 392 st.rename()
395 393 self._dirty = self._dirtypl = False
396 394
397 395 def _dirignore(self, f):
398 396 if f == '.':
399 397 return False
400 398 if self._ignore(f):
401 399 return True
402 400 for p in _finddirs(f):
403 401 if self._ignore(p):
404 402 return True
405 403 return False
406 404
407 405 def walk(self, match, unknown, ignored):
408 406 '''
409 407 walk recursively through the directory tree, finding all files
410 408 matched by the match function
411 409
412 410 results are yielded in a tuple (filename, stat), where stat
413 411 and st is the stat result if the file was found in the directory.
414 412 '''
415 413
416 414 def fwarn(f, msg):
417 415 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
418 416 return False
419 417 badfn = fwarn
420 418 if hasattr(match, 'bad'):
421 419 badfn = match.bad
422 420
423 421 def badtype(f, mode):
424 422 kind = 'unknown'
425 423 if stat.S_ISCHR(mode): kind = _('character device')
426 424 elif stat.S_ISBLK(mode): kind = _('block device')
427 425 elif stat.S_ISFIFO(mode): kind = _('fifo')
428 426 elif stat.S_ISSOCK(mode): kind = _('socket')
429 427 elif stat.S_ISDIR(mode): kind = _('directory')
430 428 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
431 429 % (self.pathto(f), kind))
432 430
433 431 ignore = self._ignore
434 432 dirignore = self._dirignore
435 433 if ignored:
436 434 ignore = util.never
437 435 dirignore = util.never
438 436 elif not unknown:
439 437 # if unknown and ignored are False, skip step 2
440 438 ignore = util.always
441 439 dirignore = util.always
442 440
443 441 matchfn = match.matchfn
444 442 dmap = self._map
445 443 normpath = util.normpath
446 444 normalize = self.normalize
447 445 listdir = osutil.listdir
448 446 lstat = os.lstat
449 447 getkind = stat.S_IFMT
450 448 dirkind = stat.S_IFDIR
451 449 regkind = stat.S_IFREG
452 450 lnkkind = stat.S_IFLNK
453 451 join = self._join
454 452 work = []
455 453 wadd = work.append
456 454
457 455 files = set(match.files())
458 456 if not files or '.' in files:
459 457 files = ['']
460 458 results = {'.hg': None}
461 459
462 460 # step 1: find all explicit files
463 461 for ff in sorted(files):
464 462 nf = normalize(normpath(ff))
465 463 if nf in results:
466 464 continue
467 465
468 466 try:
469 467 st = lstat(join(nf))
470 468 kind = getkind(st.st_mode)
471 469 if kind == dirkind:
472 470 if not dirignore(nf):
473 471 wadd(nf)
474 472 elif kind == regkind or kind == lnkkind:
475 473 results[nf] = st
476 474 else:
477 475 badtype(ff, kind)
478 476 if nf in dmap:
479 477 results[nf] = None
480 478 except OSError, inst:
481 479 keep = False
482 480 prefix = nf + "/"
483 481 for fn in dmap:
484 482 if nf == fn or fn.startswith(prefix):
485 483 keep = True
486 484 break
487 485 if not keep:
488 486 if inst.errno != errno.ENOENT:
489 487 fwarn(ff, inst.strerror)
490 488 elif badfn(ff, inst.strerror):
491 489 if (nf in dmap or not ignore(nf)) and matchfn(nf):
492 490 results[nf] = None
493 491
494 492 # step 2: visit subdirectories
495 493 while work:
496 494 nd = work.pop()
497 495 if hasattr(match, 'dir'):
498 496 match.dir(nd)
499 497 skip = None
500 498 if nd == '.':
501 499 nd = ''
502 500 else:
503 501 skip = '.hg'
504 502 try:
505 503 entries = listdir(join(nd), stat=True, skip=skip)
506 504 except OSError, inst:
507 505 if inst.errno == errno.EACCES:
508 506 fwarn(nd, inst.strerror)
509 507 continue
510 508 raise
511 509 for f, kind, st in entries:
512 510 nf = normalize(nd and (nd + "/" + f) or f, True)
513 511 if nf not in results:
514 512 if kind == dirkind:
515 513 if not ignore(nf):
516 514 wadd(nf)
517 515 if nf in dmap and matchfn(nf):
518 516 results[nf] = None
519 517 elif kind == regkind or kind == lnkkind:
520 518 if nf in dmap:
521 519 if matchfn(nf):
522 520 results[nf] = st
523 521 elif matchfn(nf) and not ignore(nf):
524 522 results[nf] = st
525 523 elif nf in dmap and matchfn(nf):
526 524 results[nf] = None
527 525
528 526 # step 3: report unseen items in the dmap hash
529 527 visit = sorted([f for f in dmap if f not in results and match(f)])
530 528 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
531 529 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
532 530 st = None
533 531 results[nf] = st
534 532
535 533 del results['.hg']
536 534 return results
537 535
538 536 def status(self, match, ignored, clean, unknown):
539 537 listignored, listclean, listunknown = ignored, clean, unknown
540 538 lookup, modified, added, unknown, ignored = [], [], [], [], []
541 539 removed, deleted, clean = [], [], []
542 540
543 541 dmap = self._map
544 542 ladd = lookup.append
545 543 madd = modified.append
546 544 aadd = added.append
547 545 uadd = unknown.append
548 546 iadd = ignored.append
549 547 radd = removed.append
550 548 dadd = deleted.append
551 549 cadd = clean.append
552 550
553 551 for fn, st in self.walk(match, listunknown, listignored).iteritems():
554 552 if fn not in dmap:
555 553 if (listignored or match.exact(fn)) and self._dirignore(fn):
556 554 if listignored:
557 555 iadd(fn)
558 556 elif listunknown:
559 557 uadd(fn)
560 558 continue
561 559
562 560 state, mode, size, time = dmap[fn]
563 561
564 562 if not st and state in "nma":
565 563 dadd(fn)
566 564 elif state == 'n':
567 565 if (size >= 0 and
568 566 (size != st.st_size
569 567 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
570 568 or size == -2
571 569 or fn in self._copymap):
572 570 madd(fn)
573 571 elif time != int(st.st_mtime):
574 572 ladd(fn)
575 573 elif listclean:
576 574 cadd(fn)
577 575 elif state == 'm':
578 576 madd(fn)
579 577 elif state == 'a':
580 578 aadd(fn)
581 579 elif state == 'r':
582 580 radd(fn)
583 581
584 582 return (lookup, modified, added, removed, deleted, unknown, ignored,
585 583 clean)
@@ -1,76 +1,74 b''
1 """
2 encoding.py - character transcoding support for Mercurial
3
4 Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
5
6 This software may be used and distributed according to the terms of the
7 GNU General Public License version 2, incorporated herein by reference.
8 """
1 # encoding.py - character transcoding support for Mercurial
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
9 7
10 8 import sys, unicodedata, locale, os, error
11 9
12 10 _encodingfixup = {'646': 'ascii', 'ANSI_X3.4-1968': 'ascii'}
13 11
14 12 try:
15 13 encoding = os.environ.get("HGENCODING")
16 14 if sys.platform == 'darwin' and not encoding:
17 15 # On darwin, getpreferredencoding ignores the locale environment and
18 16 # always returns mac-roman. We override this if the environment is
19 17 # not C (has been customized by the user).
20 18 locale.setlocale(locale.LC_CTYPE, '')
21 19 encoding = locale.getlocale()[1]
22 20 if not encoding:
23 21 encoding = locale.getpreferredencoding() or 'ascii'
24 22 encoding = _encodingfixup.get(encoding, encoding)
25 23 except locale.Error:
26 24 encoding = 'ascii'
27 25 encodingmode = os.environ.get("HGENCODINGMODE", "strict")
28 26 fallbackencoding = 'ISO-8859-1'
29 27
30 28 def tolocal(s):
31 29 """
32 30 Convert a string from internal UTF-8 to local encoding
33 31
34 32 All internal strings should be UTF-8 but some repos before the
35 33 implementation of locale support may contain latin1 or possibly
36 34 other character sets. We attempt to decode everything strictly
37 35 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
38 36 replace unknown characters.
39 37 """
40 38 for e in ('UTF-8', fallbackencoding):
41 39 try:
42 40 u = s.decode(e) # attempt strict decoding
43 41 return u.encode(encoding, "replace")
44 42 except LookupError, k:
45 43 raise error.Abort("%s, please check your locale settings" % k)
46 44 except UnicodeDecodeError:
47 45 pass
48 46 u = s.decode("utf-8", "replace") # last ditch
49 47 return u.encode(encoding, "replace")
50 48
51 49 def fromlocal(s):
52 50 """
53 51 Convert a string from the local character encoding to UTF-8
54 52
55 53 We attempt to decode strings using the encoding mode set by
56 54 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
57 55 characters will cause an error message. Other modes include
58 56 'replace', which replaces unknown characters with a special
59 57 Unicode character, and 'ignore', which drops the character.
60 58 """
61 59 try:
62 60 return s.decode(encoding, encodingmode).encode("utf-8")
63 61 except UnicodeDecodeError, inst:
64 62 sub = s[max(0, inst.start-10):inst.start+10]
65 63 raise error.Abort("decoding near '%s': %s!" % (sub, inst))
66 64 except LookupError, k:
67 65 raise error.Abort("%s, please check your locale settings" % k)
68 66
69 67 def colwidth(s):
70 68 "Find the column width of a UTF-8 string for display"
71 69 d = s.decode(encoding, 'replace')
72 70 if hasattr(unicodedata, 'east_asian_width'):
73 71 w = unicodedata.east_asian_width
74 72 return sum([w(c) in 'WF' and 2 or 1 for c in d])
75 73 return len(d)
76 74
@@ -1,70 +1,68 b''
1 """
2 error.py - Mercurial exceptions
3
4 This allows us to catch exceptions at higher levels without forcing imports
5
6 Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
7
8 This software may be used and distributed according to the terms of the
9 GNU General Public License version 2, incorporated herein by reference.
10 """
1 # error.py - Mercurial exceptions
2 #
3 # This allows us to catch exceptions at higher levels without forcing imports
4 #
5 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
6 #
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2, incorporated herein by reference.
11 9
12 10 # Do not import anything here, please
13 11
14 12 class RevlogError(Exception):
15 13 pass
16 14
17 15 class LookupError(RevlogError, KeyError):
18 16 def __init__(self, name, index, message):
19 17 self.name = name
20 18 if isinstance(name, str) and len(name) == 20:
21 19 from node import short
22 20 name = short(name)
23 21 RevlogError.__init__(self, '%s@%s: %s' % (index, name, message))
24 22
25 23 def __str__(self):
26 24 return RevlogError.__str__(self)
27 25
28 26 class ParseError(Exception):
29 27 """Exception raised on errors in parsing the command line."""
30 28
31 29 class ConfigError(Exception):
32 30 'Exception raised when parsing config files'
33 31
34 32 class RepoError(Exception):
35 33 pass
36 34
37 35 class CapabilityError(RepoError):
38 36 pass
39 37
40 38 class LockError(IOError):
41 39 def __init__(self, errno, strerror, filename, desc):
42 40 IOError.__init__(self, errno, strerror, filename)
43 41 self.desc = desc
44 42
45 43 class LockHeld(LockError):
46 44 def __init__(self, errno, filename, desc, locker):
47 45 LockError.__init__(self, errno, 'Lock held', filename, desc)
48 46 self.locker = locker
49 47
50 48 class LockUnavailable(LockError):
51 49 pass
52 50
53 51 class ResponseError(Exception):
54 52 """Raised to print an error with part of output and exit."""
55 53
56 54 class UnknownCommand(Exception):
57 55 """Exception raised if command is not in the command table."""
58 56
59 57 class AmbiguousCommand(Exception):
60 58 """Exception raised if command shortcut matches more than one command."""
61 59
62 60 # derived from KeyboardInterrupt to simplify some breakout code
63 61 class SignalInterrupt(KeyboardInterrupt):
64 62 """Exception raised on SIGTERM and SIGHUP."""
65 63
66 64 class SignatureError(Exception):
67 65 pass
68 66
69 67 class Abort(Exception):
70 68 """Raised if a command needs to print an error and exit."""
@@ -1,49 +1,47 b''
1 """
2 i18n.py - internationalization support for mercurial
3
4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5
6 This software may be used and distributed according to the terms of the
7 GNU General Public License version 2, incorporated herein by reference.
8 """
1 # i18n.py - internationalization support for mercurial
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
9 7
10 8 import gettext, sys, os, encoding
11 9
12 10 # modelled after templater.templatepath:
13 11 if hasattr(sys, 'frozen'):
14 12 module = sys.executable
15 13 else:
16 14 module = __file__
17 15
18 16 base = os.path.dirname(module)
19 17 for dir in ('.', '..'):
20 18 localedir = os.path.normpath(os.path.join(base, dir, 'locale'))
21 19 if os.path.isdir(localedir):
22 20 break
23 21
24 22 t = gettext.translation('hg', localedir, fallback=True)
25 23
26 24 def gettext(message):
27 25 """Translate message.
28 26
29 27 The message is looked up in the catalog to get a Unicode string,
30 28 which is encoded in the local encoding before being returned.
31 29
32 30 Important: message is restricted to characters in the encoding
33 31 given by sys.getdefaultencoding() which is most likely 'ascii'.
34 32 """
35 33 # If message is None, t.ugettext will return u'None' as the
36 34 # translation whereas our callers expect us to return None.
37 35 if message is None:
38 36 return message
39 37
40 38 # We cannot just run the text through encoding.tolocal since that
41 39 # leads to infinite recursion when encoding._encoding is invalid.
42 40 try:
43 41 u = t.ugettext(message)
44 42 return u.encode(encoding.encoding, "replace")
45 43 except LookupError:
46 44 return message
47 45
48 46 _ = gettext
49 47
@@ -1,20 +1,18 b''
1 """
2 node.py - basic nodeid manipulation for mercurial
3
4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5
6 This software may be used and distributed according to the terms of the
7 GNU General Public License version 2, incorporated herein by reference.
8 """
1 # node.py - basic nodeid manipulation for mercurial
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
9 7
10 8 import binascii
11 9
12 10 nullrev = -1
13 11 nullid = "\0" * 20
14 12
15 13 # This ugly style has a noticeable effect in manifest parsing
16 14 hex = binascii.hexlify
17 15 bin = binascii.unhexlify
18 16
19 17 def short(node):
20 18 return hex(node[:6])
@@ -1,219 +1,217 b''
1 """
2 posix.py - Posix utility function implementations for Mercurial
3
4 Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
5
6 This software may be used and distributed according to the terms of the
7 GNU General Public License version 2, incorporated herein by reference.
8 """
1 # posix.py - Posix utility function implementations for Mercurial
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
9 7
10 8 from i18n import _
11 9 import os, sys, osutil, errno, stat, getpass, pwd, grp
12 10
13 11 posixfile = file
14 12 nulldev = '/dev/null'
15 13 normpath = os.path.normpath
16 14 samestat = os.path.samestat
17 15
18 16 umask = os.umask(0)
19 17 os.umask(umask)
20 18
21 19 def openhardlinks():
22 20 '''return true if it is safe to hold open file handles to hardlinks'''
23 21 return True
24 22
25 23 def rcfiles(path):
26 24 rcs = [os.path.join(path, 'hgrc')]
27 25 rcdir = os.path.join(path, 'hgrc.d')
28 26 try:
29 27 rcs.extend([os.path.join(rcdir, f)
30 28 for f, kind in osutil.listdir(rcdir)
31 29 if f.endswith(".rc")])
32 30 except OSError:
33 31 pass
34 32 return rcs
35 33
36 34 def system_rcpath():
37 35 path = []
38 36 # old mod_python does not set sys.argv
39 37 if len(getattr(sys, 'argv', [])) > 0:
40 38 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
41 39 '/../etc/mercurial'))
42 40 path.extend(rcfiles('/etc/mercurial'))
43 41 return path
44 42
45 43 def user_rcpath():
46 44 return [os.path.expanduser('~/.hgrc')]
47 45
48 46 def parse_patch_output(output_line):
49 47 """parses the output produced by patch and returns the file name"""
50 48 pf = output_line[14:]
51 49 if os.sys.platform == 'OpenVMS':
52 50 if pf[0] == '`':
53 51 pf = pf[1:-1] # Remove the quotes
54 52 else:
55 53 if pf.startswith("'") and pf.endswith("'") and " " in pf:
56 54 pf = pf[1:-1] # Remove the quotes
57 55 return pf
58 56
59 57 def sshargs(sshcmd, host, user, port):
60 58 '''Build argument list for ssh'''
61 59 args = user and ("%s@%s" % (user, host)) or host
62 60 return port and ("%s -p %s" % (args, port)) or args
63 61
64 62 def is_exec(f):
65 63 """check whether a file is executable"""
66 64 return (os.lstat(f).st_mode & 0100 != 0)
67 65
68 66 def set_flags(f, l, x):
69 67 s = os.lstat(f).st_mode
70 68 if l:
71 69 if not stat.S_ISLNK(s):
72 70 # switch file to link
73 71 data = file(f).read()
74 72 os.unlink(f)
75 73 try:
76 74 os.symlink(data, f)
77 75 except:
78 76 # failed to make a link, rewrite file
79 77 file(f, "w").write(data)
80 78 # no chmod needed at this point
81 79 return
82 80 if stat.S_ISLNK(s):
83 81 # switch link to file
84 82 data = os.readlink(f)
85 83 os.unlink(f)
86 84 file(f, "w").write(data)
87 85 s = 0666 & ~umask # avoid restatting for chmod
88 86
89 87 sx = s & 0100
90 88 if x and not sx:
91 89 # Turn on +x for every +r bit when making a file executable
92 90 # and obey umask.
93 91 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
94 92 elif not x and sx:
95 93 # Turn off all +x bits
96 94 os.chmod(f, s & 0666)
97 95
98 96 def set_binary(fd):
99 97 pass
100 98
101 99 def pconvert(path):
102 100 return path
103 101
104 102 def localpath(path):
105 103 return path
106 104
107 105 def shellquote(s):
108 106 if os.sys.platform == 'OpenVMS':
109 107 return '"%s"' % s
110 108 else:
111 109 return "'%s'" % s.replace("'", "'\\''")
112 110
113 111 def quotecommand(cmd):
114 112 return cmd
115 113
116 114 def popen(command, mode='r'):
117 115 return os.popen(command, mode)
118 116
119 117 def testpid(pid):
120 118 '''return False if pid dead, True if running or not sure'''
121 119 if os.sys.platform == 'OpenVMS':
122 120 return True
123 121 try:
124 122 os.kill(pid, 0)
125 123 return True
126 124 except OSError, inst:
127 125 return inst.errno != errno.ESRCH
128 126
129 127 def explain_exit(code):
130 128 """return a 2-tuple (desc, code) describing a process's status"""
131 129 if os.WIFEXITED(code):
132 130 val = os.WEXITSTATUS(code)
133 131 return _("exited with status %d") % val, val
134 132 elif os.WIFSIGNALED(code):
135 133 val = os.WTERMSIG(code)
136 134 return _("killed by signal %d") % val, val
137 135 elif os.WIFSTOPPED(code):
138 136 val = os.WSTOPSIG(code)
139 137 return _("stopped by signal %d") % val, val
140 138 raise ValueError(_("invalid exit code"))
141 139
142 140 def isowner(fp, st=None):
143 141 """Return True if the file object f belongs to the current user.
144 142
145 143 The return value of a util.fstat(f) may be passed as the st argument.
146 144 """
147 145 if st is None:
148 146 st = fstat(fp)
149 147 return st.st_uid == os.getuid()
150 148
151 149 def find_exe(command):
152 150 '''Find executable for command searching like which does.
153 151 If command is a basename then PATH is searched for command.
154 152 PATH isn't searched if command is an absolute or relative path.
155 153 If command isn't found None is returned.'''
156 154 if sys.platform == 'OpenVMS':
157 155 return command
158 156
159 157 def findexisting(executable):
160 158 'Will return executable if existing file'
161 159 if os.path.exists(executable):
162 160 return executable
163 161 return None
164 162
165 163 if os.sep in command:
166 164 return findexisting(command)
167 165
168 166 for path in os.environ.get('PATH', '').split(os.pathsep):
169 167 executable = findexisting(os.path.join(path, command))
170 168 if executable is not None:
171 169 return executable
172 170 return None
173 171
174 172 def set_signal_handler():
175 173 pass
176 174
177 175 def statfiles(files):
178 176 'Stat each file in files and yield stat or None if file does not exist.'
179 177 lstat = os.lstat
180 178 for nf in files:
181 179 try:
182 180 st = lstat(nf)
183 181 except OSError, err:
184 182 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
185 183 raise
186 184 st = None
187 185 yield st
188 186
189 187 def getuser():
190 188 '''return name of current user'''
191 189 return getpass.getuser()
192 190
193 191 def expand_glob(pats):
194 192 '''On Windows, expand the implicit globs in a list of patterns'''
195 193 return list(pats)
196 194
197 195 def username(uid=None):
198 196 """Return the name of the user with the given uid.
199 197
200 198 If uid is None, return the name of the current user."""
201 199
202 200 if uid is None:
203 201 uid = os.getuid()
204 202 try:
205 203 return pwd.getpwuid(uid)[0]
206 204 except KeyError:
207 205 return str(uid)
208 206
209 207 def groupname(gid=None):
210 208 """Return the name of the group with the given gid.
211 209
212 210 If gid is None, return the name of the current group."""
213 211
214 212 if gid is None:
215 213 gid = os.getgid()
216 214 try:
217 215 return grp.getgrgid(gid)[0]
218 216 except KeyError:
219 217 return str(gid)
@@ -1,1369 +1,1367 b''
1 """
2 revlog.py - storage back-end for mercurial
3
4 This provides efficient delta storage with O(1) retrieve and append
5 and O(changes) merge between branches
6
7 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
8
9 This software may be used and distributed according to the terms of the
10 GNU General Public License version 2, incorporated herein by reference.
11 """
1 # revlog.py - storage back-end for mercurial
2 #
3 # This provides efficient delta storage with O(1) retrieve and append
4 # and O(changes) merge between branches
5 #
6 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
7 #
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2, incorporated herein by reference.
12 10
13 11 # import stuff from node for others to import from revlog
14 12 from node import bin, hex, nullid, nullrev, short #@UnusedImport
15 13 from i18n import _
16 14 import changegroup, errno, ancestor, mdiff, parsers
17 15 import struct, util, zlib, error
18 16
19 17 _pack = struct.pack
20 18 _unpack = struct.unpack
21 19 _compress = zlib.compress
22 20 _decompress = zlib.decompress
23 21 _sha = util.sha1
24 22
25 23 # revlog flags
26 24 REVLOGV0 = 0
27 25 REVLOGNG = 1
28 26 REVLOGNGINLINEDATA = (1 << 16)
29 27 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
30 28 REVLOG_DEFAULT_FORMAT = REVLOGNG
31 29 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
32 30
33 31 RevlogError = error.RevlogError
34 32 LookupError = error.LookupError
35 33
36 34 def getoffset(q):
37 35 return int(q >> 16)
38 36
39 37 def gettype(q):
40 38 return int(q & 0xFFFF)
41 39
42 40 def offset_type(offset, type):
43 41 return long(long(offset) << 16 | type)
44 42
45 43 nullhash = _sha(nullid)
46 44
47 45 def hash(text, p1, p2):
48 46 """generate a hash from the given text and its parent hashes
49 47
50 48 This hash combines both the current file contents and its history
51 49 in a manner that makes it easy to distinguish nodes with the same
52 50 content in the revision graph.
53 51 """
54 52 # As of now, if one of the parent node is null, p2 is null
55 53 if p2 == nullid:
56 54 # deep copy of a hash is faster than creating one
57 55 s = nullhash.copy()
58 56 s.update(p1)
59 57 else:
60 58 # none of the parent nodes are nullid
61 59 l = [p1, p2]
62 60 l.sort()
63 61 s = _sha(l[0])
64 62 s.update(l[1])
65 63 s.update(text)
66 64 return s.digest()
67 65
68 66 def compress(text):
69 67 """ generate a possibly-compressed representation of text """
70 68 if not text:
71 69 return ("", text)
72 70 l = len(text)
73 71 bin = None
74 72 if l < 44:
75 73 pass
76 74 elif l > 1000000:
77 75 # zlib makes an internal copy, thus doubling memory usage for
78 76 # large files, so lets do this in pieces
79 77 z = zlib.compressobj()
80 78 p = []
81 79 pos = 0
82 80 while pos < l:
83 81 pos2 = pos + 2**20
84 82 p.append(z.compress(text[pos:pos2]))
85 83 pos = pos2
86 84 p.append(z.flush())
87 85 if sum(map(len, p)) < l:
88 86 bin = "".join(p)
89 87 else:
90 88 bin = _compress(text)
91 89 if bin is None or len(bin) > l:
92 90 if text[0] == '\0':
93 91 return ("", text)
94 92 return ('u', text)
95 93 return ("", bin)
96 94
97 95 def decompress(bin):
98 96 """ decompress the given input """
99 97 if not bin:
100 98 return bin
101 99 t = bin[0]
102 100 if t == '\0':
103 101 return bin
104 102 if t == 'x':
105 103 return _decompress(bin)
106 104 if t == 'u':
107 105 return bin[1:]
108 106 raise RevlogError(_("unknown compression type %r") % t)
109 107
110 108 class lazyparser(object):
111 109 """
112 110 this class avoids the need to parse the entirety of large indices
113 111 """
114 112
115 113 # lazyparser is not safe to use on windows if win32 extensions not
116 114 # available. it keeps file handle open, which make it not possible
117 115 # to break hardlinks on local cloned repos.
118 116
119 117 def __init__(self, dataf, size):
120 118 self.dataf = dataf
121 119 self.s = struct.calcsize(indexformatng)
122 120 self.datasize = size
123 121 self.l = size/self.s
124 122 self.index = [None] * self.l
125 123 self.map = {nullid: nullrev}
126 124 self.allmap = 0
127 125 self.all = 0
128 126 self.mapfind_count = 0
129 127
130 128 def loadmap(self):
131 129 """
132 130 during a commit, we need to make sure the rev being added is
133 131 not a duplicate. This requires loading the entire index,
134 132 which is fairly slow. loadmap can load up just the node map,
135 133 which takes much less time.
136 134 """
137 135 if self.allmap:
138 136 return
139 137 end = self.datasize
140 138 self.allmap = 1
141 139 cur = 0
142 140 count = 0
143 141 blocksize = self.s * 256
144 142 self.dataf.seek(0)
145 143 while cur < end:
146 144 data = self.dataf.read(blocksize)
147 145 off = 0
148 146 for x in xrange(256):
149 147 n = data[off + ngshaoffset:off + ngshaoffset + 20]
150 148 self.map[n] = count
151 149 count += 1
152 150 if count >= self.l:
153 151 break
154 152 off += self.s
155 153 cur += blocksize
156 154
157 155 def loadblock(self, blockstart, blocksize, data=None):
158 156 if self.all:
159 157 return
160 158 if data is None:
161 159 self.dataf.seek(blockstart)
162 160 if blockstart + blocksize > self.datasize:
163 161 # the revlog may have grown since we've started running,
164 162 # but we don't have space in self.index for more entries.
165 163 # limit blocksize so that we don't get too much data.
166 164 blocksize = max(self.datasize - blockstart, 0)
167 165 data = self.dataf.read(blocksize)
168 166 lend = len(data) / self.s
169 167 i = blockstart / self.s
170 168 off = 0
171 169 # lazyindex supports __delitem__
172 170 if lend > len(self.index) - i:
173 171 lend = len(self.index) - i
174 172 for x in xrange(lend):
175 173 if self.index[i + x] == None:
176 174 b = data[off : off + self.s]
177 175 self.index[i + x] = b
178 176 n = b[ngshaoffset:ngshaoffset + 20]
179 177 self.map[n] = i + x
180 178 off += self.s
181 179
182 180 def findnode(self, node):
183 181 """search backwards through the index file for a specific node"""
184 182 if self.allmap:
185 183 return None
186 184
187 185 # hg log will cause many many searches for the manifest
188 186 # nodes. After we get called a few times, just load the whole
189 187 # thing.
190 188 if self.mapfind_count > 8:
191 189 self.loadmap()
192 190 if node in self.map:
193 191 return node
194 192 return None
195 193 self.mapfind_count += 1
196 194 last = self.l - 1
197 195 while self.index[last] != None:
198 196 if last == 0:
199 197 self.all = 1
200 198 self.allmap = 1
201 199 return None
202 200 last -= 1
203 201 end = (last + 1) * self.s
204 202 blocksize = self.s * 256
205 203 while end >= 0:
206 204 start = max(end - blocksize, 0)
207 205 self.dataf.seek(start)
208 206 data = self.dataf.read(end - start)
209 207 findend = end - start
210 208 while True:
211 209 # we're searching backwards, so we have to make sure
212 210 # we don't find a changeset where this node is a parent
213 211 off = data.find(node, 0, findend)
214 212 findend = off
215 213 if off >= 0:
216 214 i = off / self.s
217 215 off = i * self.s
218 216 n = data[off + ngshaoffset:off + ngshaoffset + 20]
219 217 if n == node:
220 218 self.map[n] = i + start / self.s
221 219 return node
222 220 else:
223 221 break
224 222 end -= blocksize
225 223 return None
226 224
227 225 def loadindex(self, i=None, end=None):
228 226 if self.all:
229 227 return
230 228 all = False
231 229 if i == None:
232 230 blockstart = 0
233 231 blocksize = (65536 / self.s) * self.s
234 232 end = self.datasize
235 233 all = True
236 234 else:
237 235 if end:
238 236 blockstart = i * self.s
239 237 end = end * self.s
240 238 blocksize = end - blockstart
241 239 else:
242 240 blockstart = (i & ~1023) * self.s
243 241 blocksize = self.s * 1024
244 242 end = blockstart + blocksize
245 243 while blockstart < end:
246 244 self.loadblock(blockstart, blocksize)
247 245 blockstart += blocksize
248 246 if all:
249 247 self.all = True
250 248
251 249 class lazyindex(object):
252 250 """a lazy version of the index array"""
253 251 def __init__(self, parser):
254 252 self.p = parser
255 253 def __len__(self):
256 254 return len(self.p.index)
257 255 def load(self, pos):
258 256 if pos < 0:
259 257 pos += len(self.p.index)
260 258 self.p.loadindex(pos)
261 259 return self.p.index[pos]
262 260 def __getitem__(self, pos):
263 261 return _unpack(indexformatng, self.p.index[pos] or self.load(pos))
264 262 def __setitem__(self, pos, item):
265 263 self.p.index[pos] = _pack(indexformatng, *item)
266 264 def __delitem__(self, pos):
267 265 del self.p.index[pos]
268 266 def insert(self, pos, e):
269 267 self.p.index.insert(pos, _pack(indexformatng, *e))
270 268 def append(self, e):
271 269 self.p.index.append(_pack(indexformatng, *e))
272 270
273 271 class lazymap(object):
274 272 """a lazy version of the node map"""
275 273 def __init__(self, parser):
276 274 self.p = parser
277 275 def load(self, key):
278 276 n = self.p.findnode(key)
279 277 if n == None:
280 278 raise KeyError(key)
281 279 def __contains__(self, key):
282 280 if key in self.p.map:
283 281 return True
284 282 self.p.loadmap()
285 283 return key in self.p.map
286 284 def __iter__(self):
287 285 yield nullid
288 286 for i in xrange(self.p.l):
289 287 ret = self.p.index[i]
290 288 if not ret:
291 289 self.p.loadindex(i)
292 290 ret = self.p.index[i]
293 291 if isinstance(ret, str):
294 292 ret = _unpack(indexformatng, ret)
295 293 yield ret[7]
296 294 def __getitem__(self, key):
297 295 try:
298 296 return self.p.map[key]
299 297 except KeyError:
300 298 try:
301 299 self.load(key)
302 300 return self.p.map[key]
303 301 except KeyError:
304 302 raise KeyError("node " + hex(key))
305 303 def __setitem__(self, key, val):
306 304 self.p.map[key] = val
307 305 def __delitem__(self, key):
308 306 del self.p.map[key]
309 307
310 308 indexformatv0 = ">4l20s20s20s"
311 309 v0shaoffset = 56
312 310
313 311 class revlogoldio(object):
314 312 def __init__(self):
315 313 self.size = struct.calcsize(indexformatv0)
316 314
317 315 def parseindex(self, fp, inline):
318 316 s = self.size
319 317 index = []
320 318 nodemap = {nullid: nullrev}
321 319 n = off = 0
322 320 data = fp.read()
323 321 l = len(data)
324 322 while off + s <= l:
325 323 cur = data[off:off + s]
326 324 off += s
327 325 e = _unpack(indexformatv0, cur)
328 326 # transform to revlogv1 format
329 327 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
330 328 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
331 329 index.append(e2)
332 330 nodemap[e[6]] = n
333 331 n += 1
334 332
335 333 return index, nodemap, None
336 334
337 335 def packentry(self, entry, node, version, rev):
338 336 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
339 337 node(entry[5]), node(entry[6]), entry[7])
340 338 return _pack(indexformatv0, *e2)
341 339
342 340 # index ng:
343 341 # 6 bytes offset
344 342 # 2 bytes flags
345 343 # 4 bytes compressed length
346 344 # 4 bytes uncompressed length
347 345 # 4 bytes: base rev
348 346 # 4 bytes link rev
349 347 # 4 bytes parent 1 rev
350 348 # 4 bytes parent 2 rev
351 349 # 32 bytes: nodeid
352 350 indexformatng = ">Qiiiiii20s12x"
353 351 ngshaoffset = 32
354 352 versionformat = ">I"
355 353
356 354 class revlogio(object):
357 355 def __init__(self):
358 356 self.size = struct.calcsize(indexformatng)
359 357
360 358 def parseindex(self, fp, inline):
361 359 try:
362 360 size = util.fstat(fp).st_size
363 361 except AttributeError:
364 362 size = 0
365 363
366 364 if util.openhardlinks() and not inline and size > 1000000:
367 365 # big index, let's parse it on demand
368 366 parser = lazyparser(fp, size)
369 367 index = lazyindex(parser)
370 368 nodemap = lazymap(parser)
371 369 e = list(index[0])
372 370 type = gettype(e[0])
373 371 e[0] = offset_type(0, type)
374 372 index[0] = e
375 373 return index, nodemap, None
376 374
377 375 data = fp.read()
378 376 # call the C implementation to parse the index data
379 377 index, nodemap, cache = parsers.parse_index(data, inline)
380 378 return index, nodemap, cache
381 379
382 380 def packentry(self, entry, node, version, rev):
383 381 p = _pack(indexformatng, *entry)
384 382 if rev == 0:
385 383 p = _pack(versionformat, version) + p[4:]
386 384 return p
387 385
388 386 class revlog(object):
389 387 """
390 388 the underlying revision storage object
391 389
392 390 A revlog consists of two parts, an index and the revision data.
393 391
394 392 The index is a file with a fixed record size containing
395 393 information on each revision, including its nodeid (hash), the
396 394 nodeids of its parents, the position and offset of its data within
397 395 the data file, and the revision it's based on. Finally, each entry
398 396 contains a linkrev entry that can serve as a pointer to external
399 397 data.
400 398
401 399 The revision data itself is a linear collection of data chunks.
402 400 Each chunk represents a revision and is usually represented as a
403 401 delta against the previous chunk. To bound lookup time, runs of
404 402 deltas are limited to about 2 times the length of the original
405 403 version data. This makes retrieval of a version proportional to
406 404 its size, or O(1) relative to the number of revisions.
407 405
408 406 Both pieces of the revlog are written to in an append-only
409 407 fashion, which means we never need to rewrite a file to insert or
410 408 remove data, and can use some simple techniques to avoid the need
411 409 for locking while reading.
412 410 """
413 411 def __init__(self, opener, indexfile):
414 412 """
415 413 create a revlog object
416 414
417 415 opener is a function that abstracts the file opening operation
418 416 and can be used to implement COW semantics or the like.
419 417 """
420 418 self.indexfile = indexfile
421 419 self.datafile = indexfile[:-2] + ".d"
422 420 self.opener = opener
423 421 self._cache = None
424 422 self._chunkcache = None
425 423 self.nodemap = {nullid: nullrev}
426 424 self.index = []
427 425
428 426 v = REVLOG_DEFAULT_VERSION
429 427 if hasattr(opener, "defversion"):
430 428 v = opener.defversion
431 429 if v & REVLOGNG:
432 430 v |= REVLOGNGINLINEDATA
433 431
434 432 i = ""
435 433 try:
436 434 f = self.opener(self.indexfile)
437 435 i = f.read(4)
438 436 f.seek(0)
439 437 if len(i) > 0:
440 438 v = struct.unpack(versionformat, i)[0]
441 439 except IOError, inst:
442 440 if inst.errno != errno.ENOENT:
443 441 raise
444 442
445 443 self.version = v
446 444 self._inline = v & REVLOGNGINLINEDATA
447 445 flags = v & ~0xFFFF
448 446 fmt = v & 0xFFFF
449 447 if fmt == REVLOGV0 and flags:
450 448 raise RevlogError(_("index %s unknown flags %#04x for format v0")
451 449 % (self.indexfile, flags >> 16))
452 450 elif fmt == REVLOGNG and flags & ~REVLOGNGINLINEDATA:
453 451 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
454 452 % (self.indexfile, flags >> 16))
455 453 elif fmt > REVLOGNG:
456 454 raise RevlogError(_("index %s unknown format %d")
457 455 % (self.indexfile, fmt))
458 456
459 457 self._io = revlogio()
460 458 if self.version == REVLOGV0:
461 459 self._io = revlogoldio()
462 460 if i:
463 461 try:
464 462 d = self._io.parseindex(f, self._inline)
465 463 except (ValueError, IndexError), e:
466 464 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
467 465 self.index, self.nodemap, self._chunkcache = d
468 466
469 467 # add the magic null revision at -1 (if it hasn't been done already)
470 468 if (self.index == [] or isinstance(self.index, lazyindex) or
471 469 self.index[-1][7] != nullid) :
472 470 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
473 471
474 472 def _loadindex(self, start, end):
475 473 """load a block of indexes all at once from the lazy parser"""
476 474 if isinstance(self.index, lazyindex):
477 475 self.index.p.loadindex(start, end)
478 476
479 477 def _loadindexmap(self):
480 478 """loads both the map and the index from the lazy parser"""
481 479 if isinstance(self.index, lazyindex):
482 480 p = self.index.p
483 481 p.loadindex()
484 482 self.nodemap = p.map
485 483
486 484 def _loadmap(self):
487 485 """loads the map from the lazy parser"""
488 486 if isinstance(self.nodemap, lazymap):
489 487 self.nodemap.p.loadmap()
490 488 self.nodemap = self.nodemap.p.map
491 489
492 490 def tip(self):
493 491 return self.node(len(self.index) - 2)
494 492 def __len__(self):
495 493 return len(self.index) - 1
496 494 def __iter__(self):
497 495 for i in xrange(len(self)):
498 496 yield i
499 497 def rev(self, node):
500 498 try:
501 499 return self.nodemap[node]
502 500 except KeyError:
503 501 raise LookupError(node, self.indexfile, _('no node'))
504 502 def node(self, rev):
505 503 return self.index[rev][7]
506 504 def linkrev(self, rev):
507 505 return self.index[rev][4]
508 506 def parents(self, node):
509 507 i = self.index
510 508 d = i[self.rev(node)]
511 509 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
512 510 def parentrevs(self, rev):
513 511 return self.index[rev][5:7]
514 512 def start(self, rev):
515 513 return int(self.index[rev][0] >> 16)
516 514 def end(self, rev):
517 515 return self.start(rev) + self.length(rev)
518 516 def length(self, rev):
519 517 return self.index[rev][1]
520 518 def base(self, rev):
521 519 return self.index[rev][3]
522 520
523 521 def size(self, rev):
524 522 """return the length of the uncompressed text for a given revision"""
525 523 l = self.index[rev][2]
526 524 if l >= 0:
527 525 return l
528 526
529 527 t = self.revision(self.node(rev))
530 528 return len(t)
531 529
532 530 # alternate implementation, The advantage to this code is it
533 531 # will be faster for a single revision. But, the results are not
534 532 # cached, so finding the size of every revision will be slower.
535 533 """
536 534 if self.cache and self.cache[1] == rev:
537 535 return len(self.cache[2])
538 536
539 537 base = self.base(rev)
540 538 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
541 539 base = self.cache[1]
542 540 text = self.cache[2]
543 541 else:
544 542 text = self.revision(self.node(base))
545 543
546 544 l = len(text)
547 545 for x in xrange(base + 1, rev + 1):
548 546 l = mdiff.patchedsize(l, self.chunk(x))
549 547 return l
550 548 """
551 549
552 550 def reachable(self, node, stop=None):
553 551 """return a hash of all nodes ancestral to a given node, including
554 552 the node itself, stopping when stop is matched"""
555 553 reachable = {}
556 554 visit = [node]
557 555 reachable[node] = 1
558 556 if stop:
559 557 stopn = self.rev(stop)
560 558 else:
561 559 stopn = 0
562 560 while visit:
563 561 n = visit.pop(0)
564 562 if n == stop:
565 563 continue
566 564 if n == nullid:
567 565 continue
568 566 for p in self.parents(n):
569 567 if self.rev(p) < stopn:
570 568 continue
571 569 if p not in reachable:
572 570 reachable[p] = 1
573 571 visit.append(p)
574 572 return reachable
575 573
576 574 def ancestors(self, *revs):
577 575 'Generate the ancestors of revs using a breadth-first visit'
578 576 visit = list(revs)
579 577 seen = set([nullrev])
580 578 while visit:
581 579 for parent in self.parentrevs(visit.pop(0)):
582 580 if parent not in seen:
583 581 visit.append(parent)
584 582 seen.add(parent)
585 583 yield parent
586 584
587 585 def descendants(self, *revs):
588 586 'Generate the descendants of revs in topological order'
589 587 seen = set(revs)
590 588 for i in xrange(min(revs) + 1, len(self)):
591 589 for x in self.parentrevs(i):
592 590 if x != nullrev and x in seen:
593 591 seen.add(i)
594 592 yield i
595 593 break
596 594
597 595 def findmissing(self, common=None, heads=None):
598 596 '''
599 597 returns the topologically sorted list of nodes from the set:
600 598 missing = (ancestors(heads) \ ancestors(common))
601 599
602 600 where ancestors() is the set of ancestors from heads, heads included
603 601
604 602 if heads is None, the heads of the revlog are used
605 603 if common is None, nullid is assumed to be a common node
606 604 '''
607 605 if common is None:
608 606 common = [nullid]
609 607 if heads is None:
610 608 heads = self.heads()
611 609
612 610 common = [self.rev(n) for n in common]
613 611 heads = [self.rev(n) for n in heads]
614 612
615 613 # we want the ancestors, but inclusive
616 614 has = set(self.ancestors(*common))
617 615 has.add(nullrev)
618 616 has.update(common)
619 617
620 618 # take all ancestors from heads that aren't in has
621 619 missing = {}
622 620 visit = [r for r in heads if r not in has]
623 621 while visit:
624 622 r = visit.pop(0)
625 623 if r in missing:
626 624 continue
627 625 else:
628 626 missing[r] = None
629 627 for p in self.parentrevs(r):
630 628 if p not in has:
631 629 visit.append(p)
632 630 missing = missing.keys()
633 631 missing.sort()
634 632 return [self.node(r) for r in missing]
635 633
636 634 def nodesbetween(self, roots=None, heads=None):
637 635 """Return a tuple containing three elements. Elements 1 and 2 contain
638 636 a final list bases and heads after all the unreachable ones have been
639 637 pruned. Element 0 contains a topologically sorted list of all
640 638
641 639 nodes that satisfy these constraints:
642 640 1. All nodes must be descended from a node in roots (the nodes on
643 641 roots are considered descended from themselves).
644 642 2. All nodes must also be ancestors of a node in heads (the nodes in
645 643 heads are considered to be their own ancestors).
646 644
647 645 If roots is unspecified, nullid is assumed as the only root.
648 646 If heads is unspecified, it is taken to be the output of the
649 647 heads method (i.e. a list of all nodes in the repository that
650 648 have no children)."""
651 649 nonodes = ([], [], [])
652 650 if roots is not None:
653 651 roots = list(roots)
654 652 if not roots:
655 653 return nonodes
656 654 lowestrev = min([self.rev(n) for n in roots])
657 655 else:
658 656 roots = [nullid] # Everybody's a descendent of nullid
659 657 lowestrev = nullrev
660 658 if (lowestrev == nullrev) and (heads is None):
661 659 # We want _all_ the nodes!
662 660 return ([self.node(r) for r in self], [nullid], list(self.heads()))
663 661 if heads is None:
664 662 # All nodes are ancestors, so the latest ancestor is the last
665 663 # node.
666 664 highestrev = len(self) - 1
667 665 # Set ancestors to None to signal that every node is an ancestor.
668 666 ancestors = None
669 667 # Set heads to an empty dictionary for later discovery of heads
670 668 heads = {}
671 669 else:
672 670 heads = list(heads)
673 671 if not heads:
674 672 return nonodes
675 673 ancestors = {}
676 674 # Turn heads into a dictionary so we can remove 'fake' heads.
677 675 # Also, later we will be using it to filter out the heads we can't
678 676 # find from roots.
679 677 heads = dict.fromkeys(heads, 0)
680 678 # Start at the top and keep marking parents until we're done.
681 679 nodestotag = set(heads)
682 680 # Remember where the top was so we can use it as a limit later.
683 681 highestrev = max([self.rev(n) for n in nodestotag])
684 682 while nodestotag:
685 683 # grab a node to tag
686 684 n = nodestotag.pop()
687 685 # Never tag nullid
688 686 if n == nullid:
689 687 continue
690 688 # A node's revision number represents its place in a
691 689 # topologically sorted list of nodes.
692 690 r = self.rev(n)
693 691 if r >= lowestrev:
694 692 if n not in ancestors:
695 693 # If we are possibly a descendent of one of the roots
696 694 # and we haven't already been marked as an ancestor
697 695 ancestors[n] = 1 # Mark as ancestor
698 696 # Add non-nullid parents to list of nodes to tag.
699 697 nodestotag.update([p for p in self.parents(n) if
700 698 p != nullid])
701 699 elif n in heads: # We've seen it before, is it a fake head?
702 700 # So it is, real heads should not be the ancestors of
703 701 # any other heads.
704 702 heads.pop(n)
705 703 if not ancestors:
706 704 return nonodes
707 705 # Now that we have our set of ancestors, we want to remove any
708 706 # roots that are not ancestors.
709 707
710 708 # If one of the roots was nullid, everything is included anyway.
711 709 if lowestrev > nullrev:
712 710 # But, since we weren't, let's recompute the lowest rev to not
713 711 # include roots that aren't ancestors.
714 712
715 713 # Filter out roots that aren't ancestors of heads
716 714 roots = [n for n in roots if n in ancestors]
717 715 # Recompute the lowest revision
718 716 if roots:
719 717 lowestrev = min([self.rev(n) for n in roots])
720 718 else:
721 719 # No more roots? Return empty list
722 720 return nonodes
723 721 else:
724 722 # We are descending from nullid, and don't need to care about
725 723 # any other roots.
726 724 lowestrev = nullrev
727 725 roots = [nullid]
728 726 # Transform our roots list into a set.
729 727 descendents = set(roots)
730 728 # Also, keep the original roots so we can filter out roots that aren't
731 729 # 'real' roots (i.e. are descended from other roots).
732 730 roots = descendents.copy()
733 731 # Our topologically sorted list of output nodes.
734 732 orderedout = []
735 733 # Don't start at nullid since we don't want nullid in our output list,
736 734 # and if nullid shows up in descedents, empty parents will look like
737 735 # they're descendents.
738 736 for r in xrange(max(lowestrev, 0), highestrev + 1):
739 737 n = self.node(r)
740 738 isdescendent = False
741 739 if lowestrev == nullrev: # Everybody is a descendent of nullid
742 740 isdescendent = True
743 741 elif n in descendents:
744 742 # n is already a descendent
745 743 isdescendent = True
746 744 # This check only needs to be done here because all the roots
747 745 # will start being marked is descendents before the loop.
748 746 if n in roots:
749 747 # If n was a root, check if it's a 'real' root.
750 748 p = tuple(self.parents(n))
751 749 # If any of its parents are descendents, it's not a root.
752 750 if (p[0] in descendents) or (p[1] in descendents):
753 751 roots.remove(n)
754 752 else:
755 753 p = tuple(self.parents(n))
756 754 # A node is a descendent if either of its parents are
757 755 # descendents. (We seeded the dependents list with the roots
758 756 # up there, remember?)
759 757 if (p[0] in descendents) or (p[1] in descendents):
760 758 descendents.add(n)
761 759 isdescendent = True
762 760 if isdescendent and ((ancestors is None) or (n in ancestors)):
763 761 # Only include nodes that are both descendents and ancestors.
764 762 orderedout.append(n)
765 763 if (ancestors is not None) and (n in heads):
766 764 # We're trying to figure out which heads are reachable
767 765 # from roots.
768 766 # Mark this head as having been reached
769 767 heads[n] = 1
770 768 elif ancestors is None:
771 769 # Otherwise, we're trying to discover the heads.
772 770 # Assume this is a head because if it isn't, the next step
773 771 # will eventually remove it.
774 772 heads[n] = 1
775 773 # But, obviously its parents aren't.
776 774 for p in self.parents(n):
777 775 heads.pop(p, None)
778 776 heads = [n for n in heads.iterkeys() if heads[n] != 0]
779 777 roots = list(roots)
780 778 assert orderedout
781 779 assert roots
782 780 assert heads
783 781 return (orderedout, roots, heads)
784 782
785 783 def heads(self, start=None, stop=None):
786 784 """return the list of all nodes that have no children
787 785
788 786 if start is specified, only heads that are descendants of
789 787 start will be returned
790 788 if stop is specified, it will consider all the revs from stop
791 789 as if they had no children
792 790 """
793 791 if start is None and stop is None:
794 792 count = len(self)
795 793 if not count:
796 794 return [nullid]
797 795 ishead = [1] * (count + 1)
798 796 index = self.index
799 797 for r in xrange(count):
800 798 e = index[r]
801 799 ishead[e[5]] = ishead[e[6]] = 0
802 800 return [self.node(r) for r in xrange(count) if ishead[r]]
803 801
804 802 if start is None:
805 803 start = nullid
806 804 if stop is None:
807 805 stop = []
808 806 stoprevs = set([self.rev(n) for n in stop])
809 807 startrev = self.rev(start)
810 808 reachable = {startrev: 1}
811 809 heads = {startrev: 1}
812 810
813 811 parentrevs = self.parentrevs
814 812 for r in xrange(startrev + 1, len(self)):
815 813 for p in parentrevs(r):
816 814 if p in reachable:
817 815 if r not in stoprevs:
818 816 reachable[r] = 1
819 817 heads[r] = 1
820 818 if p in heads and p not in stoprevs:
821 819 del heads[p]
822 820
823 821 return [self.node(r) for r in heads]
824 822
825 823 def children(self, node):
826 824 """find the children of a given node"""
827 825 c = []
828 826 p = self.rev(node)
829 827 for r in range(p + 1, len(self)):
830 828 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
831 829 if prevs:
832 830 for pr in prevs:
833 831 if pr == p:
834 832 c.append(self.node(r))
835 833 elif p == nullrev:
836 834 c.append(self.node(r))
837 835 return c
838 836
839 837 def _match(self, id):
840 838 if isinstance(id, (long, int)):
841 839 # rev
842 840 return self.node(id)
843 841 if len(id) == 20:
844 842 # possibly a binary node
845 843 # odds of a binary node being all hex in ASCII are 1 in 10**25
846 844 try:
847 845 node = id
848 846 self.rev(node) # quick search the index
849 847 return node
850 848 except LookupError:
851 849 pass # may be partial hex id
852 850 try:
853 851 # str(rev)
854 852 rev = int(id)
855 853 if str(rev) != id:
856 854 raise ValueError
857 855 if rev < 0:
858 856 rev = len(self) + rev
859 857 if rev < 0 or rev >= len(self):
860 858 raise ValueError
861 859 return self.node(rev)
862 860 except (ValueError, OverflowError):
863 861 pass
864 862 if len(id) == 40:
865 863 try:
866 864 # a full hex nodeid?
867 865 node = bin(id)
868 866 self.rev(node)
869 867 return node
870 868 except (TypeError, LookupError):
871 869 pass
872 870
873 871 def _partialmatch(self, id):
874 872 if len(id) < 40:
875 873 try:
876 874 # hex(node)[:...]
877 875 l = len(id) / 2 # grab an even number of digits
878 876 bin_id = bin(id[:l*2])
879 877 nl = [n for n in self.nodemap if n[:l] == bin_id]
880 878 nl = [n for n in nl if hex(n).startswith(id)]
881 879 if len(nl) > 0:
882 880 if len(nl) == 1:
883 881 return nl[0]
884 882 raise LookupError(id, self.indexfile,
885 883 _('ambiguous identifier'))
886 884 return None
887 885 except TypeError:
888 886 pass
889 887
890 888 def lookup(self, id):
891 889 """locate a node based on:
892 890 - revision number or str(revision number)
893 891 - nodeid or subset of hex nodeid
894 892 """
895 893 n = self._match(id)
896 894 if n is not None:
897 895 return n
898 896 n = self._partialmatch(id)
899 897 if n:
900 898 return n
901 899
902 900 raise LookupError(id, self.indexfile, _('no match found'))
903 901
904 902 def cmp(self, node, text):
905 903 """compare text with a given file revision"""
906 904 p1, p2 = self.parents(node)
907 905 return hash(text, p1, p2) != node
908 906
909 907 def chunk(self, rev, df=None):
910 908 def loadcache(df):
911 909 if not df:
912 910 if self._inline:
913 911 df = self.opener(self.indexfile)
914 912 else:
915 913 df = self.opener(self.datafile)
916 914 df.seek(start)
917 915 self._chunkcache = (start, df.read(cache_length))
918 916
919 917 start, length = self.start(rev), self.length(rev)
920 918 if self._inline:
921 919 start += (rev + 1) * self._io.size
922 920 end = start + length
923 921
924 922 offset = 0
925 923 if not self._chunkcache:
926 924 cache_length = max(65536, length)
927 925 loadcache(df)
928 926 else:
929 927 cache_start = self._chunkcache[0]
930 928 cache_length = len(self._chunkcache[1])
931 929 cache_end = cache_start + cache_length
932 930 if start >= cache_start and end <= cache_end:
933 931 # it is cached
934 932 offset = start - cache_start
935 933 else:
936 934 cache_length = max(65536, length)
937 935 loadcache(df)
938 936
939 937 # avoid copying large chunks
940 938 c = self._chunkcache[1]
941 939 if cache_length != length:
942 940 c = c[offset:offset + length]
943 941
944 942 return decompress(c)
945 943
946 944 def revdiff(self, rev1, rev2):
947 945 """return or calculate a delta between two revisions"""
948 946 if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
949 947 return self.chunk(rev2)
950 948
951 949 return mdiff.textdiff(self.revision(self.node(rev1)),
952 950 self.revision(self.node(rev2)))
953 951
954 952 def revision(self, node):
955 953 """return an uncompressed revision of a given node"""
956 954 if node == nullid:
957 955 return ""
958 956 if self._cache and self._cache[0] == node:
959 957 return str(self._cache[2])
960 958
961 959 # look up what we need to read
962 960 text = None
963 961 rev = self.rev(node)
964 962 base = self.base(rev)
965 963
966 964 # check rev flags
967 965 if self.index[rev][0] & 0xFFFF:
968 966 raise RevlogError(_('incompatible revision flag %x') %
969 967 (self.index[rev][0] & 0xFFFF))
970 968
971 969 df = None
972 970
973 971 # do we have useful data cached?
974 972 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
975 973 base = self._cache[1]
976 974 text = str(self._cache[2])
977 975 self._loadindex(base, rev + 1)
978 976 if not self._inline and rev > base + 1:
979 977 df = self.opener(self.datafile)
980 978 else:
981 979 self._loadindex(base, rev + 1)
982 980 if not self._inline and rev > base:
983 981 df = self.opener(self.datafile)
984 982 text = self.chunk(base, df=df)
985 983
986 984 bins = [self.chunk(r, df) for r in xrange(base + 1, rev + 1)]
987 985 text = mdiff.patches(text, bins)
988 986 p1, p2 = self.parents(node)
989 987 if node != hash(text, p1, p2):
990 988 raise RevlogError(_("integrity check failed on %s:%d")
991 989 % (self.datafile, rev))
992 990
993 991 self._cache = (node, rev, text)
994 992 return text
995 993
996 994 def checkinlinesize(self, tr, fp=None):
997 995 if not self._inline:
998 996 return
999 997 if not fp:
1000 998 fp = self.opener(self.indexfile, 'r')
1001 999 fp.seek(0, 2)
1002 1000 size = fp.tell()
1003 1001 if size < 131072:
1004 1002 return
1005 1003 trinfo = tr.find(self.indexfile)
1006 1004 if trinfo == None:
1007 1005 raise RevlogError(_("%s not found in the transaction")
1008 1006 % self.indexfile)
1009 1007
1010 1008 trindex = trinfo[2]
1011 1009 dataoff = self.start(trindex)
1012 1010
1013 1011 tr.add(self.datafile, dataoff)
1014 1012 df = self.opener(self.datafile, 'w')
1015 1013 try:
1016 1014 calc = self._io.size
1017 1015 for r in self:
1018 1016 start = self.start(r) + (r + 1) * calc
1019 1017 length = self.length(r)
1020 1018 fp.seek(start)
1021 1019 d = fp.read(length)
1022 1020 df.write(d)
1023 1021 finally:
1024 1022 df.close()
1025 1023
1026 1024 fp.close()
1027 1025 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1028 1026 self.version &= ~(REVLOGNGINLINEDATA)
1029 1027 self._inline = False
1030 1028 for i in self:
1031 1029 e = self._io.packentry(self.index[i], self.node, self.version, i)
1032 1030 fp.write(e)
1033 1031
1034 1032 # if we don't call rename, the temp file will never replace the
1035 1033 # real index
1036 1034 fp.rename()
1037 1035
1038 1036 tr.replace(self.indexfile, trindex * calc)
1039 1037 self._chunkcache = None
1040 1038
1041 1039 def addrevision(self, text, transaction, link, p1, p2, d=None):
1042 1040 """add a revision to the log
1043 1041
1044 1042 text - the revision data to add
1045 1043 transaction - the transaction object used for rollback
1046 1044 link - the linkrev data to add
1047 1045 p1, p2 - the parent nodeids of the revision
1048 1046 d - an optional precomputed delta
1049 1047 """
1050 1048 dfh = None
1051 1049 if not self._inline:
1052 1050 dfh = self.opener(self.datafile, "a")
1053 1051 ifh = self.opener(self.indexfile, "a+")
1054 1052 try:
1055 1053 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1056 1054 finally:
1057 1055 if dfh:
1058 1056 dfh.close()
1059 1057 ifh.close()
1060 1058
1061 1059 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1062 1060 node = hash(text, p1, p2)
1063 1061 if node in self.nodemap:
1064 1062 return node
1065 1063
1066 1064 curr = len(self)
1067 1065 prev = curr - 1
1068 1066 base = self.base(prev)
1069 1067 offset = self.end(prev)
1070 1068
1071 1069 if curr:
1072 1070 if not d:
1073 1071 ptext = self.revision(self.node(prev))
1074 1072 d = mdiff.textdiff(ptext, text)
1075 1073 data = compress(d)
1076 1074 l = len(data[1]) + len(data[0])
1077 1075 dist = l + offset - self.start(base)
1078 1076
1079 1077 # full versions are inserted when the needed deltas
1080 1078 # become comparable to the uncompressed text
1081 1079 if not curr or dist > len(text) * 2:
1082 1080 data = compress(text)
1083 1081 l = len(data[1]) + len(data[0])
1084 1082 base = curr
1085 1083
1086 1084 e = (offset_type(offset, 0), l, len(text),
1087 1085 base, link, self.rev(p1), self.rev(p2), node)
1088 1086 self.index.insert(-1, e)
1089 1087 self.nodemap[node] = curr
1090 1088
1091 1089 entry = self._io.packentry(e, self.node, self.version, curr)
1092 1090 if not self._inline:
1093 1091 transaction.add(self.datafile, offset)
1094 1092 transaction.add(self.indexfile, curr * len(entry))
1095 1093 if data[0]:
1096 1094 dfh.write(data[0])
1097 1095 dfh.write(data[1])
1098 1096 dfh.flush()
1099 1097 ifh.write(entry)
1100 1098 else:
1101 1099 offset += curr * self._io.size
1102 1100 transaction.add(self.indexfile, offset, curr)
1103 1101 ifh.write(entry)
1104 1102 ifh.write(data[0])
1105 1103 ifh.write(data[1])
1106 1104 self.checkinlinesize(transaction, ifh)
1107 1105
1108 1106 self._cache = (node, curr, text)
1109 1107 return node
1110 1108
1111 1109 def ancestor(self, a, b):
1112 1110 """calculate the least common ancestor of nodes a and b"""
1113 1111
1114 1112 def parents(rev):
1115 1113 return [p for p in self.parentrevs(rev) if p != nullrev]
1116 1114
1117 1115 c = ancestor.ancestor(self.rev(a), self.rev(b), parents)
1118 1116 if c is None:
1119 1117 return nullid
1120 1118
1121 1119 return self.node(c)
1122 1120
1123 1121 def group(self, nodelist, lookup, infocollect=None):
1124 1122 """calculate a delta group
1125 1123
1126 1124 Given a list of changeset revs, return a set of deltas and
1127 1125 metadata corresponding to nodes. the first delta is
1128 1126 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1129 1127 have this parent as it has all history before these
1130 1128 changesets. parent is parent[0]
1131 1129 """
1132 1130 revs = [self.rev(n) for n in nodelist]
1133 1131
1134 1132 # if we don't have any revisions touched by these changesets, bail
1135 1133 if not revs:
1136 1134 yield changegroup.closechunk()
1137 1135 return
1138 1136
1139 1137 # add the parent of the first rev
1140 1138 p = self.parents(self.node(revs[0]))[0]
1141 1139 revs.insert(0, self.rev(p))
1142 1140
1143 1141 # build deltas
1144 1142 for d in xrange(0, len(revs) - 1):
1145 1143 a, b = revs[d], revs[d + 1]
1146 1144 nb = self.node(b)
1147 1145
1148 1146 if infocollect is not None:
1149 1147 infocollect(nb)
1150 1148
1151 1149 p = self.parents(nb)
1152 1150 meta = nb + p[0] + p[1] + lookup(nb)
1153 1151 if a == -1:
1154 1152 d = self.revision(nb)
1155 1153 meta += mdiff.trivialdiffheader(len(d))
1156 1154 else:
1157 1155 d = self.revdiff(a, b)
1158 1156 yield changegroup.chunkheader(len(meta) + len(d))
1159 1157 yield meta
1160 1158 if len(d) > 2**20:
1161 1159 pos = 0
1162 1160 while pos < len(d):
1163 1161 pos2 = pos + 2 ** 18
1164 1162 yield d[pos:pos2]
1165 1163 pos = pos2
1166 1164 else:
1167 1165 yield d
1168 1166
1169 1167 yield changegroup.closechunk()
1170 1168
1171 1169 def addgroup(self, revs, linkmapper, transaction):
1172 1170 """
1173 1171 add a delta group
1174 1172
1175 1173 given a set of deltas, add them to the revision log. the
1176 1174 first delta is against its parent, which should be in our
1177 1175 log, the rest are against the previous delta.
1178 1176 """
1179 1177
1180 1178 #track the base of the current delta log
1181 1179 r = len(self)
1182 1180 t = r - 1
1183 1181 node = None
1184 1182
1185 1183 base = prev = nullrev
1186 1184 start = end = textlen = 0
1187 1185 if r:
1188 1186 end = self.end(t)
1189 1187
1190 1188 ifh = self.opener(self.indexfile, "a+")
1191 1189 isize = r * self._io.size
1192 1190 if self._inline:
1193 1191 transaction.add(self.indexfile, end + isize, r)
1194 1192 dfh = None
1195 1193 else:
1196 1194 transaction.add(self.indexfile, isize, r)
1197 1195 transaction.add(self.datafile, end)
1198 1196 dfh = self.opener(self.datafile, "a")
1199 1197
1200 1198 try:
1201 1199 # loop through our set of deltas
1202 1200 chain = None
1203 1201 for chunk in revs:
1204 1202 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1205 1203 link = linkmapper(cs)
1206 1204 if node in self.nodemap:
1207 1205 # this can happen if two branches make the same change
1208 1206 chain = node
1209 1207 continue
1210 1208 delta = buffer(chunk, 80)
1211 1209 del chunk
1212 1210
1213 1211 for p in (p1, p2):
1214 1212 if not p in self.nodemap:
1215 1213 raise LookupError(p, self.indexfile, _('unknown parent'))
1216 1214
1217 1215 if not chain:
1218 1216 # retrieve the parent revision of the delta chain
1219 1217 chain = p1
1220 1218 if not chain in self.nodemap:
1221 1219 raise LookupError(chain, self.indexfile, _('unknown base'))
1222 1220
1223 1221 # full versions are inserted when the needed deltas become
1224 1222 # comparable to the uncompressed text or when the previous
1225 1223 # version is not the one we have a delta against. We use
1226 1224 # the size of the previous full rev as a proxy for the
1227 1225 # current size.
1228 1226
1229 1227 if chain == prev:
1230 1228 cdelta = compress(delta)
1231 1229 cdeltalen = len(cdelta[0]) + len(cdelta[1])
1232 1230 textlen = mdiff.patchedsize(textlen, delta)
1233 1231
1234 1232 if chain != prev or (end - start + cdeltalen) > textlen * 2:
1235 1233 # flush our writes here so we can read it in revision
1236 1234 if dfh:
1237 1235 dfh.flush()
1238 1236 ifh.flush()
1239 1237 text = self.revision(chain)
1240 1238 if len(text) == 0:
1241 1239 # skip over trivial delta header
1242 1240 text = buffer(delta, 12)
1243 1241 else:
1244 1242 text = mdiff.patches(text, [delta])
1245 1243 del delta
1246 1244 chk = self._addrevision(text, transaction, link, p1, p2, None,
1247 1245 ifh, dfh)
1248 1246 if not dfh and not self._inline:
1249 1247 # addrevision switched from inline to conventional
1250 1248 # reopen the index
1251 1249 dfh = self.opener(self.datafile, "a")
1252 1250 ifh = self.opener(self.indexfile, "a")
1253 1251 if chk != node:
1254 1252 raise RevlogError(_("consistency error adding group"))
1255 1253 textlen = len(text)
1256 1254 else:
1257 1255 e = (offset_type(end, 0), cdeltalen, textlen, base,
1258 1256 link, self.rev(p1), self.rev(p2), node)
1259 1257 self.index.insert(-1, e)
1260 1258 self.nodemap[node] = r
1261 1259 entry = self._io.packentry(e, self.node, self.version, r)
1262 1260 if self._inline:
1263 1261 ifh.write(entry)
1264 1262 ifh.write(cdelta[0])
1265 1263 ifh.write(cdelta[1])
1266 1264 self.checkinlinesize(transaction, ifh)
1267 1265 if not self._inline:
1268 1266 dfh = self.opener(self.datafile, "a")
1269 1267 ifh = self.opener(self.indexfile, "a")
1270 1268 else:
1271 1269 dfh.write(cdelta[0])
1272 1270 dfh.write(cdelta[1])
1273 1271 ifh.write(entry)
1274 1272
1275 1273 t, r, chain, prev = r, r + 1, node, node
1276 1274 base = self.base(t)
1277 1275 start = self.start(base)
1278 1276 end = self.end(t)
1279 1277 finally:
1280 1278 if dfh:
1281 1279 dfh.close()
1282 1280 ifh.close()
1283 1281
1284 1282 return node
1285 1283
1286 1284 def strip(self, minlink, transaction):
1287 1285 """truncate the revlog on the first revision with a linkrev >= minlink
1288 1286
1289 1287 This function is called when we're stripping revision minlink and
1290 1288 its descendants from the repository.
1291 1289
1292 1290 We have to remove all revisions with linkrev >= minlink, because
1293 1291 the equivalent changelog revisions will be renumbered after the
1294 1292 strip.
1295 1293
1296 1294 So we truncate the revlog on the first of these revisions, and
1297 1295 trust that the caller has saved the revisions that shouldn't be
1298 1296 removed and that it'll readd them after this truncation.
1299 1297 """
1300 1298 if len(self) == 0:
1301 1299 return
1302 1300
1303 1301 if isinstance(self.index, lazyindex):
1304 1302 self._loadindexmap()
1305 1303
1306 1304 for rev in self:
1307 1305 if self.index[rev][4] >= minlink:
1308 1306 break
1309 1307 else:
1310 1308 return
1311 1309
1312 1310 # first truncate the files on disk
1313 1311 end = self.start(rev)
1314 1312 if not self._inline:
1315 1313 transaction.add(self.datafile, end)
1316 1314 end = rev * self._io.size
1317 1315 else:
1318 1316 end += rev * self._io.size
1319 1317
1320 1318 transaction.add(self.indexfile, end)
1321 1319
1322 1320 # then reset internal state in memory to forget those revisions
1323 1321 self._cache = None
1324 1322 self._chunkcache = None
1325 1323 for x in xrange(rev, len(self)):
1326 1324 del self.nodemap[self.node(x)]
1327 1325
1328 1326 del self.index[rev:-1]
1329 1327
1330 1328 def checksize(self):
1331 1329 expected = 0
1332 1330 if len(self):
1333 1331 expected = max(0, self.end(len(self) - 1))
1334 1332
1335 1333 try:
1336 1334 f = self.opener(self.datafile)
1337 1335 f.seek(0, 2)
1338 1336 actual = f.tell()
1339 1337 dd = actual - expected
1340 1338 except IOError, inst:
1341 1339 if inst.errno != errno.ENOENT:
1342 1340 raise
1343 1341 dd = 0
1344 1342
1345 1343 try:
1346 1344 f = self.opener(self.indexfile)
1347 1345 f.seek(0, 2)
1348 1346 actual = f.tell()
1349 1347 s = self._io.size
1350 1348 i = max(0, actual / s)
1351 1349 di = actual - (i * s)
1352 1350 if self._inline:
1353 1351 databytes = 0
1354 1352 for r in self:
1355 1353 databytes += max(0, self.length(r))
1356 1354 dd = 0
1357 1355 di = actual - len(self) * s - databytes
1358 1356 except IOError, inst:
1359 1357 if inst.errno != errno.ENOENT:
1360 1358 raise
1361 1359 di = 0
1362 1360
1363 1361 return (dd, di)
1364 1362
1365 1363 def files(self):
1366 1364 res = [ self.indexfile ]
1367 1365 if not self._inline:
1368 1366 res.append(self.datafile)
1369 1367 return res
@@ -1,1477 +1,1475 b''
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
8 This software may be used and distributed according to the terms of the
9 GNU General Public License version 2, incorporated herein by reference.
10
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
13 """
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2, incorporated herein by reference.
9 #
10 # This contains helper routines that are independent of the SCM core and hide
11 # platform-specific details from the core.
14 12
15 13 from i18n import _
16 14 import cStringIO, errno, re, shutil, sys, tempfile, traceback, error
17 15 import os, stat, threading, time, calendar, glob, osutil
18 16 import imp
19 17
20 18 # Python compatibility
21 19
22 20 _md5 = None
23 21 def md5(s):
24 22 global _md5
25 23 if _md5 is None:
26 24 try:
27 25 import hashlib
28 26 _md5 = hashlib.md5
29 27 except ImportError:
30 28 import md5
31 29 _md5 = md5.md5
32 30 return _md5(s)
33 31
34 32 _sha1 = None
35 33 def sha1(s):
36 34 global _sha1
37 35 if _sha1 is None:
38 36 try:
39 37 import hashlib
40 38 _sha1 = hashlib.sha1
41 39 except ImportError:
42 40 import sha
43 41 _sha1 = sha.sha
44 42 return _sha1(s)
45 43
46 44 try:
47 45 import subprocess
48 46 subprocess.Popen # trigger ImportError early
49 47 closefds = os.name == 'posix'
50 48 def popen2(cmd, mode='t', bufsize=-1):
51 49 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
52 50 close_fds=closefds,
53 51 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
54 52 return p.stdin, p.stdout
55 53 def popen3(cmd, mode='t', bufsize=-1):
56 54 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
57 55 close_fds=closefds,
58 56 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
59 57 stderr=subprocess.PIPE)
60 58 return p.stdin, p.stdout, p.stderr
61 59 def Popen3(cmd, capturestderr=False, bufsize=-1):
62 60 stderr = capturestderr and subprocess.PIPE or None
63 61 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
64 62 close_fds=closefds,
65 63 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
66 64 stderr=stderr)
67 65 p.fromchild = p.stdout
68 66 p.tochild = p.stdin
69 67 p.childerr = p.stderr
70 68 return p
71 69 except ImportError:
72 70 subprocess = None
73 71 from popen2 import Popen3
74 72 popen2 = os.popen2
75 73 popen3 = os.popen3
76 74
77 75
78 76 def version():
79 77 """Return version information if available."""
80 78 try:
81 79 import __version__
82 80 return __version__.version
83 81 except ImportError:
84 82 return 'unknown'
85 83
86 84 # used by parsedate
87 85 defaultdateformats = (
88 86 '%Y-%m-%d %H:%M:%S',
89 87 '%Y-%m-%d %I:%M:%S%p',
90 88 '%Y-%m-%d %H:%M',
91 89 '%Y-%m-%d %I:%M%p',
92 90 '%Y-%m-%d',
93 91 '%m-%d',
94 92 '%m/%d',
95 93 '%m/%d/%y',
96 94 '%m/%d/%Y',
97 95 '%a %b %d %H:%M:%S %Y',
98 96 '%a %b %d %I:%M:%S%p %Y',
99 97 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
100 98 '%b %d %H:%M:%S %Y',
101 99 '%b %d %I:%M:%S%p %Y',
102 100 '%b %d %H:%M:%S',
103 101 '%b %d %I:%M:%S%p',
104 102 '%b %d %H:%M',
105 103 '%b %d %I:%M%p',
106 104 '%b %d %Y',
107 105 '%b %d',
108 106 '%H:%M:%S',
109 107 '%I:%M:%SP',
110 108 '%H:%M',
111 109 '%I:%M%p',
112 110 )
113 111
114 112 extendeddateformats = defaultdateformats + (
115 113 "%Y",
116 114 "%Y-%m",
117 115 "%b",
118 116 "%b %Y",
119 117 )
120 118
121 119 def cachefunc(func):
122 120 '''cache the result of function calls'''
123 121 # XXX doesn't handle keywords args
124 122 cache = {}
125 123 if func.func_code.co_argcount == 1:
126 124 # we gain a small amount of time because
127 125 # we don't need to pack/unpack the list
128 126 def f(arg):
129 127 if arg not in cache:
130 128 cache[arg] = func(arg)
131 129 return cache[arg]
132 130 else:
133 131 def f(*args):
134 132 if args not in cache:
135 133 cache[args] = func(*args)
136 134 return cache[args]
137 135
138 136 return f
139 137
140 138 class propertycache(object):
141 139 def __init__(self, func):
142 140 self.func = func
143 141 self.name = func.__name__
144 142 def __get__(self, obj, type=None):
145 143 result = self.func(obj)
146 144 setattr(obj, self.name, result)
147 145 return result
148 146
149 147 def pipefilter(s, cmd):
150 148 '''filter string S through command CMD, returning its output'''
151 149 (pin, pout) = popen2(cmd, 'b')
152 150 def writer():
153 151 try:
154 152 pin.write(s)
155 153 pin.close()
156 154 except IOError, inst:
157 155 if inst.errno != errno.EPIPE:
158 156 raise
159 157
160 158 # we should use select instead on UNIX, but this will work on most
161 159 # systems, including Windows
162 160 w = threading.Thread(target=writer)
163 161 w.start()
164 162 f = pout.read()
165 163 pout.close()
166 164 w.join()
167 165 return f
168 166
169 167 def tempfilter(s, cmd):
170 168 '''filter string S through a pair of temporary files with CMD.
171 169 CMD is used as a template to create the real command to be run,
172 170 with the strings INFILE and OUTFILE replaced by the real names of
173 171 the temporary files generated.'''
174 172 inname, outname = None, None
175 173 try:
176 174 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
177 175 fp = os.fdopen(infd, 'wb')
178 176 fp.write(s)
179 177 fp.close()
180 178 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
181 179 os.close(outfd)
182 180 cmd = cmd.replace('INFILE', inname)
183 181 cmd = cmd.replace('OUTFILE', outname)
184 182 code = os.system(cmd)
185 183 if sys.platform == 'OpenVMS' and code & 1:
186 184 code = 0
187 185 if code: raise Abort(_("command '%s' failed: %s") %
188 186 (cmd, explain_exit(code)))
189 187 return open(outname, 'rb').read()
190 188 finally:
191 189 try:
192 190 if inname: os.unlink(inname)
193 191 except: pass
194 192 try:
195 193 if outname: os.unlink(outname)
196 194 except: pass
197 195
198 196 filtertable = {
199 197 'tempfile:': tempfilter,
200 198 'pipe:': pipefilter,
201 199 }
202 200
203 201 def filter(s, cmd):
204 202 "filter a string through a command that transforms its input to its output"
205 203 for name, fn in filtertable.iteritems():
206 204 if cmd.startswith(name):
207 205 return fn(s, cmd[len(name):].lstrip())
208 206 return pipefilter(s, cmd)
209 207
210 208 def binary(s):
211 209 """return true if a string is binary data"""
212 210 return bool(s and '\0' in s)
213 211
214 212 def increasingchunks(source, min=1024, max=65536):
215 213 '''return no less than min bytes per chunk while data remains,
216 214 doubling min after each chunk until it reaches max'''
217 215 def log2(x):
218 216 if not x:
219 217 return 0
220 218 i = 0
221 219 while x:
222 220 x >>= 1
223 221 i += 1
224 222 return i - 1
225 223
226 224 buf = []
227 225 blen = 0
228 226 for chunk in source:
229 227 buf.append(chunk)
230 228 blen += len(chunk)
231 229 if blen >= min:
232 230 if min < max:
233 231 min = min << 1
234 232 nmin = 1 << log2(blen)
235 233 if nmin > min:
236 234 min = nmin
237 235 if min > max:
238 236 min = max
239 237 yield ''.join(buf)
240 238 blen = 0
241 239 buf = []
242 240 if buf:
243 241 yield ''.join(buf)
244 242
245 243 Abort = error.Abort
246 244
247 245 def always(fn): return True
248 246 def never(fn): return False
249 247
250 248 def patkind(name, default):
251 249 """Split a string into an optional pattern kind prefix and the
252 250 actual pattern."""
253 251 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
254 252 if name.startswith(prefix + ':'): return name.split(':', 1)
255 253 return default, name
256 254
257 255 def globre(pat, head='^', tail='$'):
258 256 "convert a glob pattern into a regexp"
259 257 i, n = 0, len(pat)
260 258 res = ''
261 259 group = 0
262 260 def peek(): return i < n and pat[i]
263 261 while i < n:
264 262 c = pat[i]
265 263 i = i+1
266 264 if c == '*':
267 265 if peek() == '*':
268 266 i += 1
269 267 res += '.*'
270 268 else:
271 269 res += '[^/]*'
272 270 elif c == '?':
273 271 res += '.'
274 272 elif c == '[':
275 273 j = i
276 274 if j < n and pat[j] in '!]':
277 275 j += 1
278 276 while j < n and pat[j] != ']':
279 277 j += 1
280 278 if j >= n:
281 279 res += '\\['
282 280 else:
283 281 stuff = pat[i:j].replace('\\','\\\\')
284 282 i = j + 1
285 283 if stuff[0] == '!':
286 284 stuff = '^' + stuff[1:]
287 285 elif stuff[0] == '^':
288 286 stuff = '\\' + stuff
289 287 res = '%s[%s]' % (res, stuff)
290 288 elif c == '{':
291 289 group += 1
292 290 res += '(?:'
293 291 elif c == '}' and group:
294 292 res += ')'
295 293 group -= 1
296 294 elif c == ',' and group:
297 295 res += '|'
298 296 elif c == '\\':
299 297 p = peek()
300 298 if p:
301 299 i += 1
302 300 res += re.escape(p)
303 301 else:
304 302 res += re.escape(c)
305 303 else:
306 304 res += re.escape(c)
307 305 return head + res + tail
308 306
309 307 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
310 308
311 309 def pathto(root, n1, n2):
312 310 '''return the relative path from one place to another.
313 311 root should use os.sep to separate directories
314 312 n1 should use os.sep to separate directories
315 313 n2 should use "/" to separate directories
316 314 returns an os.sep-separated path.
317 315
318 316 If n1 is a relative path, it's assumed it's
319 317 relative to root.
320 318 n2 should always be relative to root.
321 319 '''
322 320 if not n1: return localpath(n2)
323 321 if os.path.isabs(n1):
324 322 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
325 323 return os.path.join(root, localpath(n2))
326 324 n2 = '/'.join((pconvert(root), n2))
327 325 a, b = splitpath(n1), n2.split('/')
328 326 a.reverse()
329 327 b.reverse()
330 328 while a and b and a[-1] == b[-1]:
331 329 a.pop()
332 330 b.pop()
333 331 b.reverse()
334 332 return os.sep.join((['..'] * len(a)) + b) or '.'
335 333
336 334 def canonpath(root, cwd, myname):
337 335 """return the canonical path of myname, given cwd and root"""
338 336 if root == os.sep:
339 337 rootsep = os.sep
340 338 elif endswithsep(root):
341 339 rootsep = root
342 340 else:
343 341 rootsep = root + os.sep
344 342 name = myname
345 343 if not os.path.isabs(name):
346 344 name = os.path.join(root, cwd, name)
347 345 name = os.path.normpath(name)
348 346 audit_path = path_auditor(root)
349 347 if name != rootsep and name.startswith(rootsep):
350 348 name = name[len(rootsep):]
351 349 audit_path(name)
352 350 return pconvert(name)
353 351 elif name == root:
354 352 return ''
355 353 else:
356 354 # Determine whether `name' is in the hierarchy at or beneath `root',
357 355 # by iterating name=dirname(name) until that causes no change (can't
358 356 # check name == '/', because that doesn't work on windows). For each
359 357 # `name', compare dev/inode numbers. If they match, the list `rel'
360 358 # holds the reversed list of components making up the relative file
361 359 # name we want.
362 360 root_st = os.stat(root)
363 361 rel = []
364 362 while True:
365 363 try:
366 364 name_st = os.stat(name)
367 365 except OSError:
368 366 break
369 367 if samestat(name_st, root_st):
370 368 if not rel:
371 369 # name was actually the same as root (maybe a symlink)
372 370 return ''
373 371 rel.reverse()
374 372 name = os.path.join(*rel)
375 373 audit_path(name)
376 374 return pconvert(name)
377 375 dirname, basename = os.path.split(name)
378 376 rel.append(basename)
379 377 if dirname == name:
380 378 break
381 379 name = dirname
382 380
383 381 raise Abort('%s not under root' % myname)
384 382
385 383 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
386 384 """build a function to match a set of file patterns
387 385
388 386 arguments:
389 387 canonroot - the canonical root of the tree you're matching against
390 388 cwd - the current working directory, if relevant
391 389 names - patterns to find
392 390 inc - patterns to include
393 391 exc - patterns to exclude
394 392 dflt_pat - if a pattern in names has no explicit type, assume this one
395 393 src - where these patterns came from (e.g. .hgignore)
396 394
397 395 a pattern is one of:
398 396 'glob:<glob>' - a glob relative to cwd
399 397 're:<regexp>' - a regular expression
400 398 'path:<path>' - a path relative to canonroot
401 399 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
402 400 'relpath:<path>' - a path relative to cwd
403 401 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
404 402 '<something>' - one of the cases above, selected by the dflt_pat argument
405 403
406 404 returns:
407 405 a 3-tuple containing
408 406 - list of roots (places where one should start a recursive walk of the fs);
409 407 this often matches the explicit non-pattern names passed in, but also
410 408 includes the initial part of glob: patterns that has no glob characters
411 409 - a bool match(filename) function
412 410 - a bool indicating if any patterns were passed in
413 411 """
414 412
415 413 # a common case: no patterns at all
416 414 if not names and not inc and not exc:
417 415 return [], always, False
418 416
419 417 def contains_glob(name):
420 418 for c in name:
421 419 if c in _globchars: return True
422 420 return False
423 421
424 422 def regex(kind, name, tail):
425 423 '''convert a pattern into a regular expression'''
426 424 if not name:
427 425 return ''
428 426 if kind == 're':
429 427 return name
430 428 elif kind == 'path':
431 429 return '^' + re.escape(name) + '(?:/|$)'
432 430 elif kind == 'relglob':
433 431 return globre(name, '(?:|.*/)', tail)
434 432 elif kind == 'relpath':
435 433 return re.escape(name) + '(?:/|$)'
436 434 elif kind == 'relre':
437 435 if name.startswith('^'):
438 436 return name
439 437 return '.*' + name
440 438 return globre(name, '', tail)
441 439
442 440 def matchfn(pats, tail):
443 441 """build a matching function from a set of patterns"""
444 442 if not pats:
445 443 return
446 444 try:
447 445 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
448 446 if len(pat) > 20000:
449 447 raise OverflowError()
450 448 return re.compile(pat).match
451 449 except OverflowError:
452 450 # We're using a Python with a tiny regex engine and we
453 451 # made it explode, so we'll divide the pattern list in two
454 452 # until it works
455 453 l = len(pats)
456 454 if l < 2:
457 455 raise
458 456 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
459 457 return lambda s: a(s) or b(s)
460 458 except re.error:
461 459 for k, p in pats:
462 460 try:
463 461 re.compile('(?:%s)' % regex(k, p, tail))
464 462 except re.error:
465 463 if src:
466 464 raise Abort("%s: invalid pattern (%s): %s" %
467 465 (src, k, p))
468 466 else:
469 467 raise Abort("invalid pattern (%s): %s" % (k, p))
470 468 raise Abort("invalid pattern")
471 469
472 470 def globprefix(pat):
473 471 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
474 472 root = []
475 473 for p in pat.split('/'):
476 474 if contains_glob(p): break
477 475 root.append(p)
478 476 return '/'.join(root) or '.'
479 477
480 478 def normalizepats(names, default):
481 479 pats = []
482 480 roots = []
483 481 anypats = False
484 482 for kind, name in [patkind(p, default) for p in names]:
485 483 if kind in ('glob', 'relpath'):
486 484 name = canonpath(canonroot, cwd, name)
487 485 elif kind in ('relglob', 'path'):
488 486 name = normpath(name)
489 487
490 488 pats.append((kind, name))
491 489
492 490 if kind in ('glob', 're', 'relglob', 'relre'):
493 491 anypats = True
494 492
495 493 if kind == 'glob':
496 494 root = globprefix(name)
497 495 roots.append(root)
498 496 elif kind in ('relpath', 'path'):
499 497 roots.append(name or '.')
500 498 elif kind == 'relglob':
501 499 roots.append('.')
502 500 return roots, pats, anypats
503 501
504 502 roots, pats, anypats = normalizepats(names, dflt_pat)
505 503
506 504 patmatch = matchfn(pats, '$') or always
507 505 incmatch = always
508 506 if inc:
509 507 dummy, inckinds, dummy = normalizepats(inc, 'glob')
510 508 incmatch = matchfn(inckinds, '(?:/|$)')
511 509 excmatch = never
512 510 if exc:
513 511 dummy, exckinds, dummy = normalizepats(exc, 'glob')
514 512 excmatch = matchfn(exckinds, '(?:/|$)')
515 513
516 514 if not names and inc and not exc:
517 515 # common case: hgignore patterns
518 516 match = incmatch
519 517 else:
520 518 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
521 519
522 520 return (roots, match, (inc or exc or anypats) and True)
523 521
524 522 _hgexecutable = None
525 523
526 524 def main_is_frozen():
527 525 """return True if we are a frozen executable.
528 526
529 527 The code supports py2exe (most common, Windows only) and tools/freeze
530 528 (portable, not much used).
531 529 """
532 530 return (hasattr(sys, "frozen") or # new py2exe
533 531 hasattr(sys, "importers") or # old py2exe
534 532 imp.is_frozen("__main__")) # tools/freeze
535 533
536 534 def hgexecutable():
537 535 """return location of the 'hg' executable.
538 536
539 537 Defaults to $HG or 'hg' in the search path.
540 538 """
541 539 if _hgexecutable is None:
542 540 hg = os.environ.get('HG')
543 541 if hg:
544 542 set_hgexecutable(hg)
545 543 elif main_is_frozen():
546 544 set_hgexecutable(sys.executable)
547 545 else:
548 546 set_hgexecutable(find_exe('hg') or 'hg')
549 547 return _hgexecutable
550 548
551 549 def set_hgexecutable(path):
552 550 """set location of the 'hg' executable"""
553 551 global _hgexecutable
554 552 _hgexecutable = path
555 553
556 554 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
557 555 '''enhanced shell command execution.
558 556 run with environment maybe modified, maybe in different dir.
559 557
560 558 if command fails and onerr is None, return status. if ui object,
561 559 print error message and return status, else raise onerr object as
562 560 exception.'''
563 561 def py2shell(val):
564 562 'convert python object into string that is useful to shell'
565 563 if val in (None, False):
566 564 return '0'
567 565 if val == True:
568 566 return '1'
569 567 return str(val)
570 568 oldenv = {}
571 569 for k in environ:
572 570 oldenv[k] = os.environ.get(k)
573 571 if cwd is not None:
574 572 oldcwd = os.getcwd()
575 573 origcmd = cmd
576 574 if os.name == 'nt':
577 575 cmd = '"%s"' % cmd
578 576 try:
579 577 for k, v in environ.iteritems():
580 578 os.environ[k] = py2shell(v)
581 579 os.environ['HG'] = hgexecutable()
582 580 if cwd is not None and oldcwd != cwd:
583 581 os.chdir(cwd)
584 582 rc = os.system(cmd)
585 583 if sys.platform == 'OpenVMS' and rc & 1:
586 584 rc = 0
587 585 if rc and onerr:
588 586 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
589 587 explain_exit(rc)[0])
590 588 if errprefix:
591 589 errmsg = '%s: %s' % (errprefix, errmsg)
592 590 try:
593 591 onerr.warn(errmsg + '\n')
594 592 except AttributeError:
595 593 raise onerr(errmsg)
596 594 return rc
597 595 finally:
598 596 for k, v in oldenv.iteritems():
599 597 if v is None:
600 598 del os.environ[k]
601 599 else:
602 600 os.environ[k] = v
603 601 if cwd is not None and oldcwd != cwd:
604 602 os.chdir(oldcwd)
605 603
606 604 def checksignature(func):
607 605 '''wrap a function with code to check for calling errors'''
608 606 def check(*args, **kwargs):
609 607 try:
610 608 return func(*args, **kwargs)
611 609 except TypeError:
612 610 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
613 611 raise error.SignatureError
614 612 raise
615 613
616 614 return check
617 615
618 616 # os.path.lexists is not available on python2.3
619 617 def lexists(filename):
620 618 "test whether a file with this name exists. does not follow symlinks"
621 619 try:
622 620 os.lstat(filename)
623 621 except:
624 622 return False
625 623 return True
626 624
627 625 def rename(src, dst):
628 626 """forcibly rename a file"""
629 627 try:
630 628 os.rename(src, dst)
631 629 except OSError, err: # FIXME: check err (EEXIST ?)
632 630 # on windows, rename to existing file is not allowed, so we
633 631 # must delete destination first. but if file is open, unlink
634 632 # schedules it for delete but does not delete it. rename
635 633 # happens immediately even for open files, so we rename
636 634 # destination to a temporary name, then delete that. then
637 635 # rename is safe to do.
638 636 temp = dst + "-force-rename"
639 637 os.rename(dst, temp)
640 638 os.unlink(temp)
641 639 os.rename(src, dst)
642 640
643 641 def unlink(f):
644 642 """unlink and remove the directory if it is empty"""
645 643 os.unlink(f)
646 644 # try removing directories that might now be empty
647 645 try:
648 646 os.removedirs(os.path.dirname(f))
649 647 except OSError:
650 648 pass
651 649
652 650 def copyfile(src, dest):
653 651 "copy a file, preserving mode and atime/mtime"
654 652 if os.path.islink(src):
655 653 try:
656 654 os.unlink(dest)
657 655 except:
658 656 pass
659 657 os.symlink(os.readlink(src), dest)
660 658 else:
661 659 try:
662 660 shutil.copyfile(src, dest)
663 661 shutil.copystat(src, dest)
664 662 except shutil.Error, inst:
665 663 raise Abort(str(inst))
666 664
667 665 def copyfiles(src, dst, hardlink=None):
668 666 """Copy a directory tree using hardlinks if possible"""
669 667
670 668 if hardlink is None:
671 669 hardlink = (os.stat(src).st_dev ==
672 670 os.stat(os.path.dirname(dst)).st_dev)
673 671
674 672 if os.path.isdir(src):
675 673 os.mkdir(dst)
676 674 for name, kind in osutil.listdir(src):
677 675 srcname = os.path.join(src, name)
678 676 dstname = os.path.join(dst, name)
679 677 copyfiles(srcname, dstname, hardlink)
680 678 else:
681 679 if hardlink:
682 680 try:
683 681 os_link(src, dst)
684 682 except (IOError, OSError):
685 683 hardlink = False
686 684 shutil.copy(src, dst)
687 685 else:
688 686 shutil.copy(src, dst)
689 687
690 688 class path_auditor(object):
691 689 '''ensure that a filesystem path contains no banned components.
692 690 the following properties of a path are checked:
693 691
694 692 - under top-level .hg
695 693 - starts at the root of a windows drive
696 694 - contains ".."
697 695 - traverses a symlink (e.g. a/symlink_here/b)
698 696 - inside a nested repository'''
699 697
700 698 def __init__(self, root):
701 699 self.audited = set()
702 700 self.auditeddir = set()
703 701 self.root = root
704 702
705 703 def __call__(self, path):
706 704 if path in self.audited:
707 705 return
708 706 normpath = os.path.normcase(path)
709 707 parts = splitpath(normpath)
710 708 if (os.path.splitdrive(path)[0]
711 709 or parts[0].lower() in ('.hg', '.hg.', '')
712 710 or os.pardir in parts):
713 711 raise Abort(_("path contains illegal component: %s") % path)
714 712 if '.hg' in path.lower():
715 713 lparts = [p.lower() for p in parts]
716 714 for p in '.hg', '.hg.':
717 715 if p in lparts[1:]:
718 716 pos = lparts.index(p)
719 717 base = os.path.join(*parts[:pos])
720 718 raise Abort(_('path %r is inside repo %r') % (path, base))
721 719 def check(prefix):
722 720 curpath = os.path.join(self.root, prefix)
723 721 try:
724 722 st = os.lstat(curpath)
725 723 except OSError, err:
726 724 # EINVAL can be raised as invalid path syntax under win32.
727 725 # They must be ignored for patterns can be checked too.
728 726 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
729 727 raise
730 728 else:
731 729 if stat.S_ISLNK(st.st_mode):
732 730 raise Abort(_('path %r traverses symbolic link %r') %
733 731 (path, prefix))
734 732 elif (stat.S_ISDIR(st.st_mode) and
735 733 os.path.isdir(os.path.join(curpath, '.hg'))):
736 734 raise Abort(_('path %r is inside repo %r') %
737 735 (path, prefix))
738 736 parts.pop()
739 737 prefixes = []
740 738 for n in range(len(parts)):
741 739 prefix = os.sep.join(parts)
742 740 if prefix in self.auditeddir:
743 741 break
744 742 check(prefix)
745 743 prefixes.append(prefix)
746 744 parts.pop()
747 745
748 746 self.audited.add(path)
749 747 # only add prefixes to the cache after checking everything: we don't
750 748 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
751 749 self.auditeddir.update(prefixes)
752 750
753 751 def nlinks(pathname):
754 752 """Return number of hardlinks for the given file."""
755 753 return os.lstat(pathname).st_nlink
756 754
757 755 if hasattr(os, 'link'):
758 756 os_link = os.link
759 757 else:
760 758 def os_link(src, dst):
761 759 raise OSError(0, _("Hardlinks not supported"))
762 760
763 761 def lookup_reg(key, name=None, scope=None):
764 762 return None
765 763
766 764 if os.name == 'nt':
767 765 from windows import *
768 766 def expand_glob(pats):
769 767 '''On Windows, expand the implicit globs in a list of patterns'''
770 768 ret = []
771 769 for p in pats:
772 770 kind, name = patkind(p, None)
773 771 if kind is None:
774 772 globbed = glob.glob(name)
775 773 if globbed:
776 774 ret.extend(globbed)
777 775 continue
778 776 # if we couldn't expand the glob, just keep it around
779 777 ret.append(p)
780 778 return ret
781 779 else:
782 780 from posix import *
783 781
784 782 def makelock(info, pathname):
785 783 try:
786 784 return os.symlink(info, pathname)
787 785 except OSError, why:
788 786 if why.errno == errno.EEXIST:
789 787 raise
790 788 except AttributeError: # no symlink in os
791 789 pass
792 790
793 791 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
794 792 os.write(ld, info)
795 793 os.close(ld)
796 794
797 795 def readlock(pathname):
798 796 try:
799 797 return os.readlink(pathname)
800 798 except OSError, why:
801 799 if why.errno not in (errno.EINVAL, errno.ENOSYS):
802 800 raise
803 801 except AttributeError: # no symlink in os
804 802 pass
805 803 return posixfile(pathname).read()
806 804
807 805 def fstat(fp):
808 806 '''stat file object that may not have fileno method.'''
809 807 try:
810 808 return os.fstat(fp.fileno())
811 809 except AttributeError:
812 810 return os.stat(fp.name)
813 811
814 812 # File system features
815 813
816 814 def checkcase(path):
817 815 """
818 816 Check whether the given path is on a case-sensitive filesystem
819 817
820 818 Requires a path (like /foo/.hg) ending with a foldable final
821 819 directory component.
822 820 """
823 821 s1 = os.stat(path)
824 822 d, b = os.path.split(path)
825 823 p2 = os.path.join(d, b.upper())
826 824 if path == p2:
827 825 p2 = os.path.join(d, b.lower())
828 826 try:
829 827 s2 = os.stat(p2)
830 828 if s2 == s1:
831 829 return False
832 830 return True
833 831 except:
834 832 return True
835 833
836 834 _fspathcache = {}
837 835 def fspath(name, root):
838 836 '''Get name in the case stored in the filesystem
839 837
840 838 The name is either relative to root, or it is an absolute path starting
841 839 with root. Note that this function is unnecessary, and should not be
842 840 called, for case-sensitive filesystems (simply because it's expensive).
843 841 '''
844 842 # If name is absolute, make it relative
845 843 if name.lower().startswith(root.lower()):
846 844 l = len(root)
847 845 if name[l] == os.sep or name[l] == os.altsep:
848 846 l = l + 1
849 847 name = name[l:]
850 848
851 849 if not os.path.exists(os.path.join(root, name)):
852 850 return None
853 851
854 852 seps = os.sep
855 853 if os.altsep:
856 854 seps = seps + os.altsep
857 855 # Protect backslashes. This gets silly very quickly.
858 856 seps.replace('\\','\\\\')
859 857 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
860 858 dir = os.path.normcase(os.path.normpath(root))
861 859 result = []
862 860 for part, sep in pattern.findall(name):
863 861 if sep:
864 862 result.append(sep)
865 863 continue
866 864
867 865 if dir not in _fspathcache:
868 866 _fspathcache[dir] = os.listdir(dir)
869 867 contents = _fspathcache[dir]
870 868
871 869 lpart = part.lower()
872 870 for n in contents:
873 871 if n.lower() == lpart:
874 872 result.append(n)
875 873 break
876 874 else:
877 875 # Cannot happen, as the file exists!
878 876 result.append(part)
879 877 dir = os.path.join(dir, lpart)
880 878
881 879 return ''.join(result)
882 880
883 881 def checkexec(path):
884 882 """
885 883 Check whether the given path is on a filesystem with UNIX-like exec flags
886 884
887 885 Requires a directory (like /foo/.hg)
888 886 """
889 887
890 888 # VFAT on some Linux versions can flip mode but it doesn't persist
891 889 # a FS remount. Frequently we can detect it if files are created
892 890 # with exec bit on.
893 891
894 892 try:
895 893 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
896 894 fh, fn = tempfile.mkstemp("", "", path)
897 895 try:
898 896 os.close(fh)
899 897 m = os.stat(fn).st_mode & 0777
900 898 new_file_has_exec = m & EXECFLAGS
901 899 os.chmod(fn, m ^ EXECFLAGS)
902 900 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
903 901 finally:
904 902 os.unlink(fn)
905 903 except (IOError, OSError):
906 904 # we don't care, the user probably won't be able to commit anyway
907 905 return False
908 906 return not (new_file_has_exec or exec_flags_cannot_flip)
909 907
910 908 def checklink(path):
911 909 """check whether the given path is on a symlink-capable filesystem"""
912 910 # mktemp is not racy because symlink creation will fail if the
913 911 # file already exists
914 912 name = tempfile.mktemp(dir=path)
915 913 try:
916 914 os.symlink(".", name)
917 915 os.unlink(name)
918 916 return True
919 917 except (OSError, AttributeError):
920 918 return False
921 919
922 920 def needbinarypatch():
923 921 """return True if patches should be applied in binary mode by default."""
924 922 return os.name == 'nt'
925 923
926 924 def endswithsep(path):
927 925 '''Check path ends with os.sep or os.altsep.'''
928 926 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
929 927
930 928 def splitpath(path):
931 929 '''Split path by os.sep.
932 930 Note that this function does not use os.altsep because this is
933 931 an alternative of simple "xxx.split(os.sep)".
934 932 It is recommended to use os.path.normpath() before using this
935 933 function if need.'''
936 934 return path.split(os.sep)
937 935
938 936 def gui():
939 937 '''Are we running in a GUI?'''
940 938 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
941 939
942 940 def mktempcopy(name, emptyok=False, createmode=None):
943 941 """Create a temporary file with the same contents from name
944 942
945 943 The permission bits are copied from the original file.
946 944
947 945 If the temporary file is going to be truncated immediately, you
948 946 can use emptyok=True as an optimization.
949 947
950 948 Returns the name of the temporary file.
951 949 """
952 950 d, fn = os.path.split(name)
953 951 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
954 952 os.close(fd)
955 953 # Temporary files are created with mode 0600, which is usually not
956 954 # what we want. If the original file already exists, just copy
957 955 # its mode. Otherwise, manually obey umask.
958 956 try:
959 957 st_mode = os.lstat(name).st_mode & 0777
960 958 except OSError, inst:
961 959 if inst.errno != errno.ENOENT:
962 960 raise
963 961 st_mode = createmode
964 962 if st_mode is None:
965 963 st_mode = ~umask
966 964 st_mode &= 0666
967 965 os.chmod(temp, st_mode)
968 966 if emptyok:
969 967 return temp
970 968 try:
971 969 try:
972 970 ifp = posixfile(name, "rb")
973 971 except IOError, inst:
974 972 if inst.errno == errno.ENOENT:
975 973 return temp
976 974 if not getattr(inst, 'filename', None):
977 975 inst.filename = name
978 976 raise
979 977 ofp = posixfile(temp, "wb")
980 978 for chunk in filechunkiter(ifp):
981 979 ofp.write(chunk)
982 980 ifp.close()
983 981 ofp.close()
984 982 except:
985 983 try: os.unlink(temp)
986 984 except: pass
987 985 raise
988 986 return temp
989 987
990 988 class atomictempfile(posixfile):
991 989 """file-like object that atomically updates a file
992 990
993 991 All writes will be redirected to a temporary copy of the original
994 992 file. When rename is called, the copy is renamed to the original
995 993 name, making the changes visible.
996 994 """
997 995 def __init__(self, name, mode, createmode):
998 996 self.__name = name
999 997 self.temp = mktempcopy(name, emptyok=('w' in mode),
1000 998 createmode=createmode)
1001 999 posixfile.__init__(self, self.temp, mode)
1002 1000
1003 1001 def rename(self):
1004 1002 if not self.closed:
1005 1003 posixfile.close(self)
1006 1004 rename(self.temp, localpath(self.__name))
1007 1005
1008 1006 def __del__(self):
1009 1007 if not self.closed:
1010 1008 try:
1011 1009 os.unlink(self.temp)
1012 1010 except: pass
1013 1011 posixfile.close(self)
1014 1012
1015 1013 def makedirs(name, mode=None):
1016 1014 """recursive directory creation with parent mode inheritance"""
1017 1015 try:
1018 1016 os.mkdir(name)
1019 1017 if mode is not None:
1020 1018 os.chmod(name, mode)
1021 1019 return
1022 1020 except OSError, err:
1023 1021 if err.errno == errno.EEXIST:
1024 1022 return
1025 1023 if err.errno != errno.ENOENT:
1026 1024 raise
1027 1025 parent = os.path.abspath(os.path.dirname(name))
1028 1026 makedirs(parent, mode)
1029 1027 makedirs(name, mode)
1030 1028
1031 1029 class opener(object):
1032 1030 """Open files relative to a base directory
1033 1031
1034 1032 This class is used to hide the details of COW semantics and
1035 1033 remote file access from higher level code.
1036 1034 """
1037 1035 def __init__(self, base, audit=True):
1038 1036 self.base = base
1039 1037 if audit:
1040 1038 self.audit_path = path_auditor(base)
1041 1039 else:
1042 1040 self.audit_path = always
1043 1041 self.createmode = None
1044 1042
1045 1043 def __getattr__(self, name):
1046 1044 if name == '_can_symlink':
1047 1045 self._can_symlink = checklink(self.base)
1048 1046 return self._can_symlink
1049 1047 raise AttributeError(name)
1050 1048
1051 1049 def _fixfilemode(self, name):
1052 1050 if self.createmode is None:
1053 1051 return
1054 1052 os.chmod(name, self.createmode & 0666)
1055 1053
1056 1054 def __call__(self, path, mode="r", text=False, atomictemp=False):
1057 1055 self.audit_path(path)
1058 1056 f = os.path.join(self.base, path)
1059 1057
1060 1058 if not text and "b" not in mode:
1061 1059 mode += "b" # for that other OS
1062 1060
1063 1061 nlink = -1
1064 1062 if mode not in ("r", "rb"):
1065 1063 try:
1066 1064 nlink = nlinks(f)
1067 1065 except OSError:
1068 1066 nlink = 0
1069 1067 d = os.path.dirname(f)
1070 1068 if not os.path.isdir(d):
1071 1069 makedirs(d, self.createmode)
1072 1070 if atomictemp:
1073 1071 return atomictempfile(f, mode, self.createmode)
1074 1072 if nlink > 1:
1075 1073 rename(mktempcopy(f), f)
1076 1074 fp = posixfile(f, mode)
1077 1075 if nlink == 0:
1078 1076 self._fixfilemode(f)
1079 1077 return fp
1080 1078
1081 1079 def symlink(self, src, dst):
1082 1080 self.audit_path(dst)
1083 1081 linkname = os.path.join(self.base, dst)
1084 1082 try:
1085 1083 os.unlink(linkname)
1086 1084 except OSError:
1087 1085 pass
1088 1086
1089 1087 dirname = os.path.dirname(linkname)
1090 1088 if not os.path.exists(dirname):
1091 1089 makedirs(dirname, self.createmode)
1092 1090
1093 1091 if self._can_symlink:
1094 1092 try:
1095 1093 os.symlink(src, linkname)
1096 1094 except OSError, err:
1097 1095 raise OSError(err.errno, _('could not symlink to %r: %s') %
1098 1096 (src, err.strerror), linkname)
1099 1097 else:
1100 1098 f = self(dst, "w")
1101 1099 f.write(src)
1102 1100 f.close()
1103 1101 self._fixfilemode(dst)
1104 1102
1105 1103 class chunkbuffer(object):
1106 1104 """Allow arbitrary sized chunks of data to be efficiently read from an
1107 1105 iterator over chunks of arbitrary size."""
1108 1106
1109 1107 def __init__(self, in_iter):
1110 1108 """in_iter is the iterator that's iterating over the input chunks.
1111 1109 targetsize is how big a buffer to try to maintain."""
1112 1110 self.iter = iter(in_iter)
1113 1111 self.buf = ''
1114 1112 self.targetsize = 2**16
1115 1113
1116 1114 def read(self, l):
1117 1115 """Read L bytes of data from the iterator of chunks of data.
1118 1116 Returns less than L bytes if the iterator runs dry."""
1119 1117 if l > len(self.buf) and self.iter:
1120 1118 # Clamp to a multiple of self.targetsize
1121 1119 targetsize = max(l, self.targetsize)
1122 1120 collector = cStringIO.StringIO()
1123 1121 collector.write(self.buf)
1124 1122 collected = len(self.buf)
1125 1123 for chunk in self.iter:
1126 1124 collector.write(chunk)
1127 1125 collected += len(chunk)
1128 1126 if collected >= targetsize:
1129 1127 break
1130 1128 if collected < targetsize:
1131 1129 self.iter = False
1132 1130 self.buf = collector.getvalue()
1133 1131 if len(self.buf) == l:
1134 1132 s, self.buf = str(self.buf), ''
1135 1133 else:
1136 1134 s, self.buf = self.buf[:l], buffer(self.buf, l)
1137 1135 return s
1138 1136
1139 1137 def filechunkiter(f, size=65536, limit=None):
1140 1138 """Create a generator that produces the data in the file size
1141 1139 (default 65536) bytes at a time, up to optional limit (default is
1142 1140 to read all data). Chunks may be less than size bytes if the
1143 1141 chunk is the last chunk in the file, or the file is a socket or
1144 1142 some other type of file that sometimes reads less data than is
1145 1143 requested."""
1146 1144 assert size >= 0
1147 1145 assert limit is None or limit >= 0
1148 1146 while True:
1149 1147 if limit is None: nbytes = size
1150 1148 else: nbytes = min(limit, size)
1151 1149 s = nbytes and f.read(nbytes)
1152 1150 if not s: break
1153 1151 if limit: limit -= len(s)
1154 1152 yield s
1155 1153
1156 1154 def makedate():
1157 1155 lt = time.localtime()
1158 1156 if lt[8] == 1 and time.daylight:
1159 1157 tz = time.altzone
1160 1158 else:
1161 1159 tz = time.timezone
1162 1160 return time.mktime(lt), tz
1163 1161
1164 1162 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1165 1163 """represent a (unixtime, offset) tuple as a localized time.
1166 1164 unixtime is seconds since the epoch, and offset is the time zone's
1167 1165 number of seconds away from UTC. if timezone is false, do not
1168 1166 append time zone to string."""
1169 1167 t, tz = date or makedate()
1170 1168 if "%1" in format or "%2" in format:
1171 1169 sign = (tz > 0) and "-" or "+"
1172 1170 minutes = abs(tz) / 60
1173 1171 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1174 1172 format = format.replace("%2", "%02d" % (minutes % 60))
1175 1173 s = time.strftime(format, time.gmtime(float(t) - tz))
1176 1174 return s
1177 1175
1178 1176 def shortdate(date=None):
1179 1177 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1180 1178 return datestr(date, format='%Y-%m-%d')
1181 1179
1182 1180 def strdate(string, format, defaults=[]):
1183 1181 """parse a localized time string and return a (unixtime, offset) tuple.
1184 1182 if the string cannot be parsed, ValueError is raised."""
1185 1183 def timezone(string):
1186 1184 tz = string.split()[-1]
1187 1185 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1188 1186 sign = (tz[0] == "+") and 1 or -1
1189 1187 hours = int(tz[1:3])
1190 1188 minutes = int(tz[3:5])
1191 1189 return -sign * (hours * 60 + minutes) * 60
1192 1190 if tz == "GMT" or tz == "UTC":
1193 1191 return 0
1194 1192 return None
1195 1193
1196 1194 # NOTE: unixtime = localunixtime + offset
1197 1195 offset, date = timezone(string), string
1198 1196 if offset != None:
1199 1197 date = " ".join(string.split()[:-1])
1200 1198
1201 1199 # add missing elements from defaults
1202 1200 for part in defaults:
1203 1201 found = [True for p in part if ("%"+p) in format]
1204 1202 if not found:
1205 1203 date += "@" + defaults[part]
1206 1204 format += "@%" + part[0]
1207 1205
1208 1206 timetuple = time.strptime(date, format)
1209 1207 localunixtime = int(calendar.timegm(timetuple))
1210 1208 if offset is None:
1211 1209 # local timezone
1212 1210 unixtime = int(time.mktime(timetuple))
1213 1211 offset = unixtime - localunixtime
1214 1212 else:
1215 1213 unixtime = localunixtime + offset
1216 1214 return unixtime, offset
1217 1215
1218 1216 def parsedate(date, formats=None, defaults=None):
1219 1217 """parse a localized date/time string and return a (unixtime, offset) tuple.
1220 1218
1221 1219 The date may be a "unixtime offset" string or in one of the specified
1222 1220 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1223 1221 """
1224 1222 if not date:
1225 1223 return 0, 0
1226 1224 if isinstance(date, tuple) and len(date) == 2:
1227 1225 return date
1228 1226 if not formats:
1229 1227 formats = defaultdateformats
1230 1228 date = date.strip()
1231 1229 try:
1232 1230 when, offset = map(int, date.split(' '))
1233 1231 except ValueError:
1234 1232 # fill out defaults
1235 1233 if not defaults:
1236 1234 defaults = {}
1237 1235 now = makedate()
1238 1236 for part in "d mb yY HI M S".split():
1239 1237 if part not in defaults:
1240 1238 if part[0] in "HMS":
1241 1239 defaults[part] = "00"
1242 1240 else:
1243 1241 defaults[part] = datestr(now, "%" + part[0])
1244 1242
1245 1243 for format in formats:
1246 1244 try:
1247 1245 when, offset = strdate(date, format, defaults)
1248 1246 except (ValueError, OverflowError):
1249 1247 pass
1250 1248 else:
1251 1249 break
1252 1250 else:
1253 1251 raise Abort(_('invalid date: %r ') % date)
1254 1252 # validate explicit (probably user-specified) date and
1255 1253 # time zone offset. values must fit in signed 32 bits for
1256 1254 # current 32-bit linux runtimes. timezones go from UTC-12
1257 1255 # to UTC+14
1258 1256 if abs(when) > 0x7fffffff:
1259 1257 raise Abort(_('date exceeds 32 bits: %d') % when)
1260 1258 if offset < -50400 or offset > 43200:
1261 1259 raise Abort(_('impossible time zone offset: %d') % offset)
1262 1260 return when, offset
1263 1261
1264 1262 def matchdate(date):
1265 1263 """Return a function that matches a given date match specifier
1266 1264
1267 1265 Formats include:
1268 1266
1269 1267 '{date}' match a given date to the accuracy provided
1270 1268
1271 1269 '<{date}' on or before a given date
1272 1270
1273 1271 '>{date}' on or after a given date
1274 1272
1275 1273 """
1276 1274
1277 1275 def lower(date):
1278 1276 d = dict(mb="1", d="1")
1279 1277 return parsedate(date, extendeddateformats, d)[0]
1280 1278
1281 1279 def upper(date):
1282 1280 d = dict(mb="12", HI="23", M="59", S="59")
1283 1281 for days in "31 30 29".split():
1284 1282 try:
1285 1283 d["d"] = days
1286 1284 return parsedate(date, extendeddateformats, d)[0]
1287 1285 except:
1288 1286 pass
1289 1287 d["d"] = "28"
1290 1288 return parsedate(date, extendeddateformats, d)[0]
1291 1289
1292 1290 date = date.strip()
1293 1291 if date[0] == "<":
1294 1292 when = upper(date[1:])
1295 1293 return lambda x: x <= when
1296 1294 elif date[0] == ">":
1297 1295 when = lower(date[1:])
1298 1296 return lambda x: x >= when
1299 1297 elif date[0] == "-":
1300 1298 try:
1301 1299 days = int(date[1:])
1302 1300 except ValueError:
1303 1301 raise Abort(_("invalid day spec: %s") % date[1:])
1304 1302 when = makedate()[0] - days * 3600 * 24
1305 1303 return lambda x: x >= when
1306 1304 elif " to " in date:
1307 1305 a, b = date.split(" to ")
1308 1306 start, stop = lower(a), upper(b)
1309 1307 return lambda x: x >= start and x <= stop
1310 1308 else:
1311 1309 start, stop = lower(date), upper(date)
1312 1310 return lambda x: x >= start and x <= stop
1313 1311
1314 1312 def shortuser(user):
1315 1313 """Return a short representation of a user name or email address."""
1316 1314 f = user.find('@')
1317 1315 if f >= 0:
1318 1316 user = user[:f]
1319 1317 f = user.find('<')
1320 1318 if f >= 0:
1321 1319 user = user[f+1:]
1322 1320 f = user.find(' ')
1323 1321 if f >= 0:
1324 1322 user = user[:f]
1325 1323 f = user.find('.')
1326 1324 if f >= 0:
1327 1325 user = user[:f]
1328 1326 return user
1329 1327
1330 1328 def email(author):
1331 1329 '''get email of author.'''
1332 1330 r = author.find('>')
1333 1331 if r == -1: r = None
1334 1332 return author[author.find('<')+1:r]
1335 1333
1336 1334 def ellipsis(text, maxlength=400):
1337 1335 """Trim string to at most maxlength (default: 400) characters."""
1338 1336 if len(text) <= maxlength:
1339 1337 return text
1340 1338 else:
1341 1339 return "%s..." % (text[:maxlength-3])
1342 1340
1343 1341 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1344 1342 '''yield every hg repository under path, recursively.'''
1345 1343 def errhandler(err):
1346 1344 if err.filename == path:
1347 1345 raise err
1348 1346 if followsym and hasattr(os.path, 'samestat'):
1349 1347 def _add_dir_if_not_there(dirlst, dirname):
1350 1348 match = False
1351 1349 samestat = os.path.samestat
1352 1350 dirstat = os.stat(dirname)
1353 1351 for lstdirstat in dirlst:
1354 1352 if samestat(dirstat, lstdirstat):
1355 1353 match = True
1356 1354 break
1357 1355 if not match:
1358 1356 dirlst.append(dirstat)
1359 1357 return not match
1360 1358 else:
1361 1359 followsym = False
1362 1360
1363 1361 if (seen_dirs is None) and followsym:
1364 1362 seen_dirs = []
1365 1363 _add_dir_if_not_there(seen_dirs, path)
1366 1364 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1367 1365 if '.hg' in dirs:
1368 1366 yield root # found a repository
1369 1367 qroot = os.path.join(root, '.hg', 'patches')
1370 1368 if os.path.isdir(os.path.join(qroot, '.hg')):
1371 1369 yield qroot # we have a patch queue repo here
1372 1370 if recurse:
1373 1371 # avoid recursing inside the .hg directory
1374 1372 dirs.remove('.hg')
1375 1373 else:
1376 1374 dirs[:] = [] # don't descend further
1377 1375 elif followsym:
1378 1376 newdirs = []
1379 1377 for d in dirs:
1380 1378 fname = os.path.join(root, d)
1381 1379 if _add_dir_if_not_there(seen_dirs, fname):
1382 1380 if os.path.islink(fname):
1383 1381 for hgname in walkrepos(fname, True, seen_dirs):
1384 1382 yield hgname
1385 1383 else:
1386 1384 newdirs.append(d)
1387 1385 dirs[:] = newdirs
1388 1386
1389 1387 _rcpath = None
1390 1388
1391 1389 def os_rcpath():
1392 1390 '''return default os-specific hgrc search path'''
1393 1391 path = system_rcpath()
1394 1392 path.extend(user_rcpath())
1395 1393 path = [os.path.normpath(f) for f in path]
1396 1394 return path
1397 1395
1398 1396 def rcpath():
1399 1397 '''return hgrc search path. if env var HGRCPATH is set, use it.
1400 1398 for each item in path, if directory, use files ending in .rc,
1401 1399 else use item.
1402 1400 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1403 1401 if no HGRCPATH, use default os-specific path.'''
1404 1402 global _rcpath
1405 1403 if _rcpath is None:
1406 1404 if 'HGRCPATH' in os.environ:
1407 1405 _rcpath = []
1408 1406 for p in os.environ['HGRCPATH'].split(os.pathsep):
1409 1407 if not p: continue
1410 1408 if os.path.isdir(p):
1411 1409 for f, kind in osutil.listdir(p):
1412 1410 if f.endswith('.rc'):
1413 1411 _rcpath.append(os.path.join(p, f))
1414 1412 else:
1415 1413 _rcpath.append(p)
1416 1414 else:
1417 1415 _rcpath = os_rcpath()
1418 1416 return _rcpath
1419 1417
1420 1418 def bytecount(nbytes):
1421 1419 '''return byte count formatted as readable string, with units'''
1422 1420
1423 1421 units = (
1424 1422 (100, 1<<30, _('%.0f GB')),
1425 1423 (10, 1<<30, _('%.1f GB')),
1426 1424 (1, 1<<30, _('%.2f GB')),
1427 1425 (100, 1<<20, _('%.0f MB')),
1428 1426 (10, 1<<20, _('%.1f MB')),
1429 1427 (1, 1<<20, _('%.2f MB')),
1430 1428 (100, 1<<10, _('%.0f KB')),
1431 1429 (10, 1<<10, _('%.1f KB')),
1432 1430 (1, 1<<10, _('%.2f KB')),
1433 1431 (1, 1, _('%.0f bytes')),
1434 1432 )
1435 1433
1436 1434 for multiplier, divisor, format in units:
1437 1435 if nbytes >= divisor * multiplier:
1438 1436 return format % (nbytes / float(divisor))
1439 1437 return units[-1][2] % nbytes
1440 1438
1441 1439 def drop_scheme(scheme, path):
1442 1440 sc = scheme + ':'
1443 1441 if path.startswith(sc):
1444 1442 path = path[len(sc):]
1445 1443 if path.startswith('//'):
1446 1444 path = path[2:]
1447 1445 return path
1448 1446
1449 1447 def uirepr(s):
1450 1448 # Avoid double backslash in Windows path repr()
1451 1449 return repr(s).replace('\\\\', '\\')
1452 1450
1453 1451 def termwidth():
1454 1452 if 'COLUMNS' in os.environ:
1455 1453 try:
1456 1454 return int(os.environ['COLUMNS'])
1457 1455 except ValueError:
1458 1456 pass
1459 1457 try:
1460 1458 import termios, array, fcntl
1461 1459 for dev in (sys.stdout, sys.stdin):
1462 1460 try:
1463 1461 fd = dev.fileno()
1464 1462 if not os.isatty(fd):
1465 1463 continue
1466 1464 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1467 1465 return array.array('h', arri)[1]
1468 1466 except ValueError:
1469 1467 pass
1470 1468 except ImportError:
1471 1469 pass
1472 1470 return 80
1473 1471
1474 1472 def iterlines(iterator):
1475 1473 for chunk in iterator:
1476 1474 for line in chunk.splitlines():
1477 1475 yield line
@@ -1,379 +1,377 b''
1 '''
2 win32.py - utility functions that use win32 API
3
4 Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
5
6 This software may be used and distributed according to the terms of the
7 GNU General Public License version 2, incorporated herein by reference.
8
9 Mark Hammond's win32all package allows better functionality on
10 Windows. this module overrides definitions in util.py. if not
11 available, import of this module will fail, and generic code will be
12 used.
13 '''
1 # win32.py - utility functions that use win32 API
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
7 #
8 # Mark Hammond's win32all package allows better functionality on
9 # Windows. this module overrides definitions in util.py. if not
10 # available, import of this module will fail, and generic code will be
11 # used.
14 12
15 13 import win32api
16 14
17 15 import errno, os, sys, pywintypes, win32con, win32file, win32process
18 16 import cStringIO, winerror
19 17 import osutil, encoding
20 18 import util
21 19 from win32com.shell import shell,shellcon
22 20
23 21 class WinError(Exception):
24 22 winerror_map = {
25 23 winerror.ERROR_ACCESS_DENIED: errno.EACCES,
26 24 winerror.ERROR_ACCOUNT_DISABLED: errno.EACCES,
27 25 winerror.ERROR_ACCOUNT_RESTRICTION: errno.EACCES,
28 26 winerror.ERROR_ALREADY_ASSIGNED: errno.EBUSY,
29 27 winerror.ERROR_ALREADY_EXISTS: errno.EEXIST,
30 28 winerror.ERROR_ARITHMETIC_OVERFLOW: errno.ERANGE,
31 29 winerror.ERROR_BAD_COMMAND: errno.EIO,
32 30 winerror.ERROR_BAD_DEVICE: errno.ENODEV,
33 31 winerror.ERROR_BAD_DRIVER_LEVEL: errno.ENXIO,
34 32 winerror.ERROR_BAD_EXE_FORMAT: errno.ENOEXEC,
35 33 winerror.ERROR_BAD_FORMAT: errno.ENOEXEC,
36 34 winerror.ERROR_BAD_LENGTH: errno.EINVAL,
37 35 winerror.ERROR_BAD_PATHNAME: errno.ENOENT,
38 36 winerror.ERROR_BAD_PIPE: errno.EPIPE,
39 37 winerror.ERROR_BAD_UNIT: errno.ENODEV,
40 38 winerror.ERROR_BAD_USERNAME: errno.EINVAL,
41 39 winerror.ERROR_BROKEN_PIPE: errno.EPIPE,
42 40 winerror.ERROR_BUFFER_OVERFLOW: errno.ENAMETOOLONG,
43 41 winerror.ERROR_BUSY: errno.EBUSY,
44 42 winerror.ERROR_BUSY_DRIVE: errno.EBUSY,
45 43 winerror.ERROR_CALL_NOT_IMPLEMENTED: errno.ENOSYS,
46 44 winerror.ERROR_CANNOT_MAKE: errno.EACCES,
47 45 winerror.ERROR_CANTOPEN: errno.EIO,
48 46 winerror.ERROR_CANTREAD: errno.EIO,
49 47 winerror.ERROR_CANTWRITE: errno.EIO,
50 48 winerror.ERROR_CRC: errno.EIO,
51 49 winerror.ERROR_CURRENT_DIRECTORY: errno.EACCES,
52 50 winerror.ERROR_DEVICE_IN_USE: errno.EBUSY,
53 51 winerror.ERROR_DEV_NOT_EXIST: errno.ENODEV,
54 52 winerror.ERROR_DIRECTORY: errno.EINVAL,
55 53 winerror.ERROR_DIR_NOT_EMPTY: errno.ENOTEMPTY,
56 54 winerror.ERROR_DISK_CHANGE: errno.EIO,
57 55 winerror.ERROR_DISK_FULL: errno.ENOSPC,
58 56 winerror.ERROR_DRIVE_LOCKED: errno.EBUSY,
59 57 winerror.ERROR_ENVVAR_NOT_FOUND: errno.EINVAL,
60 58 winerror.ERROR_EXE_MARKED_INVALID: errno.ENOEXEC,
61 59 winerror.ERROR_FILENAME_EXCED_RANGE: errno.ENAMETOOLONG,
62 60 winerror.ERROR_FILE_EXISTS: errno.EEXIST,
63 61 winerror.ERROR_FILE_INVALID: errno.ENODEV,
64 62 winerror.ERROR_FILE_NOT_FOUND: errno.ENOENT,
65 63 winerror.ERROR_GEN_FAILURE: errno.EIO,
66 64 winerror.ERROR_HANDLE_DISK_FULL: errno.ENOSPC,
67 65 winerror.ERROR_INSUFFICIENT_BUFFER: errno.ENOMEM,
68 66 winerror.ERROR_INVALID_ACCESS: errno.EACCES,
69 67 winerror.ERROR_INVALID_ADDRESS: errno.EFAULT,
70 68 winerror.ERROR_INVALID_BLOCK: errno.EFAULT,
71 69 winerror.ERROR_INVALID_DATA: errno.EINVAL,
72 70 winerror.ERROR_INVALID_DRIVE: errno.ENODEV,
73 71 winerror.ERROR_INVALID_EXE_SIGNATURE: errno.ENOEXEC,
74 72 winerror.ERROR_INVALID_FLAGS: errno.EINVAL,
75 73 winerror.ERROR_INVALID_FUNCTION: errno.ENOSYS,
76 74 winerror.ERROR_INVALID_HANDLE: errno.EBADF,
77 75 winerror.ERROR_INVALID_LOGON_HOURS: errno.EACCES,
78 76 winerror.ERROR_INVALID_NAME: errno.EINVAL,
79 77 winerror.ERROR_INVALID_OWNER: errno.EINVAL,
80 78 winerror.ERROR_INVALID_PARAMETER: errno.EINVAL,
81 79 winerror.ERROR_INVALID_PASSWORD: errno.EPERM,
82 80 winerror.ERROR_INVALID_PRIMARY_GROUP: errno.EINVAL,
83 81 winerror.ERROR_INVALID_SIGNAL_NUMBER: errno.EINVAL,
84 82 winerror.ERROR_INVALID_TARGET_HANDLE: errno.EIO,
85 83 winerror.ERROR_INVALID_WORKSTATION: errno.EACCES,
86 84 winerror.ERROR_IO_DEVICE: errno.EIO,
87 85 winerror.ERROR_IO_INCOMPLETE: errno.EINTR,
88 86 winerror.ERROR_LOCKED: errno.EBUSY,
89 87 winerror.ERROR_LOCK_VIOLATION: errno.EACCES,
90 88 winerror.ERROR_LOGON_FAILURE: errno.EACCES,
91 89 winerror.ERROR_MAPPED_ALIGNMENT: errno.EINVAL,
92 90 winerror.ERROR_META_EXPANSION_TOO_LONG: errno.E2BIG,
93 91 winerror.ERROR_MORE_DATA: errno.EPIPE,
94 92 winerror.ERROR_NEGATIVE_SEEK: errno.ESPIPE,
95 93 winerror.ERROR_NOACCESS: errno.EFAULT,
96 94 winerror.ERROR_NONE_MAPPED: errno.EINVAL,
97 95 winerror.ERROR_NOT_ENOUGH_MEMORY: errno.ENOMEM,
98 96 winerror.ERROR_NOT_READY: errno.EAGAIN,
99 97 winerror.ERROR_NOT_SAME_DEVICE: errno.EXDEV,
100 98 winerror.ERROR_NO_DATA: errno.EPIPE,
101 99 winerror.ERROR_NO_MORE_SEARCH_HANDLES: errno.EIO,
102 100 winerror.ERROR_NO_PROC_SLOTS: errno.EAGAIN,
103 101 winerror.ERROR_NO_SUCH_PRIVILEGE: errno.EACCES,
104 102 winerror.ERROR_OPEN_FAILED: errno.EIO,
105 103 winerror.ERROR_OPEN_FILES: errno.EBUSY,
106 104 winerror.ERROR_OPERATION_ABORTED: errno.EINTR,
107 105 winerror.ERROR_OUTOFMEMORY: errno.ENOMEM,
108 106 winerror.ERROR_PASSWORD_EXPIRED: errno.EACCES,
109 107 winerror.ERROR_PATH_BUSY: errno.EBUSY,
110 108 winerror.ERROR_PATH_NOT_FOUND: errno.ENOENT,
111 109 winerror.ERROR_PIPE_BUSY: errno.EBUSY,
112 110 winerror.ERROR_PIPE_CONNECTED: errno.EPIPE,
113 111 winerror.ERROR_PIPE_LISTENING: errno.EPIPE,
114 112 winerror.ERROR_PIPE_NOT_CONNECTED: errno.EPIPE,
115 113 winerror.ERROR_PRIVILEGE_NOT_HELD: errno.EACCES,
116 114 winerror.ERROR_READ_FAULT: errno.EIO,
117 115 winerror.ERROR_SEEK: errno.EIO,
118 116 winerror.ERROR_SEEK_ON_DEVICE: errno.ESPIPE,
119 117 winerror.ERROR_SHARING_BUFFER_EXCEEDED: errno.ENFILE,
120 118 winerror.ERROR_SHARING_VIOLATION: errno.EACCES,
121 119 winerror.ERROR_STACK_OVERFLOW: errno.ENOMEM,
122 120 winerror.ERROR_SWAPERROR: errno.ENOENT,
123 121 winerror.ERROR_TOO_MANY_MODULES: errno.EMFILE,
124 122 winerror.ERROR_TOO_MANY_OPEN_FILES: errno.EMFILE,
125 123 winerror.ERROR_UNRECOGNIZED_MEDIA: errno.ENXIO,
126 124 winerror.ERROR_UNRECOGNIZED_VOLUME: errno.ENODEV,
127 125 winerror.ERROR_WAIT_NO_CHILDREN: errno.ECHILD,
128 126 winerror.ERROR_WRITE_FAULT: errno.EIO,
129 127 winerror.ERROR_WRITE_PROTECT: errno.EROFS,
130 128 }
131 129
132 130 def __init__(self, err):
133 131 self.win_errno, self.win_function, self.win_strerror = err
134 132 if self.win_strerror.endswith('.'):
135 133 self.win_strerror = self.win_strerror[:-1]
136 134
137 135 class WinIOError(WinError, IOError):
138 136 def __init__(self, err, filename=None):
139 137 WinError.__init__(self, err)
140 138 IOError.__init__(self, self.winerror_map.get(self.win_errno, 0),
141 139 self.win_strerror)
142 140 self.filename = filename
143 141
144 142 class WinOSError(WinError, OSError):
145 143 def __init__(self, err):
146 144 WinError.__init__(self, err)
147 145 OSError.__init__(self, self.winerror_map.get(self.win_errno, 0),
148 146 self.win_strerror)
149 147
150 148 def os_link(src, dst):
151 149 try:
152 150 win32file.CreateHardLink(dst, src)
153 151 # CreateHardLink sometimes succeeds on mapped drives but
154 152 # following nlinks() returns 1. Check it now and bail out.
155 153 if nlinks(src) < 2:
156 154 try:
157 155 win32file.DeleteFile(dst)
158 156 except:
159 157 pass
160 158 # Fake hardlinking error
161 159 raise WinOSError((18, 'CreateHardLink', 'The system cannot '
162 160 'move the file to a different disk drive'))
163 161 except pywintypes.error, details:
164 162 raise WinOSError(details)
165 163 except NotImplementedError: # Another fake error win Win98
166 164 raise WinOSError((18, 'CreateHardLink', 'Hardlinking not supported'))
167 165
168 166 def nlinks(pathname):
169 167 """Return number of hardlinks for the given file."""
170 168 try:
171 169 fh = win32file.CreateFile(pathname,
172 170 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
173 171 None, win32file.OPEN_EXISTING, 0, None)
174 172 res = win32file.GetFileInformationByHandle(fh)
175 173 fh.Close()
176 174 return res[7]
177 175 except pywintypes.error:
178 176 return os.lstat(pathname).st_nlink
179 177
180 178 def testpid(pid):
181 179 '''return True if pid is still running or unable to
182 180 determine, False otherwise'''
183 181 try:
184 182 handle = win32api.OpenProcess(
185 183 win32con.PROCESS_QUERY_INFORMATION, False, pid)
186 184 if handle:
187 185 status = win32process.GetExitCodeProcess(handle)
188 186 return status == win32con.STILL_ACTIVE
189 187 except pywintypes.error, details:
190 188 return details[0] != winerror.ERROR_INVALID_PARAMETER
191 189 return True
192 190
193 191 def lookup_reg(key, valname=None, scope=None):
194 192 ''' Look up a key/value name in the Windows registry.
195 193
196 194 valname: value name. If unspecified, the default value for the key
197 195 is used.
198 196 scope: optionally specify scope for registry lookup, this can be
199 197 a sequence of scopes to look up in order. Default (CURRENT_USER,
200 198 LOCAL_MACHINE).
201 199 '''
202 200 try:
203 201 from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, \
204 202 QueryValueEx, OpenKey
205 203 except ImportError:
206 204 return None
207 205
208 206 if scope is None:
209 207 scope = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)
210 208 elif not isinstance(scope, (list, tuple)):
211 209 scope = (scope,)
212 210 for s in scope:
213 211 try:
214 212 val = QueryValueEx(OpenKey(s, key), valname)[0]
215 213 # never let a Unicode string escape into the wild
216 214 return encoding.tolocal(val.encode('UTF-8'))
217 215 except EnvironmentError:
218 216 pass
219 217
220 218 def system_rcpath_win32():
221 219 '''return default os-specific hgrc search path'''
222 220 proc = win32api.GetCurrentProcess()
223 221 try:
224 222 # This will fail on windows < NT
225 223 filename = win32process.GetModuleFileNameEx(proc, 0)
226 224 except:
227 225 filename = win32api.GetModuleFileName(0)
228 226 # Use mercurial.ini found in directory with hg.exe
229 227 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
230 228 if os.path.isfile(progrc):
231 229 return [progrc]
232 230 # else look for a system rcpath in the registry
233 231 try:
234 232 value = win32api.RegQueryValue(
235 233 win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Mercurial')
236 234 rcpath = []
237 235 for p in value.split(os.pathsep):
238 236 if p.lower().endswith('mercurial.ini'):
239 237 rcpath.append(p)
240 238 elif os.path.isdir(p):
241 239 for f, kind in osutil.listdir(p):
242 240 if f.endswith('.rc'):
243 241 rcpath.append(os.path.join(p, f))
244 242 return rcpath
245 243 except pywintypes.error:
246 244 return []
247 245
248 246 def user_rcpath_win32():
249 247 '''return os-specific hgrc search path to the user dir'''
250 248 userdir = os.path.expanduser('~')
251 249 if sys.getwindowsversion()[3] != 2 and userdir == '~':
252 250 # We are on win < nt: fetch the APPDATA directory location and use
253 251 # the parent directory as the user home dir.
254 252 appdir = shell.SHGetPathFromIDList(
255 253 shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA))
256 254 userdir = os.path.dirname(appdir)
257 255 return [os.path.join(userdir, 'mercurial.ini'),
258 256 os.path.join(userdir, '.hgrc')]
259 257
260 258 class posixfile_nt(object):
261 259 '''file object with posix-like semantics. on windows, normal
262 260 files can not be deleted or renamed if they are open. must open
263 261 with win32file.FILE_SHARE_DELETE. this flag does not exist on
264 262 windows < nt, so do not use this class there.'''
265 263
266 264 # tried to use win32file._open_osfhandle to pass fd to os.fdopen,
267 265 # but does not work at all. wrap win32 file api instead.
268 266
269 267 def __init__(self, name, mode='rb'):
270 268 self.closed = False
271 269 self.name = name
272 270 self.mode = mode
273 271 access = 0
274 272 if 'r' in mode or '+' in mode:
275 273 access |= win32file.GENERIC_READ
276 274 if 'w' in mode or 'a' in mode or '+' in mode:
277 275 access |= win32file.GENERIC_WRITE
278 276 if 'r' in mode:
279 277 creation = win32file.OPEN_EXISTING
280 278 elif 'a' in mode:
281 279 creation = win32file.OPEN_ALWAYS
282 280 else:
283 281 creation = win32file.CREATE_ALWAYS
284 282 try:
285 283 self.handle = win32file.CreateFile(name,
286 284 access,
287 285 win32file.FILE_SHARE_READ |
288 286 win32file.FILE_SHARE_WRITE |
289 287 win32file.FILE_SHARE_DELETE,
290 288 None,
291 289 creation,
292 290 win32file.FILE_ATTRIBUTE_NORMAL,
293 291 0)
294 292 except pywintypes.error, err:
295 293 raise WinIOError(err, name)
296 294
297 295 def __iter__(self):
298 296 for line in self.readlines():
299 297 yield line
300 298
301 299 def read(self, count=-1):
302 300 try:
303 301 cs = cStringIO.StringIO()
304 302 while count:
305 303 wincount = int(count)
306 304 if wincount == -1:
307 305 wincount = 1048576
308 306 val, data = win32file.ReadFile(self.handle, wincount)
309 307 if not data: break
310 308 cs.write(data)
311 309 if count != -1:
312 310 count -= len(data)
313 311 return cs.getvalue()
314 312 except pywintypes.error, err:
315 313 raise WinIOError(err)
316 314
317 315 def readlines(self, sizehint=None):
318 316 # splitlines() splits on single '\r' while readlines()
319 317 # does not. cStringIO has a well behaving readlines() and is fast.
320 318 return cStringIO.StringIO(self.read()).readlines()
321 319
322 320 def write(self, data):
323 321 try:
324 322 if 'a' in self.mode:
325 323 win32file.SetFilePointer(self.handle, 0, win32file.FILE_END)
326 324 nwrit = 0
327 325 while nwrit < len(data):
328 326 val, nwrit = win32file.WriteFile(self.handle, data)
329 327 data = data[nwrit:]
330 328 except pywintypes.error, err:
331 329 raise WinIOError(err)
332 330
333 331 def writelines(self, sequence):
334 332 for s in sequence:
335 333 self.write(s)
336 334
337 335 def seek(self, pos, whence=0):
338 336 try:
339 337 win32file.SetFilePointer(self.handle, int(pos), whence)
340 338 except pywintypes.error, err:
341 339 raise WinIOError(err)
342 340
343 341 def tell(self):
344 342 try:
345 343 return win32file.SetFilePointer(self.handle, 0,
346 344 win32file.FILE_CURRENT)
347 345 except pywintypes.error, err:
348 346 raise WinIOError(err)
349 347
350 348 def close(self):
351 349 if not self.closed:
352 350 self.handle = None
353 351 self.closed = True
354 352
355 353 def flush(self):
356 354 # we have no application-level buffering
357 355 pass
358 356
359 357 def truncate(self, pos=0):
360 358 try:
361 359 win32file.SetFilePointer(self.handle, int(pos),
362 360 win32file.FILE_BEGIN)
363 361 win32file.SetEndOfFile(self.handle)
364 362 except pywintypes.error, err:
365 363 raise WinIOError(err)
366 364
367 365 def getuser():
368 366 '''return name of current user'''
369 367 return win32api.GetUserName()
370 368
371 369 def set_signal_handler_win32():
372 370 """Register a termination handler for console events including
373 371 CTRL+C. python signal handlers do not work well with socket
374 372 operations.
375 373 """
376 374 def handler(event):
377 375 win32process.ExitProcess(1)
378 376 win32api.SetConsoleCtrlHandler(handler)
379 377
@@ -1,252 +1,250 b''
1 """
2 windows.py - Windows utility function implementations for Mercurial
3
4 Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
5
6 This software may be used and distributed according to the terms of the
7 GNU General Public License version 2, incorporated herein by reference.
8 """
1 # windows.py - Windows utility function implementations for Mercurial
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
9 7
10 8 from i18n import _
11 9 import errno, msvcrt, os, osutil, re, sys, error
12 10 nulldev = 'NUL:'
13 11
14 12 umask = 002
15 13
16 14 class winstdout:
17 15 '''stdout on windows misbehaves if sent through a pipe'''
18 16
19 17 def __init__(self, fp):
20 18 self.fp = fp
21 19
22 20 def __getattr__(self, key):
23 21 return getattr(self.fp, key)
24 22
25 23 def close(self):
26 24 try:
27 25 self.fp.close()
28 26 except: pass
29 27
30 28 def write(self, s):
31 29 try:
32 30 # This is workaround for "Not enough space" error on
33 31 # writing large size of data to console.
34 32 limit = 16000
35 33 l = len(s)
36 34 start = 0
37 35 while start < l:
38 36 end = start + limit
39 37 self.fp.write(s[start:end])
40 38 start = end
41 39 except IOError, inst:
42 40 if inst.errno != 0: raise
43 41 self.close()
44 42 raise IOError(errno.EPIPE, 'Broken pipe')
45 43
46 44 def flush(self):
47 45 try:
48 46 return self.fp.flush()
49 47 except IOError, inst:
50 48 if inst.errno != errno.EINVAL: raise
51 49 self.close()
52 50 raise IOError(errno.EPIPE, 'Broken pipe')
53 51
54 52 sys.stdout = winstdout(sys.stdout)
55 53
56 54 def _is_win_9x():
57 55 '''return true if run on windows 95, 98 or me.'''
58 56 try:
59 57 return sys.getwindowsversion()[3] == 1
60 58 except AttributeError:
61 59 return 'command' in os.environ.get('comspec', '')
62 60
63 61 def openhardlinks():
64 62 return not _is_win_9x and "win32api" in locals()
65 63
66 64 def system_rcpath():
67 65 try:
68 66 return system_rcpath_win32()
69 67 except:
70 68 return [r'c:\mercurial\mercurial.ini']
71 69
72 70 def user_rcpath():
73 71 '''return os-specific hgrc search path to the user dir'''
74 72 try:
75 73 path = user_rcpath_win32()
76 74 except:
77 75 home = os.path.expanduser('~')
78 76 path = [os.path.join(home, 'mercurial.ini'),
79 77 os.path.join(home, '.hgrc')]
80 78 userprofile = os.environ.get('USERPROFILE')
81 79 if userprofile:
82 80 path.append(os.path.join(userprofile, 'mercurial.ini'))
83 81 path.append(os.path.join(userprofile, '.hgrc'))
84 82 return path
85 83
86 84 def parse_patch_output(output_line):
87 85 """parses the output produced by patch and returns the file name"""
88 86 pf = output_line[14:]
89 87 if pf[0] == '`':
90 88 pf = pf[1:-1] # Remove the quotes
91 89 return pf
92 90
93 91 def sshargs(sshcmd, host, user, port):
94 92 '''Build argument list for ssh or Plink'''
95 93 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
96 94 args = user and ("%s@%s" % (user, host)) or host
97 95 return port and ("%s %s %s" % (args, pflag, port)) or args
98 96
99 97 def testpid(pid):
100 98 '''return False if pid dead, True if running or not known'''
101 99 return True
102 100
103 101 def set_flags(f, l, x):
104 102 pass
105 103
106 104 def set_binary(fd):
107 105 # When run without console, pipes may expose invalid
108 106 # fileno(), usually set to -1.
109 107 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
110 108 msvcrt.setmode(fd.fileno(), os.O_BINARY)
111 109
112 110 def pconvert(path):
113 111 return '/'.join(path.split(os.sep))
114 112
115 113 def localpath(path):
116 114 return path.replace('/', '\\')
117 115
118 116 def normpath(path):
119 117 return pconvert(os.path.normpath(path))
120 118
121 119 def samestat(s1, s2):
122 120 return False
123 121
124 122 # A sequence of backslashes is special iff it precedes a double quote:
125 123 # - if there's an even number of backslashes, the double quote is not
126 124 # quoted (i.e. it ends the quoted region)
127 125 # - if there's an odd number of backslashes, the double quote is quoted
128 126 # - in both cases, every pair of backslashes is unquoted into a single
129 127 # backslash
130 128 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
131 129 # So, to quote a string, we must surround it in double quotes, double
132 130 # the number of backslashes that preceed double quotes and add another
133 131 # backslash before every double quote (being careful with the double
134 132 # quote we've appended to the end)
135 133 _quotere = None
136 134 def shellquote(s):
137 135 global _quotere
138 136 if _quotere is None:
139 137 _quotere = re.compile(r'(\\*)("|\\$)')
140 138 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
141 139
142 140 def quotecommand(cmd):
143 141 """Build a command string suitable for os.popen* calls."""
144 142 # The extra quotes are needed because popen* runs the command
145 143 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
146 144 return '"' + cmd + '"'
147 145
148 146 def popen(command, mode='r'):
149 147 # Work around "popen spawned process may not write to stdout
150 148 # under windows"
151 149 # http://bugs.python.org/issue1366
152 150 command += " 2> %s" % nulldev
153 151 return os.popen(quotecommand(command), mode)
154 152
155 153 def explain_exit(code):
156 154 return _("exited with status %d") % code, code
157 155
158 156 # if you change this stub into a real check, please try to implement the
159 157 # username and groupname functions above, too.
160 158 def isowner(fp, st=None):
161 159 return True
162 160
163 161 def find_exe(command):
164 162 '''Find executable for command searching like cmd.exe does.
165 163 If command is a basename then PATH is searched for command.
166 164 PATH isn't searched if command is an absolute or relative path.
167 165 An extension from PATHEXT is found and added if not present.
168 166 If command isn't found None is returned.'''
169 167 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
170 168 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
171 169 if os.path.splitext(command)[1].lower() in pathexts:
172 170 pathexts = ['']
173 171
174 172 def findexisting(pathcommand):
175 173 'Will append extension (if needed) and return existing file'
176 174 for ext in pathexts:
177 175 executable = pathcommand + ext
178 176 if os.path.exists(executable):
179 177 return executable
180 178 return None
181 179
182 180 if os.sep in command:
183 181 return findexisting(command)
184 182
185 183 for path in os.environ.get('PATH', '').split(os.pathsep):
186 184 executable = findexisting(os.path.join(path, command))
187 185 if executable is not None:
188 186 return executable
189 187 return None
190 188
191 189 def set_signal_handler():
192 190 try:
193 191 set_signal_handler_win32()
194 192 except NameError:
195 193 pass
196 194
197 195 def statfiles(files):
198 196 '''Stat each file in files and yield stat or None if file does not exist.
199 197 Cluster and cache stat per directory to minimize number of OS stat calls.'''
200 198 ncase = os.path.normcase
201 199 sep = os.sep
202 200 dircache = {} # dirname -> filename -> status | None if file does not exist
203 201 for nf in files:
204 202 nf = ncase(nf)
205 203 pos = nf.rfind(sep)
206 204 if pos == -1:
207 205 dir, base = '.', nf
208 206 else:
209 207 dir, base = nf[:pos+1], nf[pos+1:]
210 208 cache = dircache.get(dir, None)
211 209 if cache is None:
212 210 try:
213 211 dmap = dict([(ncase(n), s)
214 212 for n, k, s in osutil.listdir(dir, True)])
215 213 except OSError, err:
216 214 # handle directory not found in Python version prior to 2.5
217 215 # Python <= 2.4 returns native Windows code 3 in errno
218 216 # Python >= 2.5 returns ENOENT and adds winerror field
219 217 # EINVAL is raised if dir is not a directory.
220 218 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
221 219 errno.ENOTDIR):
222 220 raise
223 221 dmap = {}
224 222 cache = dircache.setdefault(dir, dmap)
225 223 yield cache.get(base, None)
226 224
227 225 def getuser():
228 226 '''return name of current user'''
229 227 raise error.Abort(_('user name not available - set USERNAME '
230 228 'environment variable'))
231 229
232 230 def username(uid=None):
233 231 """Return the name of the user with the given uid.
234 232
235 233 If uid is None, return the name of the current user."""
236 234 return None
237 235
238 236 def groupname(gid=None):
239 237 """Return the name of the group with the given gid.
240 238
241 239 If gid is None, return the name of the current group."""
242 240 return None
243 241
244 242 posixfile = file
245 243 try:
246 244 # override functions with win32 versions if possible
247 245 from win32 import *
248 246 if not _is_win_9x():
249 247 posixfile = posixfile_nt
250 248 except ImportError:
251 249 pass
252 250
General Comments 0
You need to be logged in to leave comments. Login now