##// END OF EJS Templates
dirstate: move file type filtering to its source...
Bryan O'Sullivan -
r18017:74912fe3 default
parent child Browse files
Show More
@@ -1,798 +1,795 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 import errno
7 import errno
8
8
9 from node import nullid
9 from node import nullid
10 from i18n import _
10 from i18n import _
11 import scmutil, util, ignore, osutil, parsers, encoding
11 import scmutil, util, ignore, osutil, parsers, encoding
12 import struct, os, stat, errno
12 import struct, os, stat, errno
13 import cStringIO
13 import cStringIO
14
14
15 _format = ">cllll"
15 _format = ">cllll"
16 propertycache = util.propertycache
16 propertycache = util.propertycache
17 filecache = scmutil.filecache
17 filecache = scmutil.filecache
18 _rangemask = 0x7fffffff
18 _rangemask = 0x7fffffff
19
19
20 class repocache(filecache):
20 class repocache(filecache):
21 """filecache for files in .hg/"""
21 """filecache for files in .hg/"""
22 def join(self, obj, fname):
22 def join(self, obj, fname):
23 return obj._opener.join(fname)
23 return obj._opener.join(fname)
24
24
25 class rootcache(filecache):
25 class rootcache(filecache):
26 """filecache for files in the repository root"""
26 """filecache for files in the repository root"""
27 def join(self, obj, fname):
27 def join(self, obj, fname):
28 return obj._join(fname)
28 return obj._join(fname)
29
29
30 def _finddirs(path):
30 def _finddirs(path):
31 pos = path.rfind('/')
31 pos = path.rfind('/')
32 while pos != -1:
32 while pos != -1:
33 yield path[:pos]
33 yield path[:pos]
34 pos = path.rfind('/', 0, pos)
34 pos = path.rfind('/', 0, pos)
35
35
36 def _incdirs(dirs, path):
36 def _incdirs(dirs, path):
37 for base in _finddirs(path):
37 for base in _finddirs(path):
38 if base in dirs:
38 if base in dirs:
39 dirs[base] += 1
39 dirs[base] += 1
40 return
40 return
41 dirs[base] = 1
41 dirs[base] = 1
42
42
43 def _decdirs(dirs, path):
43 def _decdirs(dirs, path):
44 for base in _finddirs(path):
44 for base in _finddirs(path):
45 if dirs[base] > 1:
45 if dirs[base] > 1:
46 dirs[base] -= 1
46 dirs[base] -= 1
47 return
47 return
48 del dirs[base]
48 del dirs[base]
49
49
50 class dirstate(object):
50 class dirstate(object):
51
51
52 def __init__(self, opener, ui, root, validate):
52 def __init__(self, opener, ui, root, validate):
53 '''Create a new dirstate object.
53 '''Create a new dirstate object.
54
54
55 opener is an open()-like callable that can be used to open the
55 opener is an open()-like callable that can be used to open the
56 dirstate file; root is the root of the directory tracked by
56 dirstate file; root is the root of the directory tracked by
57 the dirstate.
57 the dirstate.
58 '''
58 '''
59 self._opener = opener
59 self._opener = opener
60 self._validate = validate
60 self._validate = validate
61 self._root = root
61 self._root = root
62 self._rootdir = os.path.join(root, '')
62 self._rootdir = os.path.join(root, '')
63 self._dirty = False
63 self._dirty = False
64 self._dirtypl = False
64 self._dirtypl = False
65 self._lastnormaltime = 0
65 self._lastnormaltime = 0
66 self._ui = ui
66 self._ui = ui
67 self._filecache = {}
67 self._filecache = {}
68
68
69 @propertycache
69 @propertycache
70 def _map(self):
70 def _map(self):
71 '''Return the dirstate contents as a map from filename to
71 '''Return the dirstate contents as a map from filename to
72 (state, mode, size, time).'''
72 (state, mode, size, time).'''
73 self._read()
73 self._read()
74 return self._map
74 return self._map
75
75
76 @propertycache
76 @propertycache
77 def _copymap(self):
77 def _copymap(self):
78 self._read()
78 self._read()
79 return self._copymap
79 return self._copymap
80
80
81 @propertycache
81 @propertycache
82 def _foldmap(self):
82 def _foldmap(self):
83 f = {}
83 f = {}
84 for name in self._map:
84 for name in self._map:
85 f[util.normcase(name)] = name
85 f[util.normcase(name)] = name
86 for name in self._dirs:
86 for name in self._dirs:
87 f[util.normcase(name)] = name
87 f[util.normcase(name)] = name
88 f['.'] = '.' # prevents useless util.fspath() invocation
88 f['.'] = '.' # prevents useless util.fspath() invocation
89 return f
89 return f
90
90
91 @repocache('branch')
91 @repocache('branch')
92 def _branch(self):
92 def _branch(self):
93 try:
93 try:
94 return self._opener.read("branch").strip() or "default"
94 return self._opener.read("branch").strip() or "default"
95 except IOError, inst:
95 except IOError, inst:
96 if inst.errno != errno.ENOENT:
96 if inst.errno != errno.ENOENT:
97 raise
97 raise
98 return "default"
98 return "default"
99
99
100 @propertycache
100 @propertycache
101 def _pl(self):
101 def _pl(self):
102 try:
102 try:
103 fp = self._opener("dirstate")
103 fp = self._opener("dirstate")
104 st = fp.read(40)
104 st = fp.read(40)
105 fp.close()
105 fp.close()
106 l = len(st)
106 l = len(st)
107 if l == 40:
107 if l == 40:
108 return st[:20], st[20:40]
108 return st[:20], st[20:40]
109 elif l > 0 and l < 40:
109 elif l > 0 and l < 40:
110 raise util.Abort(_('working directory state appears damaged!'))
110 raise util.Abort(_('working directory state appears damaged!'))
111 except IOError, err:
111 except IOError, err:
112 if err.errno != errno.ENOENT:
112 if err.errno != errno.ENOENT:
113 raise
113 raise
114 return [nullid, nullid]
114 return [nullid, nullid]
115
115
116 @propertycache
116 @propertycache
117 def _dirs(self):
117 def _dirs(self):
118 dirs = {}
118 dirs = {}
119 for f, s in self._map.iteritems():
119 for f, s in self._map.iteritems():
120 if s[0] != 'r':
120 if s[0] != 'r':
121 _incdirs(dirs, f)
121 _incdirs(dirs, f)
122 return dirs
122 return dirs
123
123
124 def dirs(self):
124 def dirs(self):
125 return self._dirs
125 return self._dirs
126
126
127 @rootcache('.hgignore')
127 @rootcache('.hgignore')
128 def _ignore(self):
128 def _ignore(self):
129 files = [self._join('.hgignore')]
129 files = [self._join('.hgignore')]
130 for name, path in self._ui.configitems("ui"):
130 for name, path in self._ui.configitems("ui"):
131 if name == 'ignore' or name.startswith('ignore.'):
131 if name == 'ignore' or name.startswith('ignore.'):
132 files.append(util.expandpath(path))
132 files.append(util.expandpath(path))
133 return ignore.ignore(self._root, files, self._ui.warn)
133 return ignore.ignore(self._root, files, self._ui.warn)
134
134
135 @propertycache
135 @propertycache
136 def _slash(self):
136 def _slash(self):
137 return self._ui.configbool('ui', 'slash') and os.sep != '/'
137 return self._ui.configbool('ui', 'slash') and os.sep != '/'
138
138
139 @propertycache
139 @propertycache
140 def _checklink(self):
140 def _checklink(self):
141 return util.checklink(self._root)
141 return util.checklink(self._root)
142
142
143 @propertycache
143 @propertycache
144 def _checkexec(self):
144 def _checkexec(self):
145 return util.checkexec(self._root)
145 return util.checkexec(self._root)
146
146
147 @propertycache
147 @propertycache
148 def _checkcase(self):
148 def _checkcase(self):
149 return not util.checkcase(self._join('.hg'))
149 return not util.checkcase(self._join('.hg'))
150
150
151 def _join(self, f):
151 def _join(self, f):
152 # much faster than os.path.join()
152 # much faster than os.path.join()
153 # it's safe because f is always a relative path
153 # it's safe because f is always a relative path
154 return self._rootdir + f
154 return self._rootdir + f
155
155
156 def flagfunc(self, buildfallback):
156 def flagfunc(self, buildfallback):
157 if self._checklink and self._checkexec:
157 if self._checklink and self._checkexec:
158 def f(x):
158 def f(x):
159 p = self._join(x)
159 p = self._join(x)
160 if os.path.islink(p):
160 if os.path.islink(p):
161 return 'l'
161 return 'l'
162 if util.isexec(p):
162 if util.isexec(p):
163 return 'x'
163 return 'x'
164 return ''
164 return ''
165 return f
165 return f
166
166
167 fallback = buildfallback()
167 fallback = buildfallback()
168 if self._checklink:
168 if self._checklink:
169 def f(x):
169 def f(x):
170 if os.path.islink(self._join(x)):
170 if os.path.islink(self._join(x)):
171 return 'l'
171 return 'l'
172 if 'x' in fallback(x):
172 if 'x' in fallback(x):
173 return 'x'
173 return 'x'
174 return ''
174 return ''
175 return f
175 return f
176 if self._checkexec:
176 if self._checkexec:
177 def f(x):
177 def f(x):
178 if 'l' in fallback(x):
178 if 'l' in fallback(x):
179 return 'l'
179 return 'l'
180 if util.isexec(self._join(x)):
180 if util.isexec(self._join(x)):
181 return 'x'
181 return 'x'
182 return ''
182 return ''
183 return f
183 return f
184 else:
184 else:
185 return fallback
185 return fallback
186
186
187 def getcwd(self):
187 def getcwd(self):
188 cwd = os.getcwd()
188 cwd = os.getcwd()
189 if cwd == self._root:
189 if cwd == self._root:
190 return ''
190 return ''
191 # self._root ends with a path separator if self._root is '/' or 'C:\'
191 # self._root ends with a path separator if self._root is '/' or 'C:\'
192 rootsep = self._root
192 rootsep = self._root
193 if not util.endswithsep(rootsep):
193 if not util.endswithsep(rootsep):
194 rootsep += os.sep
194 rootsep += os.sep
195 if cwd.startswith(rootsep):
195 if cwd.startswith(rootsep):
196 return cwd[len(rootsep):]
196 return cwd[len(rootsep):]
197 else:
197 else:
198 # we're outside the repo. return an absolute path.
198 # we're outside the repo. return an absolute path.
199 return cwd
199 return cwd
200
200
201 def pathto(self, f, cwd=None):
201 def pathto(self, f, cwd=None):
202 if cwd is None:
202 if cwd is None:
203 cwd = self.getcwd()
203 cwd = self.getcwd()
204 path = util.pathto(self._root, cwd, f)
204 path = util.pathto(self._root, cwd, f)
205 if self._slash:
205 if self._slash:
206 return util.normpath(path)
206 return util.normpath(path)
207 return path
207 return path
208
208
209 def __getitem__(self, key):
209 def __getitem__(self, key):
210 '''Return the current state of key (a filename) in the dirstate.
210 '''Return the current state of key (a filename) in the dirstate.
211
211
212 States are:
212 States are:
213 n normal
213 n normal
214 m needs merging
214 m needs merging
215 r marked for removal
215 r marked for removal
216 a marked for addition
216 a marked for addition
217 ? not tracked
217 ? not tracked
218 '''
218 '''
219 return self._map.get(key, ("?",))[0]
219 return self._map.get(key, ("?",))[0]
220
220
221 def __contains__(self, key):
221 def __contains__(self, key):
222 return key in self._map
222 return key in self._map
223
223
224 def __iter__(self):
224 def __iter__(self):
225 for x in sorted(self._map):
225 for x in sorted(self._map):
226 yield x
226 yield x
227
227
228 def parents(self):
228 def parents(self):
229 return [self._validate(p) for p in self._pl]
229 return [self._validate(p) for p in self._pl]
230
230
231 def p1(self):
231 def p1(self):
232 return self._validate(self._pl[0])
232 return self._validate(self._pl[0])
233
233
234 def p2(self):
234 def p2(self):
235 return self._validate(self._pl[1])
235 return self._validate(self._pl[1])
236
236
237 def branch(self):
237 def branch(self):
238 return encoding.tolocal(self._branch)
238 return encoding.tolocal(self._branch)
239
239
240 def setparents(self, p1, p2=nullid):
240 def setparents(self, p1, p2=nullid):
241 """Set dirstate parents to p1 and p2.
241 """Set dirstate parents to p1 and p2.
242
242
243 When moving from two parents to one, 'm' merged entries a
243 When moving from two parents to one, 'm' merged entries a
244 adjusted to normal and previous copy records discarded and
244 adjusted to normal and previous copy records discarded and
245 returned by the call.
245 returned by the call.
246
246
247 See localrepo.setparents()
247 See localrepo.setparents()
248 """
248 """
249 self._dirty = self._dirtypl = True
249 self._dirty = self._dirtypl = True
250 oldp2 = self._pl[1]
250 oldp2 = self._pl[1]
251 self._pl = p1, p2
251 self._pl = p1, p2
252 copies = {}
252 copies = {}
253 if oldp2 != nullid and p2 == nullid:
253 if oldp2 != nullid and p2 == nullid:
254 # Discard 'm' markers when moving away from a merge state
254 # Discard 'm' markers when moving away from a merge state
255 for f, s in self._map.iteritems():
255 for f, s in self._map.iteritems():
256 if s[0] == 'm':
256 if s[0] == 'm':
257 if f in self._copymap:
257 if f in self._copymap:
258 copies[f] = self._copymap[f]
258 copies[f] = self._copymap[f]
259 self.normallookup(f)
259 self.normallookup(f)
260 return copies
260 return copies
261
261
262 def setbranch(self, branch):
262 def setbranch(self, branch):
263 self._branch = encoding.fromlocal(branch)
263 self._branch = encoding.fromlocal(branch)
264 f = self._opener('branch', 'w', atomictemp=True)
264 f = self._opener('branch', 'w', atomictemp=True)
265 try:
265 try:
266 f.write(self._branch + '\n')
266 f.write(self._branch + '\n')
267 finally:
267 finally:
268 f.close()
268 f.close()
269
269
270 def _read(self):
270 def _read(self):
271 self._map = {}
271 self._map = {}
272 self._copymap = {}
272 self._copymap = {}
273 try:
273 try:
274 st = self._opener.read("dirstate")
274 st = self._opener.read("dirstate")
275 except IOError, err:
275 except IOError, err:
276 if err.errno != errno.ENOENT:
276 if err.errno != errno.ENOENT:
277 raise
277 raise
278 return
278 return
279 if not st:
279 if not st:
280 return
280 return
281
281
282 p = parsers.parse_dirstate(self._map, self._copymap, st)
282 p = parsers.parse_dirstate(self._map, self._copymap, st)
283 if not self._dirtypl:
283 if not self._dirtypl:
284 self._pl = p
284 self._pl = p
285
285
286 def invalidate(self):
286 def invalidate(self):
287 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
287 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
288 "_ignore"):
288 "_ignore"):
289 if a in self.__dict__:
289 if a in self.__dict__:
290 delattr(self, a)
290 delattr(self, a)
291 self._lastnormaltime = 0
291 self._lastnormaltime = 0
292 self._dirty = False
292 self._dirty = False
293
293
294 def copy(self, source, dest):
294 def copy(self, source, dest):
295 """Mark dest as a copy of source. Unmark dest if source is None."""
295 """Mark dest as a copy of source. Unmark dest if source is None."""
296 if source == dest:
296 if source == dest:
297 return
297 return
298 self._dirty = True
298 self._dirty = True
299 if source is not None:
299 if source is not None:
300 self._copymap[dest] = source
300 self._copymap[dest] = source
301 elif dest in self._copymap:
301 elif dest in self._copymap:
302 del self._copymap[dest]
302 del self._copymap[dest]
303
303
304 def copied(self, file):
304 def copied(self, file):
305 return self._copymap.get(file, None)
305 return self._copymap.get(file, None)
306
306
307 def copies(self):
307 def copies(self):
308 return self._copymap
308 return self._copymap
309
309
310 def _droppath(self, f):
310 def _droppath(self, f):
311 if self[f] not in "?r" and "_dirs" in self.__dict__:
311 if self[f] not in "?r" and "_dirs" in self.__dict__:
312 _decdirs(self._dirs, f)
312 _decdirs(self._dirs, f)
313
313
314 def _addpath(self, f, state, mode, size, mtime):
314 def _addpath(self, f, state, mode, size, mtime):
315 oldstate = self[f]
315 oldstate = self[f]
316 if state == 'a' or oldstate == 'r':
316 if state == 'a' or oldstate == 'r':
317 scmutil.checkfilename(f)
317 scmutil.checkfilename(f)
318 if f in self._dirs:
318 if f in self._dirs:
319 raise util.Abort(_('directory %r already in dirstate') % f)
319 raise util.Abort(_('directory %r already in dirstate') % f)
320 # shadows
320 # shadows
321 for d in _finddirs(f):
321 for d in _finddirs(f):
322 if d in self._dirs:
322 if d in self._dirs:
323 break
323 break
324 if d in self._map and self[d] != 'r':
324 if d in self._map and self[d] != 'r':
325 raise util.Abort(
325 raise util.Abort(
326 _('file %r in dirstate clashes with %r') % (d, f))
326 _('file %r in dirstate clashes with %r') % (d, f))
327 if oldstate in "?r" and "_dirs" in self.__dict__:
327 if oldstate in "?r" and "_dirs" in self.__dict__:
328 _incdirs(self._dirs, f)
328 _incdirs(self._dirs, f)
329 self._dirty = True
329 self._dirty = True
330 self._map[f] = (state, mode, size, mtime)
330 self._map[f] = (state, mode, size, mtime)
331
331
332 def normal(self, f):
332 def normal(self, f):
333 '''Mark a file normal and clean.'''
333 '''Mark a file normal and clean.'''
334 s = os.lstat(self._join(f))
334 s = os.lstat(self._join(f))
335 mtime = int(s.st_mtime)
335 mtime = int(s.st_mtime)
336 self._addpath(f, 'n', s.st_mode,
336 self._addpath(f, 'n', s.st_mode,
337 s.st_size & _rangemask, mtime & _rangemask)
337 s.st_size & _rangemask, mtime & _rangemask)
338 if f in self._copymap:
338 if f in self._copymap:
339 del self._copymap[f]
339 del self._copymap[f]
340 if mtime > self._lastnormaltime:
340 if mtime > self._lastnormaltime:
341 # Remember the most recent modification timeslot for status(),
341 # Remember the most recent modification timeslot for status(),
342 # to make sure we won't miss future size-preserving file content
342 # to make sure we won't miss future size-preserving file content
343 # modifications that happen within the same timeslot.
343 # modifications that happen within the same timeslot.
344 self._lastnormaltime = mtime
344 self._lastnormaltime = mtime
345
345
346 def normallookup(self, f):
346 def normallookup(self, f):
347 '''Mark a file normal, but possibly dirty.'''
347 '''Mark a file normal, but possibly dirty.'''
348 if self._pl[1] != nullid and f in self._map:
348 if self._pl[1] != nullid and f in self._map:
349 # if there is a merge going on and the file was either
349 # if there is a merge going on and the file was either
350 # in state 'm' (-1) or coming from other parent (-2) before
350 # in state 'm' (-1) or coming from other parent (-2) before
351 # being removed, restore that state.
351 # being removed, restore that state.
352 entry = self._map[f]
352 entry = self._map[f]
353 if entry[0] == 'r' and entry[2] in (-1, -2):
353 if entry[0] == 'r' and entry[2] in (-1, -2):
354 source = self._copymap.get(f)
354 source = self._copymap.get(f)
355 if entry[2] == -1:
355 if entry[2] == -1:
356 self.merge(f)
356 self.merge(f)
357 elif entry[2] == -2:
357 elif entry[2] == -2:
358 self.otherparent(f)
358 self.otherparent(f)
359 if source:
359 if source:
360 self.copy(source, f)
360 self.copy(source, f)
361 return
361 return
362 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
362 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
363 return
363 return
364 self._addpath(f, 'n', 0, -1, -1)
364 self._addpath(f, 'n', 0, -1, -1)
365 if f in self._copymap:
365 if f in self._copymap:
366 del self._copymap[f]
366 del self._copymap[f]
367
367
368 def otherparent(self, f):
368 def otherparent(self, f):
369 '''Mark as coming from the other parent, always dirty.'''
369 '''Mark as coming from the other parent, always dirty.'''
370 if self._pl[1] == nullid:
370 if self._pl[1] == nullid:
371 raise util.Abort(_("setting %r to other parent "
371 raise util.Abort(_("setting %r to other parent "
372 "only allowed in merges") % f)
372 "only allowed in merges") % f)
373 self._addpath(f, 'n', 0, -2, -1)
373 self._addpath(f, 'n', 0, -2, -1)
374 if f in self._copymap:
374 if f in self._copymap:
375 del self._copymap[f]
375 del self._copymap[f]
376
376
377 def add(self, f):
377 def add(self, f):
378 '''Mark a file added.'''
378 '''Mark a file added.'''
379 self._addpath(f, 'a', 0, -1, -1)
379 self._addpath(f, 'a', 0, -1, -1)
380 if f in self._copymap:
380 if f in self._copymap:
381 del self._copymap[f]
381 del self._copymap[f]
382
382
383 def remove(self, f):
383 def remove(self, f):
384 '''Mark a file removed.'''
384 '''Mark a file removed.'''
385 self._dirty = True
385 self._dirty = True
386 self._droppath(f)
386 self._droppath(f)
387 size = 0
387 size = 0
388 if self._pl[1] != nullid and f in self._map:
388 if self._pl[1] != nullid and f in self._map:
389 # backup the previous state
389 # backup the previous state
390 entry = self._map[f]
390 entry = self._map[f]
391 if entry[0] == 'm': # merge
391 if entry[0] == 'm': # merge
392 size = -1
392 size = -1
393 elif entry[0] == 'n' and entry[2] == -2: # other parent
393 elif entry[0] == 'n' and entry[2] == -2: # other parent
394 size = -2
394 size = -2
395 self._map[f] = ('r', 0, size, 0)
395 self._map[f] = ('r', 0, size, 0)
396 if size == 0 and f in self._copymap:
396 if size == 0 and f in self._copymap:
397 del self._copymap[f]
397 del self._copymap[f]
398
398
399 def merge(self, f):
399 def merge(self, f):
400 '''Mark a file merged.'''
400 '''Mark a file merged.'''
401 if self._pl[1] == nullid:
401 if self._pl[1] == nullid:
402 return self.normallookup(f)
402 return self.normallookup(f)
403 s = os.lstat(self._join(f))
403 s = os.lstat(self._join(f))
404 self._addpath(f, 'm', s.st_mode,
404 self._addpath(f, 'm', s.st_mode,
405 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
405 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
406 if f in self._copymap:
406 if f in self._copymap:
407 del self._copymap[f]
407 del self._copymap[f]
408
408
409 def drop(self, f):
409 def drop(self, f):
410 '''Drop a file from the dirstate'''
410 '''Drop a file from the dirstate'''
411 if f in self._map:
411 if f in self._map:
412 self._dirty = True
412 self._dirty = True
413 self._droppath(f)
413 self._droppath(f)
414 del self._map[f]
414 del self._map[f]
415
415
416 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
416 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
417 normed = util.normcase(path)
417 normed = util.normcase(path)
418 folded = self._foldmap.get(normed, None)
418 folded = self._foldmap.get(normed, None)
419 if folded is None:
419 if folded is None:
420 if isknown:
420 if isknown:
421 folded = path
421 folded = path
422 else:
422 else:
423 if exists is None:
423 if exists is None:
424 exists = os.path.lexists(os.path.join(self._root, path))
424 exists = os.path.lexists(os.path.join(self._root, path))
425 if not exists:
425 if not exists:
426 # Maybe a path component exists
426 # Maybe a path component exists
427 if not ignoremissing and '/' in path:
427 if not ignoremissing and '/' in path:
428 d, f = path.rsplit('/', 1)
428 d, f = path.rsplit('/', 1)
429 d = self._normalize(d, isknown, ignoremissing, None)
429 d = self._normalize(d, isknown, ignoremissing, None)
430 folded = d + "/" + f
430 folded = d + "/" + f
431 else:
431 else:
432 # No path components, preserve original case
432 # No path components, preserve original case
433 folded = path
433 folded = path
434 else:
434 else:
435 # recursively normalize leading directory components
435 # recursively normalize leading directory components
436 # against dirstate
436 # against dirstate
437 if '/' in normed:
437 if '/' in normed:
438 d, f = normed.rsplit('/', 1)
438 d, f = normed.rsplit('/', 1)
439 d = self._normalize(d, isknown, ignoremissing, True)
439 d = self._normalize(d, isknown, ignoremissing, True)
440 r = self._root + "/" + d
440 r = self._root + "/" + d
441 folded = d + "/" + util.fspath(f, r)
441 folded = d + "/" + util.fspath(f, r)
442 else:
442 else:
443 folded = util.fspath(normed, self._root)
443 folded = util.fspath(normed, self._root)
444 self._foldmap[normed] = folded
444 self._foldmap[normed] = folded
445
445
446 return folded
446 return folded
447
447
448 def normalize(self, path, isknown=False, ignoremissing=False):
448 def normalize(self, path, isknown=False, ignoremissing=False):
449 '''
449 '''
450 normalize the case of a pathname when on a casefolding filesystem
450 normalize the case of a pathname when on a casefolding filesystem
451
451
452 isknown specifies whether the filename came from walking the
452 isknown specifies whether the filename came from walking the
453 disk, to avoid extra filesystem access.
453 disk, to avoid extra filesystem access.
454
454
455 If ignoremissing is True, missing path are returned
455 If ignoremissing is True, missing path are returned
456 unchanged. Otherwise, we try harder to normalize possibly
456 unchanged. Otherwise, we try harder to normalize possibly
457 existing path components.
457 existing path components.
458
458
459 The normalized case is determined based on the following precedence:
459 The normalized case is determined based on the following precedence:
460
460
461 - version of name already stored in the dirstate
461 - version of name already stored in the dirstate
462 - version of name stored on disk
462 - version of name stored on disk
463 - version provided via command arguments
463 - version provided via command arguments
464 '''
464 '''
465
465
466 if self._checkcase:
466 if self._checkcase:
467 return self._normalize(path, isknown, ignoremissing)
467 return self._normalize(path, isknown, ignoremissing)
468 return path
468 return path
469
469
470 def clear(self):
470 def clear(self):
471 self._map = {}
471 self._map = {}
472 if "_dirs" in self.__dict__:
472 if "_dirs" in self.__dict__:
473 delattr(self, "_dirs")
473 delattr(self, "_dirs")
474 self._copymap = {}
474 self._copymap = {}
475 self._pl = [nullid, nullid]
475 self._pl = [nullid, nullid]
476 self._lastnormaltime = 0
476 self._lastnormaltime = 0
477 self._dirty = True
477 self._dirty = True
478
478
479 def rebuild(self, parent, files):
479 def rebuild(self, parent, files):
480 self.clear()
480 self.clear()
481 for f in files:
481 for f in files:
482 if 'x' in files.flags(f):
482 if 'x' in files.flags(f):
483 self._map[f] = ('n', 0777, -1, 0)
483 self._map[f] = ('n', 0777, -1, 0)
484 else:
484 else:
485 self._map[f] = ('n', 0666, -1, 0)
485 self._map[f] = ('n', 0666, -1, 0)
486 self._pl = (parent, nullid)
486 self._pl = (parent, nullid)
487 self._dirty = True
487 self._dirty = True
488
488
489 def write(self):
489 def write(self):
490 if not self._dirty:
490 if not self._dirty:
491 return
491 return
492 st = self._opener("dirstate", "w", atomictemp=True)
492 st = self._opener("dirstate", "w", atomictemp=True)
493
493
494 def finish(s):
494 def finish(s):
495 st.write(s)
495 st.write(s)
496 st.close()
496 st.close()
497 self._lastnormaltime = 0
497 self._lastnormaltime = 0
498 self._dirty = self._dirtypl = False
498 self._dirty = self._dirtypl = False
499
499
500 # use the modification time of the newly created temporary file as the
500 # use the modification time of the newly created temporary file as the
501 # filesystem's notion of 'now'
501 # filesystem's notion of 'now'
502 now = util.fstat(st).st_mtime
502 now = util.fstat(st).st_mtime
503 copymap = self._copymap
503 copymap = self._copymap
504 try:
504 try:
505 finish(parsers.pack_dirstate(self._map, copymap, self._pl, now))
505 finish(parsers.pack_dirstate(self._map, copymap, self._pl, now))
506 return
506 return
507 except AttributeError:
507 except AttributeError:
508 pass
508 pass
509
509
510 now = int(now)
510 now = int(now)
511 cs = cStringIO.StringIO()
511 cs = cStringIO.StringIO()
512 pack = struct.pack
512 pack = struct.pack
513 write = cs.write
513 write = cs.write
514 write("".join(self._pl))
514 write("".join(self._pl))
515 for f, e in self._map.iteritems():
515 for f, e in self._map.iteritems():
516 if e[0] == 'n' and e[3] == now:
516 if e[0] == 'n' and e[3] == now:
517 # The file was last modified "simultaneously" with the current
517 # The file was last modified "simultaneously" with the current
518 # write to dirstate (i.e. within the same second for file-
518 # write to dirstate (i.e. within the same second for file-
519 # systems with a granularity of 1 sec). This commonly happens
519 # systems with a granularity of 1 sec). This commonly happens
520 # for at least a couple of files on 'update'.
520 # for at least a couple of files on 'update'.
521 # The user could change the file without changing its size
521 # The user could change the file without changing its size
522 # within the same second. Invalidate the file's stat data in
522 # within the same second. Invalidate the file's stat data in
523 # dirstate, forcing future 'status' calls to compare the
523 # dirstate, forcing future 'status' calls to compare the
524 # contents of the file. This prevents mistakenly treating such
524 # contents of the file. This prevents mistakenly treating such
525 # files as clean.
525 # files as clean.
526 e = (e[0], 0, -1, -1) # mark entry as 'unset'
526 e = (e[0], 0, -1, -1) # mark entry as 'unset'
527 self._map[f] = e
527 self._map[f] = e
528
528
529 if f in copymap:
529 if f in copymap:
530 f = "%s\0%s" % (f, copymap[f])
530 f = "%s\0%s" % (f, copymap[f])
531 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
531 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
532 write(e)
532 write(e)
533 write(f)
533 write(f)
534 finish(cs.getvalue())
534 finish(cs.getvalue())
535
535
536 def _dirignore(self, f):
536 def _dirignore(self, f):
537 if f == '.':
537 if f == '.':
538 return False
538 return False
539 if self._ignore(f):
539 if self._ignore(f):
540 return True
540 return True
541 for p in _finddirs(f):
541 for p in _finddirs(f):
542 if self._ignore(p):
542 if self._ignore(p):
543 return True
543 return True
544 return False
544 return False
545
545
546 def walk(self, match, subrepos, unknown, ignored):
546 def walk(self, match, subrepos, unknown, ignored):
547 '''
547 '''
548 Walk recursively through the directory tree, finding all files
548 Walk recursively through the directory tree, finding all files
549 matched by match.
549 matched by match.
550
550
551 Return a dict mapping filename to stat-like object (either
551 Return a dict mapping filename to stat-like object (either
552 mercurial.osutil.stat instance or return value of os.stat()).
552 mercurial.osutil.stat instance or return value of os.stat()).
553 '''
553 '''
554
554
555 def fwarn(f, msg):
555 def fwarn(f, msg):
556 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
556 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
557 return False
557 return False
558
558
559 def badtype(mode):
559 def badtype(mode):
560 kind = _('unknown')
560 kind = _('unknown')
561 if stat.S_ISCHR(mode):
561 if stat.S_ISCHR(mode):
562 kind = _('character device')
562 kind = _('character device')
563 elif stat.S_ISBLK(mode):
563 elif stat.S_ISBLK(mode):
564 kind = _('block device')
564 kind = _('block device')
565 elif stat.S_ISFIFO(mode):
565 elif stat.S_ISFIFO(mode):
566 kind = _('fifo')
566 kind = _('fifo')
567 elif stat.S_ISSOCK(mode):
567 elif stat.S_ISSOCK(mode):
568 kind = _('socket')
568 kind = _('socket')
569 elif stat.S_ISDIR(mode):
569 elif stat.S_ISDIR(mode):
570 kind = _('directory')
570 kind = _('directory')
571 return _('unsupported file type (type is %s)') % kind
571 return _('unsupported file type (type is %s)') % kind
572
572
573 ignore = self._ignore
573 ignore = self._ignore
574 dirignore = self._dirignore
574 dirignore = self._dirignore
575 if ignored:
575 if ignored:
576 ignore = util.never
576 ignore = util.never
577 dirignore = util.never
577 dirignore = util.never
578 elif not unknown:
578 elif not unknown:
579 # if unknown and ignored are False, skip step 2
579 # if unknown and ignored are False, skip step 2
580 ignore = util.always
580 ignore = util.always
581 dirignore = util.always
581 dirignore = util.always
582
582
583 matchfn = match.matchfn
583 matchfn = match.matchfn
584 badfn = match.bad
584 badfn = match.bad
585 dmap = self._map
585 dmap = self._map
586 normpath = util.normpath
586 normpath = util.normpath
587 listdir = osutil.listdir
587 listdir = osutil.listdir
588 lstat = os.lstat
588 lstat = os.lstat
589 getkind = stat.S_IFMT
589 getkind = stat.S_IFMT
590 dirkind = stat.S_IFDIR
590 dirkind = stat.S_IFDIR
591 regkind = stat.S_IFREG
591 regkind = stat.S_IFREG
592 lnkkind = stat.S_IFLNK
592 lnkkind = stat.S_IFLNK
593 join = self._join
593 join = self._join
594 work = []
594 work = []
595 wadd = work.append
595 wadd = work.append
596
596
597 exact = skipstep3 = False
597 exact = skipstep3 = False
598 if matchfn == match.exact: # match.exact
598 if matchfn == match.exact: # match.exact
599 exact = True
599 exact = True
600 dirignore = util.always # skip step 2
600 dirignore = util.always # skip step 2
601 elif match.files() and not match.anypats(): # match.match, no patterns
601 elif match.files() and not match.anypats(): # match.match, no patterns
602 skipstep3 = True
602 skipstep3 = True
603
603
604 if not exact and self._checkcase:
604 if not exact and self._checkcase:
605 normalize = self._normalize
605 normalize = self._normalize
606 skipstep3 = False
606 skipstep3 = False
607 else:
607 else:
608 normalize = lambda x, y, z: x
608 normalize = lambda x, y, z: x
609
609
610 files = sorted(match.files())
610 files = sorted(match.files())
611 subrepos.sort()
611 subrepos.sort()
612 i, j = 0, 0
612 i, j = 0, 0
613 while i < len(files) and j < len(subrepos):
613 while i < len(files) and j < len(subrepos):
614 subpath = subrepos[j] + "/"
614 subpath = subrepos[j] + "/"
615 if files[i] < subpath:
615 if files[i] < subpath:
616 i += 1
616 i += 1
617 continue
617 continue
618 while i < len(files) and files[i].startswith(subpath):
618 while i < len(files) and files[i].startswith(subpath):
619 del files[i]
619 del files[i]
620 j += 1
620 j += 1
621
621
622 if not files or '.' in files:
622 if not files or '.' in files:
623 files = ['']
623 files = ['']
624 results = dict.fromkeys(subrepos)
624 results = dict.fromkeys(subrepos)
625 results['.hg'] = None
625 results['.hg'] = None
626
626
627 # step 1: find all explicit files
627 # step 1: find all explicit files
628 for ff in files:
628 for ff in files:
629 nf = normalize(normpath(ff), False, True)
629 nf = normalize(normpath(ff), False, True)
630 if nf in results:
630 if nf in results:
631 continue
631 continue
632
632
633 try:
633 try:
634 st = lstat(join(nf))
634 st = lstat(join(nf))
635 kind = getkind(st.st_mode)
635 kind = getkind(st.st_mode)
636 if kind == dirkind:
636 if kind == dirkind:
637 skipstep3 = False
637 skipstep3 = False
638 if nf in dmap:
638 if nf in dmap:
639 #file deleted on disk but still in dirstate
639 #file deleted on disk but still in dirstate
640 results[nf] = None
640 results[nf] = None
641 match.dir(nf)
641 match.dir(nf)
642 if not dirignore(nf):
642 if not dirignore(nf):
643 wadd(nf)
643 wadd(nf)
644 elif kind == regkind or kind == lnkkind:
644 elif kind == regkind or kind == lnkkind:
645 results[nf] = st
645 results[nf] = st
646 else:
646 else:
647 badfn(ff, badtype(kind))
647 badfn(ff, badtype(kind))
648 if nf in dmap:
648 if nf in dmap:
649 results[nf] = None
649 results[nf] = None
650 except OSError, inst:
650 except OSError, inst:
651 if nf in dmap: # does it exactly match a file?
651 if nf in dmap: # does it exactly match a file?
652 results[nf] = None
652 results[nf] = None
653 else: # does it match a directory?
653 else: # does it match a directory?
654 prefix = nf + "/"
654 prefix = nf + "/"
655 for fn in dmap:
655 for fn in dmap:
656 if fn.startswith(prefix):
656 if fn.startswith(prefix):
657 match.dir(nf)
657 match.dir(nf)
658 skipstep3 = False
658 skipstep3 = False
659 break
659 break
660 else:
660 else:
661 badfn(ff, inst.strerror)
661 badfn(ff, inst.strerror)
662
662
663 # step 2: visit subdirectories
663 # step 2: visit subdirectories
664 while work:
664 while work:
665 nd = work.pop()
665 nd = work.pop()
666 skip = None
666 skip = None
667 if nd == '.':
667 if nd == '.':
668 nd = ''
668 nd = ''
669 else:
669 else:
670 skip = '.hg'
670 skip = '.hg'
671 try:
671 try:
672 entries = listdir(join(nd), stat=True, skip=skip)
672 entries = listdir(join(nd), stat=True, skip=skip)
673 except OSError, inst:
673 except OSError, inst:
674 if inst.errno in (errno.EACCES, errno.ENOENT):
674 if inst.errno in (errno.EACCES, errno.ENOENT):
675 fwarn(nd, inst.strerror)
675 fwarn(nd, inst.strerror)
676 continue
676 continue
677 raise
677 raise
678 for f, kind, st in entries:
678 for f, kind, st in entries:
679 nf = normalize(nd and (nd + "/" + f) or f, True, True)
679 nf = normalize(nd and (nd + "/" + f) or f, True, True)
680 if nf not in results:
680 if nf not in results:
681 if kind == dirkind:
681 if kind == dirkind:
682 if not ignore(nf):
682 if not ignore(nf):
683 match.dir(nf)
683 match.dir(nf)
684 wadd(nf)
684 wadd(nf)
685 if nf in dmap and matchfn(nf):
685 if nf in dmap and matchfn(nf):
686 results[nf] = None
686 results[nf] = None
687 elif kind == regkind or kind == lnkkind:
687 elif kind == regkind or kind == lnkkind:
688 if nf in dmap:
688 if nf in dmap:
689 if matchfn(nf):
689 if matchfn(nf):
690 results[nf] = st
690 results[nf] = st
691 elif matchfn(nf) and not ignore(nf):
691 elif matchfn(nf) and not ignore(nf):
692 results[nf] = st
692 results[nf] = st
693 elif nf in dmap and matchfn(nf):
693 elif nf in dmap and matchfn(nf):
694 results[nf] = None
694 results[nf] = None
695
695
696 # step 3: report unseen items in the dmap hash
696 # step 3: report unseen items in the dmap hash
697 if not skipstep3 and not exact:
697 if not skipstep3 and not exact:
698 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
698 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
699 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
699 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
700 if (not st is None and
701 getkind(st.st_mode) not in (regkind, lnkkind)):
702 st = None
703 results[nf] = st
700 results[nf] = st
704 for s in subrepos:
701 for s in subrepos:
705 del results[s]
702 del results[s]
706 del results['.hg']
703 del results['.hg']
707 return results
704 return results
708
705
709 def status(self, match, subrepos, ignored, clean, unknown):
706 def status(self, match, subrepos, ignored, clean, unknown):
710 '''Determine the status of the working copy relative to the
707 '''Determine the status of the working copy relative to the
711 dirstate and return a tuple of lists (unsure, modified, added,
708 dirstate and return a tuple of lists (unsure, modified, added,
712 removed, deleted, unknown, ignored, clean), where:
709 removed, deleted, unknown, ignored, clean), where:
713
710
714 unsure:
711 unsure:
715 files that might have been modified since the dirstate was
712 files that might have been modified since the dirstate was
716 written, but need to be read to be sure (size is the same
713 written, but need to be read to be sure (size is the same
717 but mtime differs)
714 but mtime differs)
718 modified:
715 modified:
719 files that have definitely been modified since the dirstate
716 files that have definitely been modified since the dirstate
720 was written (different size or mode)
717 was written (different size or mode)
721 added:
718 added:
722 files that have been explicitly added with hg add
719 files that have been explicitly added with hg add
723 removed:
720 removed:
724 files that have been explicitly removed with hg remove
721 files that have been explicitly removed with hg remove
725 deleted:
722 deleted:
726 files that have been deleted through other means ("missing")
723 files that have been deleted through other means ("missing")
727 unknown:
724 unknown:
728 files not in the dirstate that are not ignored
725 files not in the dirstate that are not ignored
729 ignored:
726 ignored:
730 files not in the dirstate that are ignored
727 files not in the dirstate that are ignored
731 (by _dirignore())
728 (by _dirignore())
732 clean:
729 clean:
733 files that have definitely not been modified since the
730 files that have definitely not been modified since the
734 dirstate was written
731 dirstate was written
735 '''
732 '''
736 listignored, listclean, listunknown = ignored, clean, unknown
733 listignored, listclean, listunknown = ignored, clean, unknown
737 lookup, modified, added, unknown, ignored = [], [], [], [], []
734 lookup, modified, added, unknown, ignored = [], [], [], [], []
738 removed, deleted, clean = [], [], []
735 removed, deleted, clean = [], [], []
739
736
740 dmap = self._map
737 dmap = self._map
741 ladd = lookup.append # aka "unsure"
738 ladd = lookup.append # aka "unsure"
742 madd = modified.append
739 madd = modified.append
743 aadd = added.append
740 aadd = added.append
744 uadd = unknown.append
741 uadd = unknown.append
745 iadd = ignored.append
742 iadd = ignored.append
746 radd = removed.append
743 radd = removed.append
747 dadd = deleted.append
744 dadd = deleted.append
748 cadd = clean.append
745 cadd = clean.append
749
746
750 lnkkind = stat.S_IFLNK
747 lnkkind = stat.S_IFLNK
751
748
752 for fn, st in self.walk(match, subrepos, listunknown,
749 for fn, st in self.walk(match, subrepos, listunknown,
753 listignored).iteritems():
750 listignored).iteritems():
754 if fn not in dmap:
751 if fn not in dmap:
755 if (listignored or match.exact(fn)) and self._dirignore(fn):
752 if (listignored or match.exact(fn)) and self._dirignore(fn):
756 if listignored:
753 if listignored:
757 iadd(fn)
754 iadd(fn)
758 elif listunknown:
755 elif listunknown:
759 uadd(fn)
756 uadd(fn)
760 continue
757 continue
761
758
762 state, mode, size, time = dmap[fn]
759 state, mode, size, time = dmap[fn]
763
760
764 if not st and state in "nma":
761 if not st and state in "nma":
765 dadd(fn)
762 dadd(fn)
766 elif state == 'n':
763 elif state == 'n':
767 # The "mode & lnkkind != lnkkind or self._checklink"
764 # The "mode & lnkkind != lnkkind or self._checklink"
768 # lines are an expansion of "islink => checklink"
765 # lines are an expansion of "islink => checklink"
769 # where islink means "is this a link?" and checklink
766 # where islink means "is this a link?" and checklink
770 # means "can we check links?".
767 # means "can we check links?".
771 mtime = int(st.st_mtime)
768 mtime = int(st.st_mtime)
772 if (size >= 0 and
769 if (size >= 0 and
773 ((size != st.st_size and size != st.st_size & _rangemask)
770 ((size != st.st_size and size != st.st_size & _rangemask)
774 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
771 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
775 and (mode & lnkkind != lnkkind or self._checklink)
772 and (mode & lnkkind != lnkkind or self._checklink)
776 or size == -2 # other parent
773 or size == -2 # other parent
777 or fn in self._copymap):
774 or fn in self._copymap):
778 madd(fn)
775 madd(fn)
779 elif ((time != mtime and time != mtime & _rangemask)
776 elif ((time != mtime and time != mtime & _rangemask)
780 and (mode & lnkkind != lnkkind or self._checklink)):
777 and (mode & lnkkind != lnkkind or self._checklink)):
781 ladd(fn)
778 ladd(fn)
782 elif mtime == self._lastnormaltime:
779 elif mtime == self._lastnormaltime:
783 # fn may have been changed in the same timeslot without
780 # fn may have been changed in the same timeslot without
784 # changing its size. This can happen if we quickly do
781 # changing its size. This can happen if we quickly do
785 # multiple commits in a single transaction.
782 # multiple commits in a single transaction.
786 # Force lookup, so we don't miss such a racy file change.
783 # Force lookup, so we don't miss such a racy file change.
787 ladd(fn)
784 ladd(fn)
788 elif listclean:
785 elif listclean:
789 cadd(fn)
786 cadd(fn)
790 elif state == 'm':
787 elif state == 'm':
791 madd(fn)
788 madd(fn)
792 elif state == 'a':
789 elif state == 'a':
793 aadd(fn)
790 aadd(fn)
794 elif state == 'r':
791 elif state == 'r':
795 radd(fn)
792 radd(fn)
796
793
797 return (lookup, modified, added, removed, deleted, unknown, ignored,
794 return (lookup, modified, added, removed, deleted, unknown, ignored,
798 clean)
795 clean)
@@ -1,479 +1,485 b''
1 # posix.py - Posix utility function implementations for Mercurial
1 # posix.py - Posix utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import encoding
9 import encoding
10 import os, sys, errno, stat, getpass, pwd, grp, tempfile, unicodedata
10 import os, sys, errno, stat, getpass, pwd, grp, tempfile, unicodedata
11
11
12 posixfile = open
12 posixfile = open
13 normpath = os.path.normpath
13 normpath = os.path.normpath
14 samestat = os.path.samestat
14 samestat = os.path.samestat
15 oslink = os.link
15 oslink = os.link
16 unlink = os.unlink
16 unlink = os.unlink
17 rename = os.rename
17 rename = os.rename
18 expandglobs = False
18 expandglobs = False
19
19
20 umask = os.umask(0)
20 umask = os.umask(0)
21 os.umask(umask)
21 os.umask(umask)
22
22
23 def split(p):
23 def split(p):
24 '''Same as os.path.split, but faster'''
24 '''Same as os.path.split, but faster'''
25 ht = p.rsplit('/', 1)
25 ht = p.rsplit('/', 1)
26 if len(ht) == 1:
26 if len(ht) == 1:
27 return '', p
27 return '', p
28 nh = ht[0].rstrip('/')
28 nh = ht[0].rstrip('/')
29 if nh:
29 if nh:
30 return nh, ht[1]
30 return nh, ht[1]
31 return ht
31 return ht
32
32
33 def openhardlinks():
33 def openhardlinks():
34 '''return true if it is safe to hold open file handles to hardlinks'''
34 '''return true if it is safe to hold open file handles to hardlinks'''
35 return True
35 return True
36
36
37 def nlinks(name):
37 def nlinks(name):
38 '''return number of hardlinks for the given file'''
38 '''return number of hardlinks for the given file'''
39 return os.lstat(name).st_nlink
39 return os.lstat(name).st_nlink
40
40
41 def parsepatchoutput(output_line):
41 def parsepatchoutput(output_line):
42 """parses the output produced by patch and returns the filename"""
42 """parses the output produced by patch and returns the filename"""
43 pf = output_line[14:]
43 pf = output_line[14:]
44 if os.sys.platform == 'OpenVMS':
44 if os.sys.platform == 'OpenVMS':
45 if pf[0] == '`':
45 if pf[0] == '`':
46 pf = pf[1:-1] # Remove the quotes
46 pf = pf[1:-1] # Remove the quotes
47 else:
47 else:
48 if pf.startswith("'") and pf.endswith("'") and " " in pf:
48 if pf.startswith("'") and pf.endswith("'") and " " in pf:
49 pf = pf[1:-1] # Remove the quotes
49 pf = pf[1:-1] # Remove the quotes
50 return pf
50 return pf
51
51
52 def sshargs(sshcmd, host, user, port):
52 def sshargs(sshcmd, host, user, port):
53 '''Build argument list for ssh'''
53 '''Build argument list for ssh'''
54 args = user and ("%s@%s" % (user, host)) or host
54 args = user and ("%s@%s" % (user, host)) or host
55 return port and ("%s -p %s" % (args, port)) or args
55 return port and ("%s -p %s" % (args, port)) or args
56
56
57 def isexec(f):
57 def isexec(f):
58 """check whether a file is executable"""
58 """check whether a file is executable"""
59 return (os.lstat(f).st_mode & 0100 != 0)
59 return (os.lstat(f).st_mode & 0100 != 0)
60
60
61 def setflags(f, l, x):
61 def setflags(f, l, x):
62 s = os.lstat(f).st_mode
62 s = os.lstat(f).st_mode
63 if l:
63 if l:
64 if not stat.S_ISLNK(s):
64 if not stat.S_ISLNK(s):
65 # switch file to link
65 # switch file to link
66 fp = open(f)
66 fp = open(f)
67 data = fp.read()
67 data = fp.read()
68 fp.close()
68 fp.close()
69 os.unlink(f)
69 os.unlink(f)
70 try:
70 try:
71 os.symlink(data, f)
71 os.symlink(data, f)
72 except OSError:
72 except OSError:
73 # failed to make a link, rewrite file
73 # failed to make a link, rewrite file
74 fp = open(f, "w")
74 fp = open(f, "w")
75 fp.write(data)
75 fp.write(data)
76 fp.close()
76 fp.close()
77 # no chmod needed at this point
77 # no chmod needed at this point
78 return
78 return
79 if stat.S_ISLNK(s):
79 if stat.S_ISLNK(s):
80 # switch link to file
80 # switch link to file
81 data = os.readlink(f)
81 data = os.readlink(f)
82 os.unlink(f)
82 os.unlink(f)
83 fp = open(f, "w")
83 fp = open(f, "w")
84 fp.write(data)
84 fp.write(data)
85 fp.close()
85 fp.close()
86 s = 0666 & ~umask # avoid restatting for chmod
86 s = 0666 & ~umask # avoid restatting for chmod
87
87
88 sx = s & 0100
88 sx = s & 0100
89 if x and not sx:
89 if x and not sx:
90 # Turn on +x for every +r bit when making a file executable
90 # Turn on +x for every +r bit when making a file executable
91 # and obey umask.
91 # and obey umask.
92 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
92 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
93 elif not x and sx:
93 elif not x and sx:
94 # Turn off all +x bits
94 # Turn off all +x bits
95 os.chmod(f, s & 0666)
95 os.chmod(f, s & 0666)
96
96
97 def copymode(src, dst, mode=None):
97 def copymode(src, dst, mode=None):
98 '''Copy the file mode from the file at path src to dst.
98 '''Copy the file mode from the file at path src to dst.
99 If src doesn't exist, we're using mode instead. If mode is None, we're
99 If src doesn't exist, we're using mode instead. If mode is None, we're
100 using umask.'''
100 using umask.'''
101 try:
101 try:
102 st_mode = os.lstat(src).st_mode & 0777
102 st_mode = os.lstat(src).st_mode & 0777
103 except OSError, inst:
103 except OSError, inst:
104 if inst.errno != errno.ENOENT:
104 if inst.errno != errno.ENOENT:
105 raise
105 raise
106 st_mode = mode
106 st_mode = mode
107 if st_mode is None:
107 if st_mode is None:
108 st_mode = ~umask
108 st_mode = ~umask
109 st_mode &= 0666
109 st_mode &= 0666
110 os.chmod(dst, st_mode)
110 os.chmod(dst, st_mode)
111
111
112 def checkexec(path):
112 def checkexec(path):
113 """
113 """
114 Check whether the given path is on a filesystem with UNIX-like exec flags
114 Check whether the given path is on a filesystem with UNIX-like exec flags
115
115
116 Requires a directory (like /foo/.hg)
116 Requires a directory (like /foo/.hg)
117 """
117 """
118
118
119 # VFAT on some Linux versions can flip mode but it doesn't persist
119 # VFAT on some Linux versions can flip mode but it doesn't persist
120 # a FS remount. Frequently we can detect it if files are created
120 # a FS remount. Frequently we can detect it if files are created
121 # with exec bit on.
121 # with exec bit on.
122
122
123 try:
123 try:
124 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
124 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
125 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
125 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
126 try:
126 try:
127 os.close(fh)
127 os.close(fh)
128 m = os.stat(fn).st_mode & 0777
128 m = os.stat(fn).st_mode & 0777
129 new_file_has_exec = m & EXECFLAGS
129 new_file_has_exec = m & EXECFLAGS
130 os.chmod(fn, m ^ EXECFLAGS)
130 os.chmod(fn, m ^ EXECFLAGS)
131 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
131 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
132 finally:
132 finally:
133 os.unlink(fn)
133 os.unlink(fn)
134 except (IOError, OSError):
134 except (IOError, OSError):
135 # we don't care, the user probably won't be able to commit anyway
135 # we don't care, the user probably won't be able to commit anyway
136 return False
136 return False
137 return not (new_file_has_exec or exec_flags_cannot_flip)
137 return not (new_file_has_exec or exec_flags_cannot_flip)
138
138
139 def checklink(path):
139 def checklink(path):
140 """check whether the given path is on a symlink-capable filesystem"""
140 """check whether the given path is on a symlink-capable filesystem"""
141 # mktemp is not racy because symlink creation will fail if the
141 # mktemp is not racy because symlink creation will fail if the
142 # file already exists
142 # file already exists
143 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
143 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
144 try:
144 try:
145 os.symlink(".", name)
145 os.symlink(".", name)
146 os.unlink(name)
146 os.unlink(name)
147 return True
147 return True
148 except (OSError, AttributeError):
148 except (OSError, AttributeError):
149 return False
149 return False
150
150
151 def checkosfilename(path):
151 def checkosfilename(path):
152 '''Check that the base-relative path is a valid filename on this platform.
152 '''Check that the base-relative path is a valid filename on this platform.
153 Returns None if the path is ok, or a UI string describing the problem.'''
153 Returns None if the path is ok, or a UI string describing the problem.'''
154 pass # on posix platforms, every path is ok
154 pass # on posix platforms, every path is ok
155
155
156 def setbinary(fd):
156 def setbinary(fd):
157 pass
157 pass
158
158
159 def pconvert(path):
159 def pconvert(path):
160 return path
160 return path
161
161
162 def localpath(path):
162 def localpath(path):
163 return path
163 return path
164
164
165 def samefile(fpath1, fpath2):
165 def samefile(fpath1, fpath2):
166 """Returns whether path1 and path2 refer to the same file. This is only
166 """Returns whether path1 and path2 refer to the same file. This is only
167 guaranteed to work for files, not directories."""
167 guaranteed to work for files, not directories."""
168 return os.path.samefile(fpath1, fpath2)
168 return os.path.samefile(fpath1, fpath2)
169
169
170 def samedevice(fpath1, fpath2):
170 def samedevice(fpath1, fpath2):
171 """Returns whether fpath1 and fpath2 are on the same device. This is only
171 """Returns whether fpath1 and fpath2 are on the same device. This is only
172 guaranteed to work for files, not directories."""
172 guaranteed to work for files, not directories."""
173 st1 = os.lstat(fpath1)
173 st1 = os.lstat(fpath1)
174 st2 = os.lstat(fpath2)
174 st2 = os.lstat(fpath2)
175 return st1.st_dev == st2.st_dev
175 return st1.st_dev == st2.st_dev
176
176
177 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
177 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
178 def normcase(path):
178 def normcase(path):
179 return path.lower()
179 return path.lower()
180
180
181 if sys.platform == 'darwin':
181 if sys.platform == 'darwin':
182 import fcntl # only needed on darwin, missing on jython
182 import fcntl # only needed on darwin, missing on jython
183
183
184 def normcase(path):
184 def normcase(path):
185 try:
185 try:
186 u = path.decode('utf-8')
186 u = path.decode('utf-8')
187 except UnicodeDecodeError:
187 except UnicodeDecodeError:
188 # percent-encode any characters that don't round-trip
188 # percent-encode any characters that don't round-trip
189 p2 = path.decode('utf-8', 'ignore').encode('utf-8')
189 p2 = path.decode('utf-8', 'ignore').encode('utf-8')
190 s = ""
190 s = ""
191 pos = 0
191 pos = 0
192 for c in path:
192 for c in path:
193 if p2[pos:pos + 1] == c:
193 if p2[pos:pos + 1] == c:
194 s += c
194 s += c
195 pos += 1
195 pos += 1
196 else:
196 else:
197 s += "%%%02X" % ord(c)
197 s += "%%%02X" % ord(c)
198 u = s.decode('utf-8')
198 u = s.decode('utf-8')
199
199
200 # Decompose then lowercase (HFS+ technote specifies lower)
200 # Decompose then lowercase (HFS+ technote specifies lower)
201 return unicodedata.normalize('NFD', u).lower().encode('utf-8')
201 return unicodedata.normalize('NFD', u).lower().encode('utf-8')
202
202
203 def realpath(path):
203 def realpath(path):
204 '''
204 '''
205 Returns the true, canonical file system path equivalent to the given
205 Returns the true, canonical file system path equivalent to the given
206 path.
206 path.
207
207
208 Equivalent means, in this case, resulting in the same, unique
208 Equivalent means, in this case, resulting in the same, unique
209 file system link to the path. Every file system entry, whether a file,
209 file system link to the path. Every file system entry, whether a file,
210 directory, hard link or symbolic link or special, will have a single
210 directory, hard link or symbolic link or special, will have a single
211 path preferred by the system, but may allow multiple, differing path
211 path preferred by the system, but may allow multiple, differing path
212 lookups to point to it.
212 lookups to point to it.
213
213
214 Most regular UNIX file systems only allow a file system entry to be
214 Most regular UNIX file systems only allow a file system entry to be
215 looked up by its distinct path. Obviously, this does not apply to case
215 looked up by its distinct path. Obviously, this does not apply to case
216 insensitive file systems, whether case preserving or not. The most
216 insensitive file systems, whether case preserving or not. The most
217 complex issue to deal with is file systems transparently reencoding the
217 complex issue to deal with is file systems transparently reencoding the
218 path, such as the non-standard Unicode normalisation required for HFS+
218 path, such as the non-standard Unicode normalisation required for HFS+
219 and HFSX.
219 and HFSX.
220 '''
220 '''
221 # Constants copied from /usr/include/sys/fcntl.h
221 # Constants copied from /usr/include/sys/fcntl.h
222 F_GETPATH = 50
222 F_GETPATH = 50
223 O_SYMLINK = 0x200000
223 O_SYMLINK = 0x200000
224
224
225 try:
225 try:
226 fd = os.open(path, O_SYMLINK)
226 fd = os.open(path, O_SYMLINK)
227 except OSError, err:
227 except OSError, err:
228 if err.errno == errno.ENOENT:
228 if err.errno == errno.ENOENT:
229 return path
229 return path
230 raise
230 raise
231
231
232 try:
232 try:
233 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
233 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
234 finally:
234 finally:
235 os.close(fd)
235 os.close(fd)
236 elif sys.version_info < (2, 4, 2, 'final'):
236 elif sys.version_info < (2, 4, 2, 'final'):
237 # Workaround for http://bugs.python.org/issue1213894 (os.path.realpath
237 # Workaround for http://bugs.python.org/issue1213894 (os.path.realpath
238 # didn't resolve symlinks that were the first component of the path.)
238 # didn't resolve symlinks that were the first component of the path.)
239 def realpath(path):
239 def realpath(path):
240 if os.path.isabs(path):
240 if os.path.isabs(path):
241 return os.path.realpath(path)
241 return os.path.realpath(path)
242 else:
242 else:
243 return os.path.realpath('./' + path)
243 return os.path.realpath('./' + path)
244 else:
244 else:
245 # Fallback to the likely inadequate Python builtin function.
245 # Fallback to the likely inadequate Python builtin function.
246 realpath = os.path.realpath
246 realpath = os.path.realpath
247
247
248 if sys.platform == 'cygwin':
248 if sys.platform == 'cygwin':
249 # workaround for cygwin, in which mount point part of path is
249 # workaround for cygwin, in which mount point part of path is
250 # treated as case sensitive, even though underlying NTFS is case
250 # treated as case sensitive, even though underlying NTFS is case
251 # insensitive.
251 # insensitive.
252
252
253 # default mount points
253 # default mount points
254 cygwinmountpoints = sorted([
254 cygwinmountpoints = sorted([
255 "/usr/bin",
255 "/usr/bin",
256 "/usr/lib",
256 "/usr/lib",
257 "/cygdrive",
257 "/cygdrive",
258 ], reverse=True)
258 ], reverse=True)
259
259
260 # use upper-ing as normcase as same as NTFS workaround
260 # use upper-ing as normcase as same as NTFS workaround
261 def normcase(path):
261 def normcase(path):
262 pathlen = len(path)
262 pathlen = len(path)
263 if (pathlen == 0) or (path[0] != os.sep):
263 if (pathlen == 0) or (path[0] != os.sep):
264 # treat as relative
264 # treat as relative
265 return encoding.upper(path)
265 return encoding.upper(path)
266
266
267 # to preserve case of mountpoint part
267 # to preserve case of mountpoint part
268 for mp in cygwinmountpoints:
268 for mp in cygwinmountpoints:
269 if not path.startswith(mp):
269 if not path.startswith(mp):
270 continue
270 continue
271
271
272 mplen = len(mp)
272 mplen = len(mp)
273 if mplen == pathlen: # mount point itself
273 if mplen == pathlen: # mount point itself
274 return mp
274 return mp
275 if path[mplen] == os.sep:
275 if path[mplen] == os.sep:
276 return mp + encoding.upper(path[mplen:])
276 return mp + encoding.upper(path[mplen:])
277
277
278 return encoding.upper(path)
278 return encoding.upper(path)
279
279
280 # Cygwin translates native ACLs to POSIX permissions,
280 # Cygwin translates native ACLs to POSIX permissions,
281 # but these translations are not supported by native
281 # but these translations are not supported by native
282 # tools, so the exec bit tends to be set erroneously.
282 # tools, so the exec bit tends to be set erroneously.
283 # Therefore, disable executable bit access on Cygwin.
283 # Therefore, disable executable bit access on Cygwin.
284 def checkexec(path):
284 def checkexec(path):
285 return False
285 return False
286
286
287 # Similarly, Cygwin's symlink emulation is likely to create
287 # Similarly, Cygwin's symlink emulation is likely to create
288 # problems when Mercurial is used from both Cygwin and native
288 # problems when Mercurial is used from both Cygwin and native
289 # Windows, with other native tools, or on shared volumes
289 # Windows, with other native tools, or on shared volumes
290 def checklink(path):
290 def checklink(path):
291 return False
291 return False
292
292
293 def shellquote(s):
293 def shellquote(s):
294 if os.sys.platform == 'OpenVMS':
294 if os.sys.platform == 'OpenVMS':
295 return '"%s"' % s
295 return '"%s"' % s
296 else:
296 else:
297 return "'%s'" % s.replace("'", "'\\''")
297 return "'%s'" % s.replace("'", "'\\''")
298
298
299 def quotecommand(cmd):
299 def quotecommand(cmd):
300 return cmd
300 return cmd
301
301
302 def popen(command, mode='r'):
302 def popen(command, mode='r'):
303 return os.popen(command, mode)
303 return os.popen(command, mode)
304
304
305 def testpid(pid):
305 def testpid(pid):
306 '''return False if pid dead, True if running or not sure'''
306 '''return False if pid dead, True if running or not sure'''
307 if os.sys.platform == 'OpenVMS':
307 if os.sys.platform == 'OpenVMS':
308 return True
308 return True
309 try:
309 try:
310 os.kill(pid, 0)
310 os.kill(pid, 0)
311 return True
311 return True
312 except OSError, inst:
312 except OSError, inst:
313 return inst.errno != errno.ESRCH
313 return inst.errno != errno.ESRCH
314
314
315 def explainexit(code):
315 def explainexit(code):
316 """return a 2-tuple (desc, code) describing a subprocess status
316 """return a 2-tuple (desc, code) describing a subprocess status
317 (codes from kill are negative - not os.system/wait encoding)"""
317 (codes from kill are negative - not os.system/wait encoding)"""
318 if code >= 0:
318 if code >= 0:
319 return _("exited with status %d") % code, code
319 return _("exited with status %d") % code, code
320 return _("killed by signal %d") % -code, -code
320 return _("killed by signal %d") % -code, -code
321
321
322 def isowner(st):
322 def isowner(st):
323 """Return True if the stat object st is from the current user."""
323 """Return True if the stat object st is from the current user."""
324 return st.st_uid == os.getuid()
324 return st.st_uid == os.getuid()
325
325
326 def findexe(command):
326 def findexe(command):
327 '''Find executable for command searching like which does.
327 '''Find executable for command searching like which does.
328 If command is a basename then PATH is searched for command.
328 If command is a basename then PATH is searched for command.
329 PATH isn't searched if command is an absolute or relative path.
329 PATH isn't searched if command is an absolute or relative path.
330 If command isn't found None is returned.'''
330 If command isn't found None is returned.'''
331 if sys.platform == 'OpenVMS':
331 if sys.platform == 'OpenVMS':
332 return command
332 return command
333
333
334 def findexisting(executable):
334 def findexisting(executable):
335 'Will return executable if existing file'
335 'Will return executable if existing file'
336 if os.path.isfile(executable) and os.access(executable, os.X_OK):
336 if os.path.isfile(executable) and os.access(executable, os.X_OK):
337 return executable
337 return executable
338 return None
338 return None
339
339
340 if os.sep in command:
340 if os.sep in command:
341 return findexisting(command)
341 return findexisting(command)
342
342
343 if sys.platform == 'plan9':
343 if sys.platform == 'plan9':
344 return findexisting(os.path.join('/bin', command))
344 return findexisting(os.path.join('/bin', command))
345
345
346 for path in os.environ.get('PATH', '').split(os.pathsep):
346 for path in os.environ.get('PATH', '').split(os.pathsep):
347 executable = findexisting(os.path.join(path, command))
347 executable = findexisting(os.path.join(path, command))
348 if executable is not None:
348 if executable is not None:
349 return executable
349 return executable
350 return None
350 return None
351
351
352 def setsignalhandler():
352 def setsignalhandler():
353 pass
353 pass
354
354
355 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
356
355 def statfiles(files):
357 def statfiles(files):
356 'Stat each file in files and yield stat or None if file does not exist.'
358 '''Stat each file in files. Yield each stat, or None if a file does not
359 exist or has a type we don't care about.'''
357 lstat = os.lstat
360 lstat = os.lstat
361 getkind = stat.S_IFMT
358 for nf in files:
362 for nf in files:
359 try:
363 try:
360 st = lstat(nf)
364 st = lstat(nf)
365 if getkind(st.st_mode) not in _wantedkinds:
366 st = None
361 except OSError, err:
367 except OSError, err:
362 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
368 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
363 raise
369 raise
364 st = None
370 st = None
365 yield st
371 yield st
366
372
367 def getuser():
373 def getuser():
368 '''return name of current user'''
374 '''return name of current user'''
369 return getpass.getuser()
375 return getpass.getuser()
370
376
371 def username(uid=None):
377 def username(uid=None):
372 """Return the name of the user with the given uid.
378 """Return the name of the user with the given uid.
373
379
374 If uid is None, return the name of the current user."""
380 If uid is None, return the name of the current user."""
375
381
376 if uid is None:
382 if uid is None:
377 uid = os.getuid()
383 uid = os.getuid()
378 try:
384 try:
379 return pwd.getpwuid(uid)[0]
385 return pwd.getpwuid(uid)[0]
380 except KeyError:
386 except KeyError:
381 return str(uid)
387 return str(uid)
382
388
383 def groupname(gid=None):
389 def groupname(gid=None):
384 """Return the name of the group with the given gid.
390 """Return the name of the group with the given gid.
385
391
386 If gid is None, return the name of the current group."""
392 If gid is None, return the name of the current group."""
387
393
388 if gid is None:
394 if gid is None:
389 gid = os.getgid()
395 gid = os.getgid()
390 try:
396 try:
391 return grp.getgrgid(gid)[0]
397 return grp.getgrgid(gid)[0]
392 except KeyError:
398 except KeyError:
393 return str(gid)
399 return str(gid)
394
400
395 def groupmembers(name):
401 def groupmembers(name):
396 """Return the list of members of the group with the given
402 """Return the list of members of the group with the given
397 name, KeyError if the group does not exist.
403 name, KeyError if the group does not exist.
398 """
404 """
399 return list(grp.getgrnam(name).gr_mem)
405 return list(grp.getgrnam(name).gr_mem)
400
406
401 def spawndetached(args):
407 def spawndetached(args):
402 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
408 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
403 args[0], args)
409 args[0], args)
404
410
405 def gethgcmd():
411 def gethgcmd():
406 return sys.argv[:1]
412 return sys.argv[:1]
407
413
408 def termwidth():
414 def termwidth():
409 try:
415 try:
410 import termios, array, fcntl
416 import termios, array, fcntl
411 for dev in (sys.stderr, sys.stdout, sys.stdin):
417 for dev in (sys.stderr, sys.stdout, sys.stdin):
412 try:
418 try:
413 try:
419 try:
414 fd = dev.fileno()
420 fd = dev.fileno()
415 except AttributeError:
421 except AttributeError:
416 continue
422 continue
417 if not os.isatty(fd):
423 if not os.isatty(fd):
418 continue
424 continue
419 try:
425 try:
420 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
426 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
421 width = array.array('h', arri)[1]
427 width = array.array('h', arri)[1]
422 if width > 0:
428 if width > 0:
423 return width
429 return width
424 except AttributeError:
430 except AttributeError:
425 pass
431 pass
426 except ValueError:
432 except ValueError:
427 pass
433 pass
428 except IOError, e:
434 except IOError, e:
429 if e[0] == errno.EINVAL:
435 if e[0] == errno.EINVAL:
430 pass
436 pass
431 else:
437 else:
432 raise
438 raise
433 except ImportError:
439 except ImportError:
434 pass
440 pass
435 return 80
441 return 80
436
442
437 def makedir(path, notindexed):
443 def makedir(path, notindexed):
438 os.mkdir(path)
444 os.mkdir(path)
439
445
440 def unlinkpath(f):
446 def unlinkpath(f):
441 """unlink and remove the directory if it is empty"""
447 """unlink and remove the directory if it is empty"""
442 os.unlink(f)
448 os.unlink(f)
443 # try removing directories that might now be empty
449 # try removing directories that might now be empty
444 try:
450 try:
445 os.removedirs(os.path.dirname(f))
451 os.removedirs(os.path.dirname(f))
446 except OSError:
452 except OSError:
447 pass
453 pass
448
454
449 def lookupreg(key, name=None, scope=None):
455 def lookupreg(key, name=None, scope=None):
450 return None
456 return None
451
457
452 def hidewindow():
458 def hidewindow():
453 """Hide current shell window.
459 """Hide current shell window.
454
460
455 Used to hide the window opened when starting asynchronous
461 Used to hide the window opened when starting asynchronous
456 child process under Windows, unneeded on other systems.
462 child process under Windows, unneeded on other systems.
457 """
463 """
458 pass
464 pass
459
465
460 class cachestat(object):
466 class cachestat(object):
461 def __init__(self, path):
467 def __init__(self, path):
462 self.stat = os.stat(path)
468 self.stat = os.stat(path)
463
469
464 def cacheable(self):
470 def cacheable(self):
465 return bool(self.stat.st_ino)
471 return bool(self.stat.st_ino)
466
472
467 __hash__ = object.__hash__
473 __hash__ = object.__hash__
468
474
469 def __eq__(self, other):
475 def __eq__(self, other):
470 try:
476 try:
471 return self.stat == other.stat
477 return self.stat == other.stat
472 except AttributeError:
478 except AttributeError:
473 return False
479 return False
474
480
475 def __ne__(self, other):
481 def __ne__(self, other):
476 return not self == other
482 return not self == other
477
483
478 def executablepath():
484 def executablepath():
479 return None # available on Windows only
485 return None # available on Windows only
@@ -1,329 +1,335 b''
1 # windows.py - Windows utility function implementations for Mercurial
1 # windows.py - Windows utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import osutil, encoding
9 import osutil, encoding
10 import errno, msvcrt, os, re, sys, _winreg
10 import errno, msvcrt, os, re, stat, sys, _winreg
11
11
12 import win32
12 import win32
13 executablepath = win32.executablepath
13 executablepath = win32.executablepath
14 getuser = win32.getuser
14 getuser = win32.getuser
15 hidewindow = win32.hidewindow
15 hidewindow = win32.hidewindow
16 makedir = win32.makedir
16 makedir = win32.makedir
17 nlinks = win32.nlinks
17 nlinks = win32.nlinks
18 oslink = win32.oslink
18 oslink = win32.oslink
19 samedevice = win32.samedevice
19 samedevice = win32.samedevice
20 samefile = win32.samefile
20 samefile = win32.samefile
21 setsignalhandler = win32.setsignalhandler
21 setsignalhandler = win32.setsignalhandler
22 spawndetached = win32.spawndetached
22 spawndetached = win32.spawndetached
23 split = os.path.split
23 split = os.path.split
24 termwidth = win32.termwidth
24 termwidth = win32.termwidth
25 testpid = win32.testpid
25 testpid = win32.testpid
26 unlink = win32.unlink
26 unlink = win32.unlink
27
27
28 umask = 0022
28 umask = 0022
29
29
30 # wrap osutil.posixfile to provide friendlier exceptions
30 # wrap osutil.posixfile to provide friendlier exceptions
31 def posixfile(name, mode='r', buffering=-1):
31 def posixfile(name, mode='r', buffering=-1):
32 try:
32 try:
33 return osutil.posixfile(name, mode, buffering)
33 return osutil.posixfile(name, mode, buffering)
34 except WindowsError, err:
34 except WindowsError, err:
35 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
35 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
36 posixfile.__doc__ = osutil.posixfile.__doc__
36 posixfile.__doc__ = osutil.posixfile.__doc__
37
37
38 class winstdout(object):
38 class winstdout(object):
39 '''stdout on windows misbehaves if sent through a pipe'''
39 '''stdout on windows misbehaves if sent through a pipe'''
40
40
41 def __init__(self, fp):
41 def __init__(self, fp):
42 self.fp = fp
42 self.fp = fp
43
43
44 def __getattr__(self, key):
44 def __getattr__(self, key):
45 return getattr(self.fp, key)
45 return getattr(self.fp, key)
46
46
47 def close(self):
47 def close(self):
48 try:
48 try:
49 self.fp.close()
49 self.fp.close()
50 except IOError:
50 except IOError:
51 pass
51 pass
52
52
53 def write(self, s):
53 def write(self, s):
54 try:
54 try:
55 # This is workaround for "Not enough space" error on
55 # This is workaround for "Not enough space" error on
56 # writing large size of data to console.
56 # writing large size of data to console.
57 limit = 16000
57 limit = 16000
58 l = len(s)
58 l = len(s)
59 start = 0
59 start = 0
60 self.softspace = 0
60 self.softspace = 0
61 while start < l:
61 while start < l:
62 end = start + limit
62 end = start + limit
63 self.fp.write(s[start:end])
63 self.fp.write(s[start:end])
64 start = end
64 start = end
65 except IOError, inst:
65 except IOError, inst:
66 if inst.errno != 0:
66 if inst.errno != 0:
67 raise
67 raise
68 self.close()
68 self.close()
69 raise IOError(errno.EPIPE, 'Broken pipe')
69 raise IOError(errno.EPIPE, 'Broken pipe')
70
70
71 def flush(self):
71 def flush(self):
72 try:
72 try:
73 return self.fp.flush()
73 return self.fp.flush()
74 except IOError, inst:
74 except IOError, inst:
75 if inst.errno != errno.EINVAL:
75 if inst.errno != errno.EINVAL:
76 raise
76 raise
77 self.close()
77 self.close()
78 raise IOError(errno.EPIPE, 'Broken pipe')
78 raise IOError(errno.EPIPE, 'Broken pipe')
79
79
80 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
80 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
81
81
82 def _is_win_9x():
82 def _is_win_9x():
83 '''return true if run on windows 95, 98 or me.'''
83 '''return true if run on windows 95, 98 or me.'''
84 try:
84 try:
85 return sys.getwindowsversion()[3] == 1
85 return sys.getwindowsversion()[3] == 1
86 except AttributeError:
86 except AttributeError:
87 return 'command' in os.environ.get('comspec', '')
87 return 'command' in os.environ.get('comspec', '')
88
88
89 def openhardlinks():
89 def openhardlinks():
90 return not _is_win_9x()
90 return not _is_win_9x()
91
91
92 def parsepatchoutput(output_line):
92 def parsepatchoutput(output_line):
93 """parses the output produced by patch and returns the filename"""
93 """parses the output produced by patch and returns the filename"""
94 pf = output_line[14:]
94 pf = output_line[14:]
95 if pf[0] == '`':
95 if pf[0] == '`':
96 pf = pf[1:-1] # Remove the quotes
96 pf = pf[1:-1] # Remove the quotes
97 return pf
97 return pf
98
98
99 def sshargs(sshcmd, host, user, port):
99 def sshargs(sshcmd, host, user, port):
100 '''Build argument list for ssh or Plink'''
100 '''Build argument list for ssh or Plink'''
101 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
101 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
102 args = user and ("%s@%s" % (user, host)) or host
102 args = user and ("%s@%s" % (user, host)) or host
103 return port and ("%s %s %s" % (args, pflag, port)) or args
103 return port and ("%s %s %s" % (args, pflag, port)) or args
104
104
105 def setflags(f, l, x):
105 def setflags(f, l, x):
106 pass
106 pass
107
107
108 def copymode(src, dst, mode=None):
108 def copymode(src, dst, mode=None):
109 pass
109 pass
110
110
111 def checkexec(path):
111 def checkexec(path):
112 return False
112 return False
113
113
114 def checklink(path):
114 def checklink(path):
115 return False
115 return False
116
116
117 def setbinary(fd):
117 def setbinary(fd):
118 # When run without console, pipes may expose invalid
118 # When run without console, pipes may expose invalid
119 # fileno(), usually set to -1.
119 # fileno(), usually set to -1.
120 fno = getattr(fd, 'fileno', None)
120 fno = getattr(fd, 'fileno', None)
121 if fno is not None and fno() >= 0:
121 if fno is not None and fno() >= 0:
122 msvcrt.setmode(fno(), os.O_BINARY)
122 msvcrt.setmode(fno(), os.O_BINARY)
123
123
124 def pconvert(path):
124 def pconvert(path):
125 return path.replace(os.sep, '/')
125 return path.replace(os.sep, '/')
126
126
127 def localpath(path):
127 def localpath(path):
128 return path.replace('/', '\\')
128 return path.replace('/', '\\')
129
129
130 def normpath(path):
130 def normpath(path):
131 return pconvert(os.path.normpath(path))
131 return pconvert(os.path.normpath(path))
132
132
133 def normcase(path):
133 def normcase(path):
134 return encoding.upper(path)
134 return encoding.upper(path)
135
135
136 def realpath(path):
136 def realpath(path):
137 '''
137 '''
138 Returns the true, canonical file system path equivalent to the given
138 Returns the true, canonical file system path equivalent to the given
139 path.
139 path.
140 '''
140 '''
141 # TODO: There may be a more clever way to do this that also handles other,
141 # TODO: There may be a more clever way to do this that also handles other,
142 # less common file systems.
142 # less common file systems.
143 return os.path.normpath(normcase(os.path.realpath(path)))
143 return os.path.normpath(normcase(os.path.realpath(path)))
144
144
145 def samestat(s1, s2):
145 def samestat(s1, s2):
146 return False
146 return False
147
147
148 # A sequence of backslashes is special iff it precedes a double quote:
148 # A sequence of backslashes is special iff it precedes a double quote:
149 # - if there's an even number of backslashes, the double quote is not
149 # - if there's an even number of backslashes, the double quote is not
150 # quoted (i.e. it ends the quoted region)
150 # quoted (i.e. it ends the quoted region)
151 # - if there's an odd number of backslashes, the double quote is quoted
151 # - if there's an odd number of backslashes, the double quote is quoted
152 # - in both cases, every pair of backslashes is unquoted into a single
152 # - in both cases, every pair of backslashes is unquoted into a single
153 # backslash
153 # backslash
154 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
154 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
155 # So, to quote a string, we must surround it in double quotes, double
155 # So, to quote a string, we must surround it in double quotes, double
156 # the number of backslashes that precede double quotes and add another
156 # the number of backslashes that precede double quotes and add another
157 # backslash before every double quote (being careful with the double
157 # backslash before every double quote (being careful with the double
158 # quote we've appended to the end)
158 # quote we've appended to the end)
159 _quotere = None
159 _quotere = None
160 def shellquote(s):
160 def shellquote(s):
161 global _quotere
161 global _quotere
162 if _quotere is None:
162 if _quotere is None:
163 _quotere = re.compile(r'(\\*)("|\\$)')
163 _quotere = re.compile(r'(\\*)("|\\$)')
164 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
164 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
165
165
166 def quotecommand(cmd):
166 def quotecommand(cmd):
167 """Build a command string suitable for os.popen* calls."""
167 """Build a command string suitable for os.popen* calls."""
168 if sys.version_info < (2, 7, 1):
168 if sys.version_info < (2, 7, 1):
169 # Python versions since 2.7.1 do this extra quoting themselves
169 # Python versions since 2.7.1 do this extra quoting themselves
170 return '"' + cmd + '"'
170 return '"' + cmd + '"'
171 return cmd
171 return cmd
172
172
173 def popen(command, mode='r'):
173 def popen(command, mode='r'):
174 # Work around "popen spawned process may not write to stdout
174 # Work around "popen spawned process may not write to stdout
175 # under windows"
175 # under windows"
176 # http://bugs.python.org/issue1366
176 # http://bugs.python.org/issue1366
177 command += " 2> %s" % os.devnull
177 command += " 2> %s" % os.devnull
178 return os.popen(quotecommand(command), mode)
178 return os.popen(quotecommand(command), mode)
179
179
180 def explainexit(code):
180 def explainexit(code):
181 return _("exited with status %d") % code, code
181 return _("exited with status %d") % code, code
182
182
183 # if you change this stub into a real check, please try to implement the
183 # if you change this stub into a real check, please try to implement the
184 # username and groupname functions above, too.
184 # username and groupname functions above, too.
185 def isowner(st):
185 def isowner(st):
186 return True
186 return True
187
187
188 def findexe(command):
188 def findexe(command):
189 '''Find executable for command searching like cmd.exe does.
189 '''Find executable for command searching like cmd.exe does.
190 If command is a basename then PATH is searched for command.
190 If command is a basename then PATH is searched for command.
191 PATH isn't searched if command is an absolute or relative path.
191 PATH isn't searched if command is an absolute or relative path.
192 An extension from PATHEXT is found and added if not present.
192 An extension from PATHEXT is found and added if not present.
193 If command isn't found None is returned.'''
193 If command isn't found None is returned.'''
194 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
194 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
195 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
195 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
196 if os.path.splitext(command)[1].lower() in pathexts:
196 if os.path.splitext(command)[1].lower() in pathexts:
197 pathexts = ['']
197 pathexts = ['']
198
198
199 def findexisting(pathcommand):
199 def findexisting(pathcommand):
200 'Will append extension (if needed) and return existing file'
200 'Will append extension (if needed) and return existing file'
201 for ext in pathexts:
201 for ext in pathexts:
202 executable = pathcommand + ext
202 executable = pathcommand + ext
203 if os.path.exists(executable):
203 if os.path.exists(executable):
204 return executable
204 return executable
205 return None
205 return None
206
206
207 if os.sep in command:
207 if os.sep in command:
208 return findexisting(command)
208 return findexisting(command)
209
209
210 for path in os.environ.get('PATH', '').split(os.pathsep):
210 for path in os.environ.get('PATH', '').split(os.pathsep):
211 executable = findexisting(os.path.join(path, command))
211 executable = findexisting(os.path.join(path, command))
212 if executable is not None:
212 if executable is not None:
213 return executable
213 return executable
214 return findexisting(os.path.expanduser(os.path.expandvars(command)))
214 return findexisting(os.path.expanduser(os.path.expandvars(command)))
215
215
216 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
217
216 def statfiles(files):
218 def statfiles(files):
217 '''Stat each file in files and yield stat or None if file does not exist.
219 '''Stat each file in files. Yield each stat, or None if a file
220 does not exist or has a type we don't care about.
221
218 Cluster and cache stat per directory to minimize number of OS stat calls.'''
222 Cluster and cache stat per directory to minimize number of OS stat calls.'''
219 dircache = {} # dirname -> filename -> status | None if file does not exist
223 dircache = {} # dirname -> filename -> status | None if file does not exist
224 getkind = stat.S_IFMT
220 for nf in files:
225 for nf in files:
221 nf = normcase(nf)
226 nf = normcase(nf)
222 dir, base = os.path.split(nf)
227 dir, base = os.path.split(nf)
223 if not dir:
228 if not dir:
224 dir = '.'
229 dir = '.'
225 cache = dircache.get(dir, None)
230 cache = dircache.get(dir, None)
226 if cache is None:
231 if cache is None:
227 try:
232 try:
228 dmap = dict([(normcase(n), s)
233 dmap = dict([(normcase(n), s)
229 for n, k, s in osutil.listdir(dir, True)])
234 for n, k, s in osutil.listdir(dir, True)
235 if getkind(s) in _wantedkinds])
230 except OSError, err:
236 except OSError, err:
231 # handle directory not found in Python version prior to 2.5
237 # handle directory not found in Python version prior to 2.5
232 # Python <= 2.4 returns native Windows code 3 in errno
238 # Python <= 2.4 returns native Windows code 3 in errno
233 # Python >= 2.5 returns ENOENT and adds winerror field
239 # Python >= 2.5 returns ENOENT and adds winerror field
234 # EINVAL is raised if dir is not a directory.
240 # EINVAL is raised if dir is not a directory.
235 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
241 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
236 errno.ENOTDIR):
242 errno.ENOTDIR):
237 raise
243 raise
238 dmap = {}
244 dmap = {}
239 cache = dircache.setdefault(dir, dmap)
245 cache = dircache.setdefault(dir, dmap)
240 yield cache.get(base, None)
246 yield cache.get(base, None)
241
247
242 def username(uid=None):
248 def username(uid=None):
243 """Return the name of the user with the given uid.
249 """Return the name of the user with the given uid.
244
250
245 If uid is None, return the name of the current user."""
251 If uid is None, return the name of the current user."""
246 return None
252 return None
247
253
248 def groupname(gid=None):
254 def groupname(gid=None):
249 """Return the name of the group with the given gid.
255 """Return the name of the group with the given gid.
250
256
251 If gid is None, return the name of the current group."""
257 If gid is None, return the name of the current group."""
252 return None
258 return None
253
259
254 def _removedirs(name):
260 def _removedirs(name):
255 """special version of os.removedirs that does not remove symlinked
261 """special version of os.removedirs that does not remove symlinked
256 directories or junction points if they actually contain files"""
262 directories or junction points if they actually contain files"""
257 if osutil.listdir(name):
263 if osutil.listdir(name):
258 return
264 return
259 os.rmdir(name)
265 os.rmdir(name)
260 head, tail = os.path.split(name)
266 head, tail = os.path.split(name)
261 if not tail:
267 if not tail:
262 head, tail = os.path.split(head)
268 head, tail = os.path.split(head)
263 while head and tail:
269 while head and tail:
264 try:
270 try:
265 if osutil.listdir(head):
271 if osutil.listdir(head):
266 return
272 return
267 os.rmdir(head)
273 os.rmdir(head)
268 except (ValueError, OSError):
274 except (ValueError, OSError):
269 break
275 break
270 head, tail = os.path.split(head)
276 head, tail = os.path.split(head)
271
277
272 def unlinkpath(f):
278 def unlinkpath(f):
273 """unlink and remove the directory if it is empty"""
279 """unlink and remove the directory if it is empty"""
274 unlink(f)
280 unlink(f)
275 # try removing directories that might now be empty
281 # try removing directories that might now be empty
276 try:
282 try:
277 _removedirs(os.path.dirname(f))
283 _removedirs(os.path.dirname(f))
278 except OSError:
284 except OSError:
279 pass
285 pass
280
286
281 def rename(src, dst):
287 def rename(src, dst):
282 '''atomically rename file src to dst, replacing dst if it exists'''
288 '''atomically rename file src to dst, replacing dst if it exists'''
283 try:
289 try:
284 os.rename(src, dst)
290 os.rename(src, dst)
285 except OSError, e:
291 except OSError, e:
286 if e.errno != errno.EEXIST:
292 if e.errno != errno.EEXIST:
287 raise
293 raise
288 unlink(dst)
294 unlink(dst)
289 os.rename(src, dst)
295 os.rename(src, dst)
290
296
291 def gethgcmd():
297 def gethgcmd():
292 return [sys.executable] + sys.argv[:1]
298 return [sys.executable] + sys.argv[:1]
293
299
294 def groupmembers(name):
300 def groupmembers(name):
295 # Don't support groups on Windows for now
301 # Don't support groups on Windows for now
296 raise KeyError
302 raise KeyError
297
303
298 def isexec(f):
304 def isexec(f):
299 return False
305 return False
300
306
301 class cachestat(object):
307 class cachestat(object):
302 def __init__(self, path):
308 def __init__(self, path):
303 pass
309 pass
304
310
305 def cacheable(self):
311 def cacheable(self):
306 return False
312 return False
307
313
308 def lookupreg(key, valname=None, scope=None):
314 def lookupreg(key, valname=None, scope=None):
309 ''' Look up a key/value name in the Windows registry.
315 ''' Look up a key/value name in the Windows registry.
310
316
311 valname: value name. If unspecified, the default value for the key
317 valname: value name. If unspecified, the default value for the key
312 is used.
318 is used.
313 scope: optionally specify scope for registry lookup, this can be
319 scope: optionally specify scope for registry lookup, this can be
314 a sequence of scopes to look up in order. Default (CURRENT_USER,
320 a sequence of scopes to look up in order. Default (CURRENT_USER,
315 LOCAL_MACHINE).
321 LOCAL_MACHINE).
316 '''
322 '''
317 if scope is None:
323 if scope is None:
318 scope = (_winreg.HKEY_CURRENT_USER, _winreg.HKEY_LOCAL_MACHINE)
324 scope = (_winreg.HKEY_CURRENT_USER, _winreg.HKEY_LOCAL_MACHINE)
319 elif not isinstance(scope, (list, tuple)):
325 elif not isinstance(scope, (list, tuple)):
320 scope = (scope,)
326 scope = (scope,)
321 for s in scope:
327 for s in scope:
322 try:
328 try:
323 val = _winreg.QueryValueEx(_winreg.OpenKey(s, key), valname)[0]
329 val = _winreg.QueryValueEx(_winreg.OpenKey(s, key), valname)[0]
324 # never let a Unicode string escape into the wild
330 # never let a Unicode string escape into the wild
325 return encoding.tolocal(val.encode('UTF-8'))
331 return encoding.tolocal(val.encode('UTF-8'))
326 except EnvironmentError:
332 except EnvironmentError:
327 pass
333 pass
328
334
329 expandglobs = True
335 expandglobs = True
General Comments 0
You need to be logged in to leave comments. Login now