##// END OF EJS Templates
make path expanding more consistent...
Alexander Solovyov -
r9610:d78fe60f default
parent child Browse files
Show More
@@ -1,641 +1,641 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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from node import nullid
8 from node import nullid
9 from i18n import _
9 from i18n import _
10 import util, ignore, osutil, parsers
10 import util, ignore, osutil, parsers
11 import struct, os, stat, errno
11 import struct, os, stat, errno
12 import cStringIO, sys
12 import cStringIO, sys
13
13
14 _unknown = ('?', 0, 0, 0)
14 _unknown = ('?', 0, 0, 0)
15 _format = ">cllll"
15 _format = ">cllll"
16 propertycache = util.propertycache
16 propertycache = util.propertycache
17
17
18 def _finddirs(path):
18 def _finddirs(path):
19 pos = path.rfind('/')
19 pos = path.rfind('/')
20 while pos != -1:
20 while pos != -1:
21 yield path[:pos]
21 yield path[:pos]
22 pos = path.rfind('/', 0, pos)
22 pos = path.rfind('/', 0, pos)
23
23
24 def _incdirs(dirs, path):
24 def _incdirs(dirs, path):
25 for base in _finddirs(path):
25 for base in _finddirs(path):
26 if base in dirs:
26 if base in dirs:
27 dirs[base] += 1
27 dirs[base] += 1
28 return
28 return
29 dirs[base] = 1
29 dirs[base] = 1
30
30
31 def _decdirs(dirs, path):
31 def _decdirs(dirs, path):
32 for base in _finddirs(path):
32 for base in _finddirs(path):
33 if dirs[base] > 1:
33 if dirs[base] > 1:
34 dirs[base] -= 1
34 dirs[base] -= 1
35 return
35 return
36 del dirs[base]
36 del dirs[base]
37
37
38 class dirstate(object):
38 class dirstate(object):
39
39
40 def __init__(self, opener, ui, root):
40 def __init__(self, opener, ui, root):
41 '''Create a new dirstate object. opener is an open()-like callable
41 '''Create a new dirstate object. opener is an open()-like callable
42 that can be used to open the dirstate file; root is the root of the
42 that can be used to open the dirstate file; root is the root of the
43 directory tracked by the dirstate.'''
43 directory tracked by the dirstate.'''
44 self._opener = opener
44 self._opener = opener
45 self._root = root
45 self._root = root
46 self._rootdir = os.path.join(root, '')
46 self._rootdir = os.path.join(root, '')
47 self._dirty = False
47 self._dirty = False
48 self._dirtypl = False
48 self._dirtypl = False
49 self._ui = ui
49 self._ui = ui
50
50
51 @propertycache
51 @propertycache
52 def _map(self):
52 def _map(self):
53 '''Return the dirstate contents as a map from filename to
53 '''Return the dirstate contents as a map from filename to
54 (state, mode, size, time).'''
54 (state, mode, size, time).'''
55 self._read()
55 self._read()
56 return self._map
56 return self._map
57
57
58 @propertycache
58 @propertycache
59 def _copymap(self):
59 def _copymap(self):
60 self._read()
60 self._read()
61 return self._copymap
61 return self._copymap
62
62
63 @propertycache
63 @propertycache
64 def _foldmap(self):
64 def _foldmap(self):
65 f = {}
65 f = {}
66 for name in self._map:
66 for name in self._map:
67 f[os.path.normcase(name)] = name
67 f[os.path.normcase(name)] = name
68 return f
68 return f
69
69
70 @propertycache
70 @propertycache
71 def _branch(self):
71 def _branch(self):
72 try:
72 try:
73 return self._opener("branch").read().strip() or "default"
73 return self._opener("branch").read().strip() or "default"
74 except IOError:
74 except IOError:
75 return "default"
75 return "default"
76
76
77 @propertycache
77 @propertycache
78 def _pl(self):
78 def _pl(self):
79 try:
79 try:
80 st = self._opener("dirstate").read(40)
80 st = self._opener("dirstate").read(40)
81 l = len(st)
81 l = len(st)
82 if l == 40:
82 if l == 40:
83 return st[:20], st[20:40]
83 return st[:20], st[20:40]
84 elif l > 0 and l < 40:
84 elif l > 0 and l < 40:
85 raise util.Abort(_('working directory state appears damaged!'))
85 raise util.Abort(_('working directory state appears damaged!'))
86 except IOError, err:
86 except IOError, err:
87 if err.errno != errno.ENOENT: raise
87 if err.errno != errno.ENOENT: raise
88 return [nullid, nullid]
88 return [nullid, nullid]
89
89
90 @propertycache
90 @propertycache
91 def _dirs(self):
91 def _dirs(self):
92 dirs = {}
92 dirs = {}
93 for f,s in self._map.iteritems():
93 for f,s in self._map.iteritems():
94 if s[0] != 'r':
94 if s[0] != 'r':
95 _incdirs(dirs, f)
95 _incdirs(dirs, f)
96 return dirs
96 return dirs
97
97
98 @propertycache
98 @propertycache
99 def _ignore(self):
99 def _ignore(self):
100 files = [self._join('.hgignore')]
100 files = [self._join('.hgignore')]
101 for name, path in self._ui.configitems("ui"):
101 for name, path in self._ui.configitems("ui"):
102 if name == 'ignore' or name.startswith('ignore.'):
102 if name == 'ignore' or name.startswith('ignore.'):
103 files.append(os.path.expanduser(path))
103 files.append(util.expandpath(path))
104 return ignore.ignore(self._root, files, self._ui.warn)
104 return ignore.ignore(self._root, files, self._ui.warn)
105
105
106 @propertycache
106 @propertycache
107 def _slash(self):
107 def _slash(self):
108 return self._ui.configbool('ui', 'slash') and os.sep != '/'
108 return self._ui.configbool('ui', 'slash') and os.sep != '/'
109
109
110 @propertycache
110 @propertycache
111 def _checklink(self):
111 def _checklink(self):
112 return util.checklink(self._root)
112 return util.checklink(self._root)
113
113
114 @propertycache
114 @propertycache
115 def _checkexec(self):
115 def _checkexec(self):
116 return util.checkexec(self._root)
116 return util.checkexec(self._root)
117
117
118 @propertycache
118 @propertycache
119 def _checkcase(self):
119 def _checkcase(self):
120 return not util.checkcase(self._join('.hg'))
120 return not util.checkcase(self._join('.hg'))
121
121
122 def _join(self, f):
122 def _join(self, f):
123 # much faster than os.path.join()
123 # much faster than os.path.join()
124 # it's safe because f is always a relative path
124 # it's safe because f is always a relative path
125 return self._rootdir + f
125 return self._rootdir + f
126
126
127 def flagfunc(self, fallback):
127 def flagfunc(self, fallback):
128 if self._checklink:
128 if self._checklink:
129 if self._checkexec:
129 if self._checkexec:
130 def f(x):
130 def f(x):
131 p = self._join(x)
131 p = self._join(x)
132 if os.path.islink(p):
132 if os.path.islink(p):
133 return 'l'
133 return 'l'
134 if util.is_exec(p):
134 if util.is_exec(p):
135 return 'x'
135 return 'x'
136 return ''
136 return ''
137 return f
137 return f
138 def f(x):
138 def f(x):
139 if os.path.islink(self._join(x)):
139 if os.path.islink(self._join(x)):
140 return 'l'
140 return 'l'
141 if 'x' in fallback(x):
141 if 'x' in fallback(x):
142 return 'x'
142 return 'x'
143 return ''
143 return ''
144 return f
144 return f
145 if self._checkexec:
145 if self._checkexec:
146 def f(x):
146 def f(x):
147 if 'l' in fallback(x):
147 if 'l' in fallback(x):
148 return 'l'
148 return 'l'
149 if util.is_exec(self._join(x)):
149 if util.is_exec(self._join(x)):
150 return 'x'
150 return 'x'
151 return ''
151 return ''
152 return f
152 return f
153 return fallback
153 return fallback
154
154
155 def getcwd(self):
155 def getcwd(self):
156 cwd = os.getcwd()
156 cwd = os.getcwd()
157 if cwd == self._root: return ''
157 if cwd == self._root: return ''
158 # self._root ends with a path separator if self._root is '/' or 'C:\'
158 # self._root ends with a path separator if self._root is '/' or 'C:\'
159 rootsep = self._root
159 rootsep = self._root
160 if not util.endswithsep(rootsep):
160 if not util.endswithsep(rootsep):
161 rootsep += os.sep
161 rootsep += os.sep
162 if cwd.startswith(rootsep):
162 if cwd.startswith(rootsep):
163 return cwd[len(rootsep):]
163 return cwd[len(rootsep):]
164 else:
164 else:
165 # we're outside the repo. return an absolute path.
165 # we're outside the repo. return an absolute path.
166 return cwd
166 return cwd
167
167
168 def pathto(self, f, cwd=None):
168 def pathto(self, f, cwd=None):
169 if cwd is None:
169 if cwd is None:
170 cwd = self.getcwd()
170 cwd = self.getcwd()
171 path = util.pathto(self._root, cwd, f)
171 path = util.pathto(self._root, cwd, f)
172 if self._slash:
172 if self._slash:
173 return util.normpath(path)
173 return util.normpath(path)
174 return path
174 return path
175
175
176 def __getitem__(self, key):
176 def __getitem__(self, key):
177 '''Return the current state of key (a filename) in the dirstate.
177 '''Return the current state of key (a filename) in the dirstate.
178 States are:
178 States are:
179 n normal
179 n normal
180 m needs merging
180 m needs merging
181 r marked for removal
181 r marked for removal
182 a marked for addition
182 a marked for addition
183 ? not tracked
183 ? not tracked
184 '''
184 '''
185 return self._map.get(key, ("?",))[0]
185 return self._map.get(key, ("?",))[0]
186
186
187 def __contains__(self, key):
187 def __contains__(self, key):
188 return key in self._map
188 return key in self._map
189
189
190 def __iter__(self):
190 def __iter__(self):
191 for x in sorted(self._map):
191 for x in sorted(self._map):
192 yield x
192 yield x
193
193
194 def parents(self):
194 def parents(self):
195 return self._pl
195 return self._pl
196
196
197 def branch(self):
197 def branch(self):
198 return self._branch
198 return self._branch
199
199
200 def setparents(self, p1, p2=nullid):
200 def setparents(self, p1, p2=nullid):
201 self._dirty = self._dirtypl = True
201 self._dirty = self._dirtypl = True
202 self._pl = p1, p2
202 self._pl = p1, p2
203
203
204 def setbranch(self, branch):
204 def setbranch(self, branch):
205 self._branch = branch
205 self._branch = branch
206 self._opener("branch", "w").write(branch + '\n')
206 self._opener("branch", "w").write(branch + '\n')
207
207
208 def _read(self):
208 def _read(self):
209 self._map = {}
209 self._map = {}
210 self._copymap = {}
210 self._copymap = {}
211 try:
211 try:
212 st = self._opener("dirstate").read()
212 st = self._opener("dirstate").read()
213 except IOError, err:
213 except IOError, err:
214 if err.errno != errno.ENOENT: raise
214 if err.errno != errno.ENOENT: raise
215 return
215 return
216 if not st:
216 if not st:
217 return
217 return
218
218
219 p = parsers.parse_dirstate(self._map, self._copymap, st)
219 p = parsers.parse_dirstate(self._map, self._copymap, st)
220 if not self._dirtypl:
220 if not self._dirtypl:
221 self._pl = p
221 self._pl = p
222
222
223 def invalidate(self):
223 def invalidate(self):
224 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
224 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
225 if a in self.__dict__:
225 if a in self.__dict__:
226 delattr(self, a)
226 delattr(self, a)
227 self._dirty = False
227 self._dirty = False
228
228
229 def copy(self, source, dest):
229 def copy(self, source, dest):
230 """Mark dest as a copy of source. Unmark dest if source is None.
230 """Mark dest as a copy of source. Unmark dest if source is None.
231 """
231 """
232 if source == dest:
232 if source == dest:
233 return
233 return
234 self._dirty = True
234 self._dirty = True
235 if source is not None:
235 if source is not None:
236 self._copymap[dest] = source
236 self._copymap[dest] = source
237 elif dest in self._copymap:
237 elif dest in self._copymap:
238 del self._copymap[dest]
238 del self._copymap[dest]
239
239
240 def copied(self, file):
240 def copied(self, file):
241 return self._copymap.get(file, None)
241 return self._copymap.get(file, None)
242
242
243 def copies(self):
243 def copies(self):
244 return self._copymap
244 return self._copymap
245
245
246 def _droppath(self, f):
246 def _droppath(self, f):
247 if self[f] not in "?r" and "_dirs" in self.__dict__:
247 if self[f] not in "?r" and "_dirs" in self.__dict__:
248 _decdirs(self._dirs, f)
248 _decdirs(self._dirs, f)
249
249
250 def _addpath(self, f, check=False):
250 def _addpath(self, f, check=False):
251 oldstate = self[f]
251 oldstate = self[f]
252 if check or oldstate == "r":
252 if check or oldstate == "r":
253 if '\r' in f or '\n' in f:
253 if '\r' in f or '\n' in f:
254 raise util.Abort(
254 raise util.Abort(
255 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
255 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
256 if f in self._dirs:
256 if f in self._dirs:
257 raise util.Abort(_('directory %r already in dirstate') % f)
257 raise util.Abort(_('directory %r already in dirstate') % f)
258 # shadows
258 # shadows
259 for d in _finddirs(f):
259 for d in _finddirs(f):
260 if d in self._dirs:
260 if d in self._dirs:
261 break
261 break
262 if d in self._map and self[d] != 'r':
262 if d in self._map and self[d] != 'r':
263 raise util.Abort(
263 raise util.Abort(
264 _('file %r in dirstate clashes with %r') % (d, f))
264 _('file %r in dirstate clashes with %r') % (d, f))
265 if oldstate in "?r" and "_dirs" in self.__dict__:
265 if oldstate in "?r" and "_dirs" in self.__dict__:
266 _incdirs(self._dirs, f)
266 _incdirs(self._dirs, f)
267
267
268 def normal(self, f):
268 def normal(self, f):
269 'mark a file normal and clean'
269 'mark a file normal and clean'
270 self._dirty = True
270 self._dirty = True
271 self._addpath(f)
271 self._addpath(f)
272 s = os.lstat(self._join(f))
272 s = os.lstat(self._join(f))
273 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
273 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
274 if f in self._copymap:
274 if f in self._copymap:
275 del self._copymap[f]
275 del self._copymap[f]
276
276
277 def normallookup(self, f):
277 def normallookup(self, f):
278 'mark a file normal, but possibly dirty'
278 'mark a file normal, but possibly dirty'
279 if self._pl[1] != nullid and f in self._map:
279 if self._pl[1] != nullid and f in self._map:
280 # if there is a merge going on and the file was either
280 # if there is a merge going on and the file was either
281 # in state 'm' or dirty before being removed, restore that state.
281 # in state 'm' or dirty before being removed, restore that state.
282 entry = self._map[f]
282 entry = self._map[f]
283 if entry[0] == 'r' and entry[2] in (-1, -2):
283 if entry[0] == 'r' and entry[2] in (-1, -2):
284 source = self._copymap.get(f)
284 source = self._copymap.get(f)
285 if entry[2] == -1:
285 if entry[2] == -1:
286 self.merge(f)
286 self.merge(f)
287 elif entry[2] == -2:
287 elif entry[2] == -2:
288 self.normaldirty(f)
288 self.normaldirty(f)
289 if source:
289 if source:
290 self.copy(source, f)
290 self.copy(source, f)
291 return
291 return
292 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
292 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
293 return
293 return
294 self._dirty = True
294 self._dirty = True
295 self._addpath(f)
295 self._addpath(f)
296 self._map[f] = ('n', 0, -1, -1)
296 self._map[f] = ('n', 0, -1, -1)
297 if f in self._copymap:
297 if f in self._copymap:
298 del self._copymap[f]
298 del self._copymap[f]
299
299
300 def normaldirty(self, f):
300 def normaldirty(self, f):
301 'mark a file normal, but dirty'
301 'mark a file normal, but dirty'
302 self._dirty = True
302 self._dirty = True
303 self._addpath(f)
303 self._addpath(f)
304 self._map[f] = ('n', 0, -2, -1)
304 self._map[f] = ('n', 0, -2, -1)
305 if f in self._copymap:
305 if f in self._copymap:
306 del self._copymap[f]
306 del self._copymap[f]
307
307
308 def add(self, f):
308 def add(self, f):
309 'mark a file added'
309 'mark a file added'
310 self._dirty = True
310 self._dirty = True
311 self._addpath(f, True)
311 self._addpath(f, True)
312 self._map[f] = ('a', 0, -1, -1)
312 self._map[f] = ('a', 0, -1, -1)
313 if f in self._copymap:
313 if f in self._copymap:
314 del self._copymap[f]
314 del self._copymap[f]
315
315
316 def remove(self, f):
316 def remove(self, f):
317 'mark a file removed'
317 'mark a file removed'
318 self._dirty = True
318 self._dirty = True
319 self._droppath(f)
319 self._droppath(f)
320 size = 0
320 size = 0
321 if self._pl[1] != nullid and f in self._map:
321 if self._pl[1] != nullid and f in self._map:
322 entry = self._map[f]
322 entry = self._map[f]
323 if entry[0] == 'm':
323 if entry[0] == 'm':
324 size = -1
324 size = -1
325 elif entry[0] == 'n' and entry[2] == -2:
325 elif entry[0] == 'n' and entry[2] == -2:
326 size = -2
326 size = -2
327 self._map[f] = ('r', 0, size, 0)
327 self._map[f] = ('r', 0, size, 0)
328 if size == 0 and f in self._copymap:
328 if size == 0 and f in self._copymap:
329 del self._copymap[f]
329 del self._copymap[f]
330
330
331 def merge(self, f):
331 def merge(self, f):
332 'mark a file merged'
332 'mark a file merged'
333 self._dirty = True
333 self._dirty = True
334 s = os.lstat(self._join(f))
334 s = os.lstat(self._join(f))
335 self._addpath(f)
335 self._addpath(f)
336 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
336 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
337 if f in self._copymap:
337 if f in self._copymap:
338 del self._copymap[f]
338 del self._copymap[f]
339
339
340 def forget(self, f):
340 def forget(self, f):
341 'forget a file'
341 'forget a file'
342 self._dirty = True
342 self._dirty = True
343 try:
343 try:
344 self._droppath(f)
344 self._droppath(f)
345 del self._map[f]
345 del self._map[f]
346 except KeyError:
346 except KeyError:
347 self._ui.warn(_("not in dirstate: %s\n") % f)
347 self._ui.warn(_("not in dirstate: %s\n") % f)
348
348
349 def _normalize(self, path, knownpath):
349 def _normalize(self, path, knownpath):
350 norm_path = os.path.normcase(path)
350 norm_path = os.path.normcase(path)
351 fold_path = self._foldmap.get(norm_path, None)
351 fold_path = self._foldmap.get(norm_path, None)
352 if fold_path is None:
352 if fold_path is None:
353 if knownpath or not os.path.exists(os.path.join(self._root, path)):
353 if knownpath or not os.path.exists(os.path.join(self._root, path)):
354 fold_path = path
354 fold_path = path
355 else:
355 else:
356 fold_path = self._foldmap.setdefault(norm_path,
356 fold_path = self._foldmap.setdefault(norm_path,
357 util.fspath(path, self._root))
357 util.fspath(path, self._root))
358 return fold_path
358 return fold_path
359
359
360 def clear(self):
360 def clear(self):
361 self._map = {}
361 self._map = {}
362 if "_dirs" in self.__dict__:
362 if "_dirs" in self.__dict__:
363 delattr(self, "_dirs");
363 delattr(self, "_dirs");
364 self._copymap = {}
364 self._copymap = {}
365 self._pl = [nullid, nullid]
365 self._pl = [nullid, nullid]
366 self._dirty = True
366 self._dirty = True
367
367
368 def rebuild(self, parent, files):
368 def rebuild(self, parent, files):
369 self.clear()
369 self.clear()
370 for f in files:
370 for f in files:
371 if 'x' in files.flags(f):
371 if 'x' in files.flags(f):
372 self._map[f] = ('n', 0777, -1, 0)
372 self._map[f] = ('n', 0777, -1, 0)
373 else:
373 else:
374 self._map[f] = ('n', 0666, -1, 0)
374 self._map[f] = ('n', 0666, -1, 0)
375 self._pl = (parent, nullid)
375 self._pl = (parent, nullid)
376 self._dirty = True
376 self._dirty = True
377
377
378 def write(self):
378 def write(self):
379 if not self._dirty:
379 if not self._dirty:
380 return
380 return
381 st = self._opener("dirstate", "w", atomictemp=True)
381 st = self._opener("dirstate", "w", atomictemp=True)
382
382
383 # use the modification time of the newly created temporary file as the
383 # use the modification time of the newly created temporary file as the
384 # filesystem's notion of 'now'
384 # filesystem's notion of 'now'
385 now = int(util.fstat(st).st_mtime)
385 now = int(util.fstat(st).st_mtime)
386
386
387 cs = cStringIO.StringIO()
387 cs = cStringIO.StringIO()
388 copymap = self._copymap
388 copymap = self._copymap
389 pack = struct.pack
389 pack = struct.pack
390 write = cs.write
390 write = cs.write
391 write("".join(self._pl))
391 write("".join(self._pl))
392 for f, e in self._map.iteritems():
392 for f, e in self._map.iteritems():
393 if f in copymap:
393 if f in copymap:
394 f = "%s\0%s" % (f, copymap[f])
394 f = "%s\0%s" % (f, copymap[f])
395
395
396 if e[0] == 'n' and e[3] == now:
396 if e[0] == 'n' and e[3] == now:
397 # The file was last modified "simultaneously" with the current
397 # The file was last modified "simultaneously" with the current
398 # write to dirstate (i.e. within the same second for file-
398 # write to dirstate (i.e. within the same second for file-
399 # systems with a granularity of 1 sec). This commonly happens
399 # systems with a granularity of 1 sec). This commonly happens
400 # for at least a couple of files on 'update'.
400 # for at least a couple of files on 'update'.
401 # The user could change the file without changing its size
401 # The user could change the file without changing its size
402 # within the same second. Invalidate the file's stat data in
402 # within the same second. Invalidate the file's stat data in
403 # dirstate, forcing future 'status' calls to compare the
403 # dirstate, forcing future 'status' calls to compare the
404 # contents of the file. This prevents mistakenly treating such
404 # contents of the file. This prevents mistakenly treating such
405 # files as clean.
405 # files as clean.
406 e = (e[0], 0, -1, -1) # mark entry as 'unset'
406 e = (e[0], 0, -1, -1) # mark entry as 'unset'
407
407
408 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
408 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
409 write(e)
409 write(e)
410 write(f)
410 write(f)
411 st.write(cs.getvalue())
411 st.write(cs.getvalue())
412 st.rename()
412 st.rename()
413 self._dirty = self._dirtypl = False
413 self._dirty = self._dirtypl = False
414
414
415 def _dirignore(self, f):
415 def _dirignore(self, f):
416 if f == '.':
416 if f == '.':
417 return False
417 return False
418 if self._ignore(f):
418 if self._ignore(f):
419 return True
419 return True
420 for p in _finddirs(f):
420 for p in _finddirs(f):
421 if self._ignore(p):
421 if self._ignore(p):
422 return True
422 return True
423 return False
423 return False
424
424
425 def walk(self, match, unknown, ignored):
425 def walk(self, match, unknown, ignored):
426 '''
426 '''
427 Walk recursively through the directory tree, finding all files
427 Walk recursively through the directory tree, finding all files
428 matched by match.
428 matched by match.
429
429
430 Return a dict mapping filename to stat-like object (either
430 Return a dict mapping filename to stat-like object (either
431 mercurial.osutil.stat instance or return value of os.stat()).
431 mercurial.osutil.stat instance or return value of os.stat()).
432 '''
432 '''
433
433
434 def fwarn(f, msg):
434 def fwarn(f, msg):
435 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
435 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
436 return False
436 return False
437
437
438 def badtype(mode):
438 def badtype(mode):
439 kind = _('unknown')
439 kind = _('unknown')
440 if stat.S_ISCHR(mode): kind = _('character device')
440 if stat.S_ISCHR(mode): kind = _('character device')
441 elif stat.S_ISBLK(mode): kind = _('block device')
441 elif stat.S_ISBLK(mode): kind = _('block device')
442 elif stat.S_ISFIFO(mode): kind = _('fifo')
442 elif stat.S_ISFIFO(mode): kind = _('fifo')
443 elif stat.S_ISSOCK(mode): kind = _('socket')
443 elif stat.S_ISSOCK(mode): kind = _('socket')
444 elif stat.S_ISDIR(mode): kind = _('directory')
444 elif stat.S_ISDIR(mode): kind = _('directory')
445 return _('unsupported file type (type is %s)') % kind
445 return _('unsupported file type (type is %s)') % kind
446
446
447 ignore = self._ignore
447 ignore = self._ignore
448 dirignore = self._dirignore
448 dirignore = self._dirignore
449 if ignored:
449 if ignored:
450 ignore = util.never
450 ignore = util.never
451 dirignore = util.never
451 dirignore = util.never
452 elif not unknown:
452 elif not unknown:
453 # if unknown and ignored are False, skip step 2
453 # if unknown and ignored are False, skip step 2
454 ignore = util.always
454 ignore = util.always
455 dirignore = util.always
455 dirignore = util.always
456
456
457 matchfn = match.matchfn
457 matchfn = match.matchfn
458 badfn = match.bad
458 badfn = match.bad
459 dmap = self._map
459 dmap = self._map
460 normpath = util.normpath
460 normpath = util.normpath
461 listdir = osutil.listdir
461 listdir = osutil.listdir
462 lstat = os.lstat
462 lstat = os.lstat
463 getkind = stat.S_IFMT
463 getkind = stat.S_IFMT
464 dirkind = stat.S_IFDIR
464 dirkind = stat.S_IFDIR
465 regkind = stat.S_IFREG
465 regkind = stat.S_IFREG
466 lnkkind = stat.S_IFLNK
466 lnkkind = stat.S_IFLNK
467 join = self._join
467 join = self._join
468 work = []
468 work = []
469 wadd = work.append
469 wadd = work.append
470
470
471 if self._checkcase:
471 if self._checkcase:
472 normalize = self._normalize
472 normalize = self._normalize
473 else:
473 else:
474 normalize = lambda x, y: x
474 normalize = lambda x, y: x
475
475
476 exact = skipstep3 = False
476 exact = skipstep3 = False
477 if matchfn == match.exact: # match.exact
477 if matchfn == match.exact: # match.exact
478 exact = True
478 exact = True
479 dirignore = util.always # skip step 2
479 dirignore = util.always # skip step 2
480 elif match.files() and not match.anypats(): # match.match, no patterns
480 elif match.files() and not match.anypats(): # match.match, no patterns
481 skipstep3 = True
481 skipstep3 = True
482
482
483 files = set(match.files())
483 files = set(match.files())
484 if not files or '.' in files:
484 if not files or '.' in files:
485 files = ['']
485 files = ['']
486 results = {'.hg': None}
486 results = {'.hg': None}
487
487
488 # step 1: find all explicit files
488 # step 1: find all explicit files
489 for ff in sorted(files):
489 for ff in sorted(files):
490 nf = normalize(normpath(ff), False)
490 nf = normalize(normpath(ff), False)
491 if nf in results:
491 if nf in results:
492 continue
492 continue
493
493
494 try:
494 try:
495 st = lstat(join(nf))
495 st = lstat(join(nf))
496 kind = getkind(st.st_mode)
496 kind = getkind(st.st_mode)
497 if kind == dirkind:
497 if kind == dirkind:
498 skipstep3 = False
498 skipstep3 = False
499 if nf in dmap:
499 if nf in dmap:
500 #file deleted on disk but still in dirstate
500 #file deleted on disk but still in dirstate
501 results[nf] = None
501 results[nf] = None
502 match.dir(nf)
502 match.dir(nf)
503 if not dirignore(nf):
503 if not dirignore(nf):
504 wadd(nf)
504 wadd(nf)
505 elif kind == regkind or kind == lnkkind:
505 elif kind == regkind or kind == lnkkind:
506 results[nf] = st
506 results[nf] = st
507 else:
507 else:
508 badfn(ff, badtype(kind))
508 badfn(ff, badtype(kind))
509 if nf in dmap:
509 if nf in dmap:
510 results[nf] = None
510 results[nf] = None
511 except OSError, inst:
511 except OSError, inst:
512 if nf in dmap: # does it exactly match a file?
512 if nf in dmap: # does it exactly match a file?
513 results[nf] = None
513 results[nf] = None
514 else: # does it match a directory?
514 else: # does it match a directory?
515 prefix = nf + "/"
515 prefix = nf + "/"
516 for fn in dmap:
516 for fn in dmap:
517 if fn.startswith(prefix):
517 if fn.startswith(prefix):
518 match.dir(nf)
518 match.dir(nf)
519 skipstep3 = False
519 skipstep3 = False
520 break
520 break
521 else:
521 else:
522 badfn(ff, inst.strerror)
522 badfn(ff, inst.strerror)
523
523
524 # step 2: visit subdirectories
524 # step 2: visit subdirectories
525 while work:
525 while work:
526 nd = work.pop()
526 nd = work.pop()
527 skip = None
527 skip = None
528 if nd == '.':
528 if nd == '.':
529 nd = ''
529 nd = ''
530 else:
530 else:
531 skip = '.hg'
531 skip = '.hg'
532 try:
532 try:
533 entries = listdir(join(nd), stat=True, skip=skip)
533 entries = listdir(join(nd), stat=True, skip=skip)
534 except OSError, inst:
534 except OSError, inst:
535 if inst.errno == errno.EACCES:
535 if inst.errno == errno.EACCES:
536 fwarn(nd, inst.strerror)
536 fwarn(nd, inst.strerror)
537 continue
537 continue
538 raise
538 raise
539 for f, kind, st in entries:
539 for f, kind, st in entries:
540 nf = normalize(nd and (nd + "/" + f) or f, True)
540 nf = normalize(nd and (nd + "/" + f) or f, True)
541 if nf not in results:
541 if nf not in results:
542 if kind == dirkind:
542 if kind == dirkind:
543 if not ignore(nf):
543 if not ignore(nf):
544 match.dir(nf)
544 match.dir(nf)
545 wadd(nf)
545 wadd(nf)
546 if nf in dmap and matchfn(nf):
546 if nf in dmap and matchfn(nf):
547 results[nf] = None
547 results[nf] = None
548 elif kind == regkind or kind == lnkkind:
548 elif kind == regkind or kind == lnkkind:
549 if nf in dmap:
549 if nf in dmap:
550 if matchfn(nf):
550 if matchfn(nf):
551 results[nf] = st
551 results[nf] = st
552 elif matchfn(nf) and not ignore(nf):
552 elif matchfn(nf) and not ignore(nf):
553 results[nf] = st
553 results[nf] = st
554 elif nf in dmap and matchfn(nf):
554 elif nf in dmap and matchfn(nf):
555 results[nf] = None
555 results[nf] = None
556
556
557 # step 3: report unseen items in the dmap hash
557 # step 3: report unseen items in the dmap hash
558 if not skipstep3 and not exact:
558 if not skipstep3 and not exact:
559 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
559 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
560 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
560 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
561 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
561 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
562 st = None
562 st = None
563 results[nf] = st
563 results[nf] = st
564
564
565 del results['.hg']
565 del results['.hg']
566 return results
566 return results
567
567
568 def status(self, match, ignored, clean, unknown):
568 def status(self, match, ignored, clean, unknown):
569 '''Determine the status of the working copy relative to the
569 '''Determine the status of the working copy relative to the
570 dirstate and return a tuple of lists (unsure, modified, added,
570 dirstate and return a tuple of lists (unsure, modified, added,
571 removed, deleted, unknown, ignored, clean), where:
571 removed, deleted, unknown, ignored, clean), where:
572
572
573 unsure:
573 unsure:
574 files that might have been modified since the dirstate was
574 files that might have been modified since the dirstate was
575 written, but need to be read to be sure (size is the same
575 written, but need to be read to be sure (size is the same
576 but mtime differs)
576 but mtime differs)
577 modified:
577 modified:
578 files that have definitely been modified since the dirstate
578 files that have definitely been modified since the dirstate
579 was written (different size or mode)
579 was written (different size or mode)
580 added:
580 added:
581 files that have been explicitly added with hg add
581 files that have been explicitly added with hg add
582 removed:
582 removed:
583 files that have been explicitly removed with hg remove
583 files that have been explicitly removed with hg remove
584 deleted:
584 deleted:
585 files that have been deleted through other means ("missing")
585 files that have been deleted through other means ("missing")
586 unknown:
586 unknown:
587 files not in the dirstate that are not ignored
587 files not in the dirstate that are not ignored
588 ignored:
588 ignored:
589 files not in the dirstate that are ignored
589 files not in the dirstate that are ignored
590 (by _dirignore())
590 (by _dirignore())
591 clean:
591 clean:
592 files that have definitely not been modified since the
592 files that have definitely not been modified since the
593 dirstate was written
593 dirstate was written
594 '''
594 '''
595 listignored, listclean, listunknown = ignored, clean, unknown
595 listignored, listclean, listunknown = ignored, clean, unknown
596 lookup, modified, added, unknown, ignored = [], [], [], [], []
596 lookup, modified, added, unknown, ignored = [], [], [], [], []
597 removed, deleted, clean = [], [], []
597 removed, deleted, clean = [], [], []
598
598
599 dmap = self._map
599 dmap = self._map
600 ladd = lookup.append # aka "unsure"
600 ladd = lookup.append # aka "unsure"
601 madd = modified.append
601 madd = modified.append
602 aadd = added.append
602 aadd = added.append
603 uadd = unknown.append
603 uadd = unknown.append
604 iadd = ignored.append
604 iadd = ignored.append
605 radd = removed.append
605 radd = removed.append
606 dadd = deleted.append
606 dadd = deleted.append
607 cadd = clean.append
607 cadd = clean.append
608
608
609 for fn, st in self.walk(match, listunknown, listignored).iteritems():
609 for fn, st in self.walk(match, listunknown, listignored).iteritems():
610 if fn not in dmap:
610 if fn not in dmap:
611 if (listignored or match.exact(fn)) and self._dirignore(fn):
611 if (listignored or match.exact(fn)) and self._dirignore(fn):
612 if listignored:
612 if listignored:
613 iadd(fn)
613 iadd(fn)
614 elif listunknown:
614 elif listunknown:
615 uadd(fn)
615 uadd(fn)
616 continue
616 continue
617
617
618 state, mode, size, time = dmap[fn]
618 state, mode, size, time = dmap[fn]
619
619
620 if not st and state in "nma":
620 if not st and state in "nma":
621 dadd(fn)
621 dadd(fn)
622 elif state == 'n':
622 elif state == 'n':
623 if (size >= 0 and
623 if (size >= 0 and
624 (size != st.st_size
624 (size != st.st_size
625 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
625 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
626 or size == -2
626 or size == -2
627 or fn in self._copymap):
627 or fn in self._copymap):
628 madd(fn)
628 madd(fn)
629 elif time != int(st.st_mtime):
629 elif time != int(st.st_mtime):
630 ladd(fn)
630 ladd(fn)
631 elif listclean:
631 elif listclean:
632 cadd(fn)
632 cadd(fn)
633 elif state == 'm':
633 elif state == 'm':
634 madd(fn)
634 madd(fn)
635 elif state == 'a':
635 elif state == 'a':
636 aadd(fn)
636 aadd(fn)
637 elif state == 'r':
637 elif state == 'r':
638 radd(fn)
638 radd(fn)
639
639
640 return (lookup, modified, added, removed, deleted, unknown, ignored,
640 return (lookup, modified, added, removed, deleted, unknown, ignored,
641 clean)
641 clean)
@@ -1,512 +1,511 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching 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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time
10 import util, commands, hg, fancyopts, extensions, hook, error
10 import util, commands, hg, fancyopts, extensions, hook, error
11 import cmdutil, encoding
11 import cmdutil, encoding
12 import ui as _ui
12 import ui as _ui
13
13
14 def run():
14 def run():
15 "run the command in sys.argv"
15 "run the command in sys.argv"
16 sys.exit(dispatch(sys.argv[1:]))
16 sys.exit(dispatch(sys.argv[1:]))
17
17
18 def dispatch(args):
18 def dispatch(args):
19 "run the command specified in args"
19 "run the command specified in args"
20 try:
20 try:
21 u = _ui.ui()
21 u = _ui.ui()
22 if '--traceback' in args:
22 if '--traceback' in args:
23 u.setconfig('ui', 'traceback', 'on')
23 u.setconfig('ui', 'traceback', 'on')
24 except util.Abort, inst:
24 except util.Abort, inst:
25 sys.stderr.write(_("abort: %s\n") % inst)
25 sys.stderr.write(_("abort: %s\n") % inst)
26 return -1
26 return -1
27 except error.ConfigError, inst:
27 except error.ConfigError, inst:
28 sys.stderr.write(_("hg: %s\n") % inst)
28 sys.stderr.write(_("hg: %s\n") % inst)
29 return -1
29 return -1
30 return _runcatch(u, args)
30 return _runcatch(u, args)
31
31
32 def _runcatch(ui, args):
32 def _runcatch(ui, args):
33 def catchterm(*args):
33 def catchterm(*args):
34 raise error.SignalInterrupt
34 raise error.SignalInterrupt
35
35
36 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
36 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
37 num = getattr(signal, name, None)
37 num = getattr(signal, name, None)
38 if num: signal.signal(num, catchterm)
38 if num: signal.signal(num, catchterm)
39
39
40 try:
40 try:
41 try:
41 try:
42 # enter the debugger before command execution
42 # enter the debugger before command execution
43 if '--debugger' in args:
43 if '--debugger' in args:
44 pdb.set_trace()
44 pdb.set_trace()
45 try:
45 try:
46 return _dispatch(ui, args)
46 return _dispatch(ui, args)
47 finally:
47 finally:
48 ui.flush()
48 ui.flush()
49 except:
49 except:
50 # enter the debugger when we hit an exception
50 # enter the debugger when we hit an exception
51 if '--debugger' in args:
51 if '--debugger' in args:
52 pdb.post_mortem(sys.exc_info()[2])
52 pdb.post_mortem(sys.exc_info()[2])
53 ui.traceback()
53 ui.traceback()
54 raise
54 raise
55
55
56 # Global exception handling, alphabetically
56 # Global exception handling, alphabetically
57 # Mercurial-specific first, followed by built-in and library exceptions
57 # Mercurial-specific first, followed by built-in and library exceptions
58 except error.AmbiguousCommand, inst:
58 except error.AmbiguousCommand, inst:
59 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
59 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
60 (inst.args[0], " ".join(inst.args[1])))
60 (inst.args[0], " ".join(inst.args[1])))
61 except error.ConfigError, inst:
61 except error.ConfigError, inst:
62 ui.warn(_("hg: %s\n") % inst.args[0])
62 ui.warn(_("hg: %s\n") % inst.args[0])
63 except error.LockHeld, inst:
63 except error.LockHeld, inst:
64 if inst.errno == errno.ETIMEDOUT:
64 if inst.errno == errno.ETIMEDOUT:
65 reason = _('timed out waiting for lock held by %s') % inst.locker
65 reason = _('timed out waiting for lock held by %s') % inst.locker
66 else:
66 else:
67 reason = _('lock held by %s') % inst.locker
67 reason = _('lock held by %s') % inst.locker
68 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
68 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
69 except error.LockUnavailable, inst:
69 except error.LockUnavailable, inst:
70 ui.warn(_("abort: could not lock %s: %s\n") %
70 ui.warn(_("abort: could not lock %s: %s\n") %
71 (inst.desc or inst.filename, inst.strerror))
71 (inst.desc or inst.filename, inst.strerror))
72 except error.ParseError, inst:
72 except error.ParseError, inst:
73 if inst.args[0]:
73 if inst.args[0]:
74 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
74 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
75 commands.help_(ui, inst.args[0])
75 commands.help_(ui, inst.args[0])
76 else:
76 else:
77 ui.warn(_("hg: %s\n") % inst.args[1])
77 ui.warn(_("hg: %s\n") % inst.args[1])
78 commands.help_(ui, 'shortlist')
78 commands.help_(ui, 'shortlist')
79 except error.RepoError, inst:
79 except error.RepoError, inst:
80 ui.warn(_("abort: %s!\n") % inst)
80 ui.warn(_("abort: %s!\n") % inst)
81 except error.ResponseError, inst:
81 except error.ResponseError, inst:
82 ui.warn(_("abort: %s") % inst.args[0])
82 ui.warn(_("abort: %s") % inst.args[0])
83 if not isinstance(inst.args[1], basestring):
83 if not isinstance(inst.args[1], basestring):
84 ui.warn(" %r\n" % (inst.args[1],))
84 ui.warn(" %r\n" % (inst.args[1],))
85 elif not inst.args[1]:
85 elif not inst.args[1]:
86 ui.warn(_(" empty string\n"))
86 ui.warn(_(" empty string\n"))
87 else:
87 else:
88 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
88 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
89 except error.RevlogError, inst:
89 except error.RevlogError, inst:
90 ui.warn(_("abort: %s!\n") % inst)
90 ui.warn(_("abort: %s!\n") % inst)
91 except error.SignalInterrupt:
91 except error.SignalInterrupt:
92 ui.warn(_("killed!\n"))
92 ui.warn(_("killed!\n"))
93 except error.UnknownCommand, inst:
93 except error.UnknownCommand, inst:
94 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
94 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
95 commands.help_(ui, 'shortlist')
95 commands.help_(ui, 'shortlist')
96 except util.Abort, inst:
96 except util.Abort, inst:
97 ui.warn(_("abort: %s\n") % inst)
97 ui.warn(_("abort: %s\n") % inst)
98 except ImportError, inst:
98 except ImportError, inst:
99 m = str(inst).split()[-1]
99 m = str(inst).split()[-1]
100 ui.warn(_("abort: could not import module %s!\n") % m)
100 ui.warn(_("abort: could not import module %s!\n") % m)
101 if m in "mpatch bdiff".split():
101 if m in "mpatch bdiff".split():
102 ui.warn(_("(did you forget to compile extensions?)\n"))
102 ui.warn(_("(did you forget to compile extensions?)\n"))
103 elif m in "zlib".split():
103 elif m in "zlib".split():
104 ui.warn(_("(is your Python install correct?)\n"))
104 ui.warn(_("(is your Python install correct?)\n"))
105 except IOError, inst:
105 except IOError, inst:
106 if hasattr(inst, "code"):
106 if hasattr(inst, "code"):
107 ui.warn(_("abort: %s\n") % inst)
107 ui.warn(_("abort: %s\n") % inst)
108 elif hasattr(inst, "reason"):
108 elif hasattr(inst, "reason"):
109 try: # usually it is in the form (errno, strerror)
109 try: # usually it is in the form (errno, strerror)
110 reason = inst.reason.args[1]
110 reason = inst.reason.args[1]
111 except: # it might be anything, for example a string
111 except: # it might be anything, for example a string
112 reason = inst.reason
112 reason = inst.reason
113 ui.warn(_("abort: error: %s\n") % reason)
113 ui.warn(_("abort: error: %s\n") % reason)
114 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
114 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
115 if ui.debugflag:
115 if ui.debugflag:
116 ui.warn(_("broken pipe\n"))
116 ui.warn(_("broken pipe\n"))
117 elif getattr(inst, "strerror", None):
117 elif getattr(inst, "strerror", None):
118 if getattr(inst, "filename", None):
118 if getattr(inst, "filename", None):
119 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
119 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
120 else:
120 else:
121 ui.warn(_("abort: %s\n") % inst.strerror)
121 ui.warn(_("abort: %s\n") % inst.strerror)
122 else:
122 else:
123 raise
123 raise
124 except OSError, inst:
124 except OSError, inst:
125 if getattr(inst, "filename", None):
125 if getattr(inst, "filename", None):
126 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
126 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
127 else:
127 else:
128 ui.warn(_("abort: %s\n") % inst.strerror)
128 ui.warn(_("abort: %s\n") % inst.strerror)
129 except KeyboardInterrupt:
129 except KeyboardInterrupt:
130 try:
130 try:
131 ui.warn(_("interrupted!\n"))
131 ui.warn(_("interrupted!\n"))
132 except IOError, inst:
132 except IOError, inst:
133 if inst.errno == errno.EPIPE:
133 if inst.errno == errno.EPIPE:
134 if ui.debugflag:
134 if ui.debugflag:
135 ui.warn(_("\nbroken pipe\n"))
135 ui.warn(_("\nbroken pipe\n"))
136 else:
136 else:
137 raise
137 raise
138 except MemoryError:
138 except MemoryError:
139 ui.warn(_("abort: out of memory\n"))
139 ui.warn(_("abort: out of memory\n"))
140 except SystemExit, inst:
140 except SystemExit, inst:
141 # Commands shouldn't sys.exit directly, but give a return code.
141 # Commands shouldn't sys.exit directly, but give a return code.
142 # Just in case catch this and and pass exit code to caller.
142 # Just in case catch this and and pass exit code to caller.
143 return inst.code
143 return inst.code
144 except socket.error, inst:
144 except socket.error, inst:
145 ui.warn(_("abort: %s\n") % inst.args[-1])
145 ui.warn(_("abort: %s\n") % inst.args[-1])
146 except:
146 except:
147 ui.warn(_("** unknown exception encountered, details follow\n"))
147 ui.warn(_("** unknown exception encountered, details follow\n"))
148 ui.warn(_("** report bug details to "
148 ui.warn(_("** report bug details to "
149 "http://mercurial.selenic.com/bts/\n"))
149 "http://mercurial.selenic.com/bts/\n"))
150 ui.warn(_("** or mercurial@selenic.com\n"))
150 ui.warn(_("** or mercurial@selenic.com\n"))
151 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
151 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
152 % util.version())
152 % util.version())
153 ui.warn(_("** Extensions loaded: %s\n")
153 ui.warn(_("** Extensions loaded: %s\n")
154 % ", ".join([x[0] for x in extensions.extensions()]))
154 % ", ".join([x[0] for x in extensions.extensions()]))
155 raise
155 raise
156
156
157 return -1
157 return -1
158
158
159 def _findrepo(p):
159 def _findrepo(p):
160 while not os.path.isdir(os.path.join(p, ".hg")):
160 while not os.path.isdir(os.path.join(p, ".hg")):
161 oldp, p = p, os.path.dirname(p)
161 oldp, p = p, os.path.dirname(p)
162 if p == oldp:
162 if p == oldp:
163 return None
163 return None
164
164
165 return p
165 return p
166
166
167 def aliasargs(fn):
167 def aliasargs(fn):
168 if hasattr(fn, 'args'):
168 if hasattr(fn, 'args'):
169 return fn.args
169 return fn.args
170 return []
170 return []
171
171
172 class cmdalias(object):
172 class cmdalias(object):
173 def __init__(self, name, definition, cmdtable):
173 def __init__(self, name, definition, cmdtable):
174 self.name = name
174 self.name = name
175 self.definition = definition
175 self.definition = definition
176 self.args = []
176 self.args = []
177 self.opts = []
177 self.opts = []
178 self.help = ''
178 self.help = ''
179 self.norepo = True
179 self.norepo = True
180
180
181 try:
181 try:
182 cmdutil.findcmd(self.name, cmdtable, True)
182 cmdutil.findcmd(self.name, cmdtable, True)
183 self.shadows = True
183 self.shadows = True
184 except error.UnknownCommand:
184 except error.UnknownCommand:
185 self.shadows = False
185 self.shadows = False
186
186
187 if not self.definition:
187 if not self.definition:
188 def fn(ui, *args):
188 def fn(ui, *args):
189 ui.warn(_("no definition for alias '%s'\n") % self.name)
189 ui.warn(_("no definition for alias '%s'\n") % self.name)
190 return 1
190 return 1
191 self.fn = fn
191 self.fn = fn
192
192
193 return
193 return
194
194
195 args = shlex.split(self.definition)
195 args = shlex.split(self.definition)
196 cmd = args.pop(0)
196 cmd = args.pop(0)
197 opts = []
197 opts = []
198 help = ''
198 help = ''
199
199
200 try:
200 try:
201 self.fn, self.opts, self.help = cmdutil.findcmd(cmd, cmdtable, False)[1]
201 self.fn, self.opts, self.help = cmdutil.findcmd(cmd, cmdtable, False)[1]
202 self.args = aliasargs(self.fn) + args
202 self.args = aliasargs(self.fn) + args
203 if cmd not in commands.norepo.split(' '):
203 if cmd not in commands.norepo.split(' '):
204 self.norepo = False
204 self.norepo = False
205 except error.UnknownCommand:
205 except error.UnknownCommand:
206 def fn(ui, *args):
206 def fn(ui, *args):
207 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
207 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
208 % (self.name, cmd))
208 % (self.name, cmd))
209 return 1
209 return 1
210 self.fn = fn
210 self.fn = fn
211 except error.AmbiguousCommand:
211 except error.AmbiguousCommand:
212 def fn(ui, *args):
212 def fn(ui, *args):
213 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
213 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
214 % (self.name, cmd))
214 % (self.name, cmd))
215 return 1
215 return 1
216 self.fn = fn
216 self.fn = fn
217
217
218 def __call__(self, ui, *args, **opts):
218 def __call__(self, ui, *args, **opts):
219 if self.shadows:
219 if self.shadows:
220 ui.debug("alias '%s' shadows command\n" % self.name)
220 ui.debug("alias '%s' shadows command\n" % self.name)
221
221
222 return self.fn(ui, *args, **opts)
222 return self.fn(ui, *args, **opts)
223
223
224 def addaliases(ui, cmdtable):
224 def addaliases(ui, cmdtable):
225 # aliases are processed after extensions have been loaded, so they
225 # aliases are processed after extensions have been loaded, so they
226 # may use extension commands. Aliases can also use other alias definitions,
226 # may use extension commands. Aliases can also use other alias definitions,
227 # but only if they have been defined prior to the current definition.
227 # but only if they have been defined prior to the current definition.
228 for alias, definition in ui.configitems('alias'):
228 for alias, definition in ui.configitems('alias'):
229 aliasdef = cmdalias(alias, definition, cmdtable)
229 aliasdef = cmdalias(alias, definition, cmdtable)
230 cmdtable[alias] = (aliasdef, aliasdef.opts, aliasdef.help)
230 cmdtable[alias] = (aliasdef, aliasdef.opts, aliasdef.help)
231 if aliasdef.norepo:
231 if aliasdef.norepo:
232 commands.norepo += ' %s' % alias
232 commands.norepo += ' %s' % alias
233
233
234 def _parse(ui, args):
234 def _parse(ui, args):
235 options = {}
235 options = {}
236 cmdoptions = {}
236 cmdoptions = {}
237
237
238 try:
238 try:
239 args = fancyopts.fancyopts(args, commands.globalopts, options)
239 args = fancyopts.fancyopts(args, commands.globalopts, options)
240 except fancyopts.getopt.GetoptError, inst:
240 except fancyopts.getopt.GetoptError, inst:
241 raise error.ParseError(None, inst)
241 raise error.ParseError(None, inst)
242
242
243 if args:
243 if args:
244 cmd, args = args[0], args[1:]
244 cmd, args = args[0], args[1:]
245 aliases, i = cmdutil.findcmd(cmd, commands.table,
245 aliases, i = cmdutil.findcmd(cmd, commands.table,
246 ui.config("ui", "strict"))
246 ui.config("ui", "strict"))
247 cmd = aliases[0]
247 cmd = aliases[0]
248 args = aliasargs(i[0]) + args
248 args = aliasargs(i[0]) + args
249 defaults = ui.config("defaults", cmd)
249 defaults = ui.config("defaults", cmd)
250 if defaults:
250 if defaults:
251 args = shlex.split(defaults) + args
251 args = map(util.expandpath, shlex.split(defaults)) + args
252 c = list(i[1])
252 c = list(i[1])
253 else:
253 else:
254 cmd = None
254 cmd = None
255 c = []
255 c = []
256
256
257 # combine global options into local
257 # combine global options into local
258 for o in commands.globalopts:
258 for o in commands.globalopts:
259 c.append((o[0], o[1], options[o[1]], o[3]))
259 c.append((o[0], o[1], options[o[1]], o[3]))
260
260
261 try:
261 try:
262 args = fancyopts.fancyopts(args, c, cmdoptions, True)
262 args = fancyopts.fancyopts(args, c, cmdoptions, True)
263 except fancyopts.getopt.GetoptError, inst:
263 except fancyopts.getopt.GetoptError, inst:
264 raise error.ParseError(cmd, inst)
264 raise error.ParseError(cmd, inst)
265
265
266 # separate global options back out
266 # separate global options back out
267 for o in commands.globalopts:
267 for o in commands.globalopts:
268 n = o[1]
268 n = o[1]
269 options[n] = cmdoptions[n]
269 options[n] = cmdoptions[n]
270 del cmdoptions[n]
270 del cmdoptions[n]
271
271
272 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
272 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
273
273
274 def _parseconfig(ui, config):
274 def _parseconfig(ui, config):
275 """parse the --config options from the command line"""
275 """parse the --config options from the command line"""
276 for cfg in config:
276 for cfg in config:
277 try:
277 try:
278 name, value = cfg.split('=', 1)
278 name, value = cfg.split('=', 1)
279 section, name = name.split('.', 1)
279 section, name = name.split('.', 1)
280 if not section or not name:
280 if not section or not name:
281 raise IndexError
281 raise IndexError
282 ui.setconfig(section, name, value)
282 ui.setconfig(section, name, value)
283 except (IndexError, ValueError):
283 except (IndexError, ValueError):
284 raise util.Abort(_('malformed --config option: %s') % cfg)
284 raise util.Abort(_('malformed --config option: %s') % cfg)
285
285
286 def _earlygetopt(aliases, args):
286 def _earlygetopt(aliases, args):
287 """Return list of values for an option (or aliases).
287 """Return list of values for an option (or aliases).
288
288
289 The values are listed in the order they appear in args.
289 The values are listed in the order they appear in args.
290 The options and values are removed from args.
290 The options and values are removed from args.
291 """
291 """
292 try:
292 try:
293 argcount = args.index("--")
293 argcount = args.index("--")
294 except ValueError:
294 except ValueError:
295 argcount = len(args)
295 argcount = len(args)
296 shortopts = [opt for opt in aliases if len(opt) == 2]
296 shortopts = [opt for opt in aliases if len(opt) == 2]
297 values = []
297 values = []
298 pos = 0
298 pos = 0
299 while pos < argcount:
299 while pos < argcount:
300 if args[pos] in aliases:
300 if args[pos] in aliases:
301 if pos + 1 >= argcount:
301 if pos + 1 >= argcount:
302 # ignore and let getopt report an error if there is no value
302 # ignore and let getopt report an error if there is no value
303 break
303 break
304 del args[pos]
304 del args[pos]
305 values.append(args.pop(pos))
305 values.append(args.pop(pos))
306 argcount -= 2
306 argcount -= 2
307 elif args[pos][:2] in shortopts:
307 elif args[pos][:2] in shortopts:
308 # short option can have no following space, e.g. hg log -Rfoo
308 # short option can have no following space, e.g. hg log -Rfoo
309 values.append(args.pop(pos)[2:])
309 values.append(args.pop(pos)[2:])
310 argcount -= 1
310 argcount -= 1
311 else:
311 else:
312 pos += 1
312 pos += 1
313 return values
313 return values
314
314
315 def runcommand(lui, repo, cmd, fullargs, ui, options, d):
315 def runcommand(lui, repo, cmd, fullargs, ui, options, d):
316 # run pre-hook, and abort if it fails
316 # run pre-hook, and abort if it fails
317 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
317 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
318 if ret:
318 if ret:
319 return ret
319 return ret
320 ret = _runcommand(ui, options, cmd, d)
320 ret = _runcommand(ui, options, cmd, d)
321 # run post-hook, passing command result
321 # run post-hook, passing command result
322 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
322 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
323 result = ret)
323 result = ret)
324 return ret
324 return ret
325
325
326 _loaded = set()
326 _loaded = set()
327 def _dispatch(ui, args):
327 def _dispatch(ui, args):
328 # read --config before doing anything else
328 # read --config before doing anything else
329 # (e.g. to change trust settings for reading .hg/hgrc)
329 # (e.g. to change trust settings for reading .hg/hgrc)
330 _parseconfig(ui, _earlygetopt(['--config'], args))
330 _parseconfig(ui, _earlygetopt(['--config'], args))
331
331
332 # check for cwd
332 # check for cwd
333 cwd = _earlygetopt(['--cwd'], args)
333 cwd = _earlygetopt(['--cwd'], args)
334 if cwd:
334 if cwd:
335 os.chdir(cwd[-1])
335 os.chdir(cwd[-1])
336
336
337 # read the local repository .hgrc into a local ui object
337 # read the local repository .hgrc into a local ui object
338 path = _findrepo(os.getcwd()) or ""
338 path = _findrepo(os.getcwd()) or ""
339 if not path:
339 if not path:
340 lui = ui
340 lui = ui
341 else:
341 else:
342 try:
342 try:
343 lui = ui.copy()
343 lui = ui.copy()
344 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
344 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
345 except IOError:
345 except IOError:
346 pass
346 pass
347
347
348 # now we can expand paths, even ones in .hg/hgrc
348 # now we can expand paths, even ones in .hg/hgrc
349 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
349 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
350 if rpath:
350 if rpath:
351 path = lui.expandpath(rpath[-1])
351 path = lui.expandpath(rpath[-1])
352 lui = ui.copy()
352 lui = ui.copy()
353 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
353 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
354
354
355 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
355 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
356 # reposetup. Programs like TortoiseHg will call _dispatch several
356 # reposetup. Programs like TortoiseHg will call _dispatch several
357 # times so we keep track of configured extensions in _loaded.
357 # times so we keep track of configured extensions in _loaded.
358 extensions.loadall(lui)
358 extensions.loadall(lui)
359 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
359 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
360
360
361 # (uisetup is handled in extensions.loadall)
361 # (uisetup is handled in extensions.loadall)
362
362
363 for name, module in exts:
363 for name, module in exts:
364 extsetup = getattr(module, 'extsetup', None)
364 extsetup = getattr(module, 'extsetup', None)
365 if extsetup:
365 if extsetup:
366 try:
366 try:
367 extsetup(ui)
367 extsetup(ui)
368 except TypeError:
368 except TypeError:
369 if extsetup.func_code.co_argcount != 0:
369 if extsetup.func_code.co_argcount != 0:
370 raise
370 raise
371 extsetup() # old extsetup with no ui argument
371 extsetup() # old extsetup with no ui argument
372
372
373 for name, module in exts:
373 for name, module in exts:
374 cmdtable = getattr(module, 'cmdtable', {})
374 cmdtable = getattr(module, 'cmdtable', {})
375 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
375 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
376 if overrides:
376 if overrides:
377 ui.warn(_("extension '%s' overrides commands: %s\n")
377 ui.warn(_("extension '%s' overrides commands: %s\n")
378 % (name, " ".join(overrides)))
378 % (name, " ".join(overrides)))
379 commands.table.update(cmdtable)
379 commands.table.update(cmdtable)
380 _loaded.add(name)
380 _loaded.add(name)
381
381
382 # (reposetup is handled in hg.repository)
382 # (reposetup is handled in hg.repository)
383
383
384 addaliases(lui, commands.table)
384 addaliases(lui, commands.table)
385
385
386 # check for fallback encoding
386 # check for fallback encoding
387 fallback = lui.config('ui', 'fallbackencoding')
387 fallback = lui.config('ui', 'fallbackencoding')
388 if fallback:
388 if fallback:
389 encoding.fallbackencoding = fallback
389 encoding.fallbackencoding = fallback
390
390
391 fullargs = args
391 fullargs = args
392 cmd, func, args, options, cmdoptions = _parse(lui, args)
392 cmd, func, args, options, cmdoptions = _parse(lui, args)
393
393
394 if options["config"]:
394 if options["config"]:
395 raise util.Abort(_("Option --config may not be abbreviated!"))
395 raise util.Abort(_("Option --config may not be abbreviated!"))
396 if options["cwd"]:
396 if options["cwd"]:
397 raise util.Abort(_("Option --cwd may not be abbreviated!"))
397 raise util.Abort(_("Option --cwd may not be abbreviated!"))
398 if options["repository"]:
398 if options["repository"]:
399 raise util.Abort(_(
399 raise util.Abort(_(
400 "Option -R has to be separated from other options (e.g. not -qR) "
400 "Option -R has to be separated from other options (e.g. not -qR) "
401 "and --repository may only be abbreviated as --repo!"))
401 "and --repository may only be abbreviated as --repo!"))
402
402
403 if options["encoding"]:
403 if options["encoding"]:
404 encoding.encoding = options["encoding"]
404 encoding.encoding = options["encoding"]
405 if options["encodingmode"]:
405 if options["encodingmode"]:
406 encoding.encodingmode = options["encodingmode"]
406 encoding.encodingmode = options["encodingmode"]
407 if options["time"]:
407 if options["time"]:
408 def get_times():
408 def get_times():
409 t = os.times()
409 t = os.times()
410 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
410 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
411 t = (t[0], t[1], t[2], t[3], time.clock())
411 t = (t[0], t[1], t[2], t[3], time.clock())
412 return t
412 return t
413 s = get_times()
413 s = get_times()
414 def print_time():
414 def print_time():
415 t = get_times()
415 t = get_times()
416 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
416 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
417 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
417 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
418 atexit.register(print_time)
418 atexit.register(print_time)
419
419
420 if options['verbose'] or options['debug'] or options['quiet']:
420 if options['verbose'] or options['debug'] or options['quiet']:
421 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
421 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
422 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
422 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
423 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
423 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
424 if options['traceback']:
424 if options['traceback']:
425 ui.setconfig('ui', 'traceback', 'on')
425 ui.setconfig('ui', 'traceback', 'on')
426 if options['noninteractive']:
426 if options['noninteractive']:
427 ui.setconfig('ui', 'interactive', 'off')
427 ui.setconfig('ui', 'interactive', 'off')
428
428
429 if options['help']:
429 if options['help']:
430 return commands.help_(ui, cmd, options['version'])
430 return commands.help_(ui, cmd, options['version'])
431 elif options['version']:
431 elif options['version']:
432 return commands.version_(ui)
432 return commands.version_(ui)
433 elif not cmd:
433 elif not cmd:
434 return commands.help_(ui, 'shortlist')
434 return commands.help_(ui, 'shortlist')
435
435
436 repo = None
436 repo = None
437 if cmd not in commands.norepo.split():
437 if cmd not in commands.norepo.split():
438 try:
438 try:
439 repo = hg.repository(ui, path=path)
439 repo = hg.repository(ui, path=path)
440 ui = repo.ui
440 ui = repo.ui
441 if not repo.local():
441 if not repo.local():
442 raise util.Abort(_("repository '%s' is not local") % path)
442 raise util.Abort(_("repository '%s' is not local") % path)
443 ui.setconfig("bundle", "mainreporoot", repo.root)
443 ui.setconfig("bundle", "mainreporoot", repo.root)
444 except error.RepoError:
444 except error.RepoError:
445 if cmd not in commands.optionalrepo.split():
445 if cmd not in commands.optionalrepo.split():
446 if args and not path: # try to infer -R from command args
446 if args and not path: # try to infer -R from command args
447 repos = map(_findrepo, args)
447 repos = map(_findrepo, args)
448 guess = repos[0]
448 guess = repos[0]
449 if guess and repos.count(guess) == len(repos):
449 if guess and repos.count(guess) == len(repos):
450 return _dispatch(ui, ['--repository', guess] + fullargs)
450 return _dispatch(ui, ['--repository', guess] + fullargs)
451 if not path:
451 if not path:
452 raise error.RepoError(_("There is no Mercurial repository"
452 raise error.RepoError(_("There is no Mercurial repository"
453 " here (.hg not found)"))
453 " here (.hg not found)"))
454 raise
454 raise
455 args.insert(0, repo)
455 args.insert(0, repo)
456 elif rpath:
456 elif rpath:
457 ui.warn("warning: --repository ignored\n")
457 ui.warn("warning: --repository ignored\n")
458
458
459 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
459 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
460 return runcommand(lui, repo, cmd, fullargs, ui, options, d)
460 return runcommand(lui, repo, cmd, fullargs, ui, options, d)
461
461
462 def _runcommand(ui, options, cmd, cmdfunc):
462 def _runcommand(ui, options, cmd, cmdfunc):
463 def checkargs():
463 def checkargs():
464 try:
464 try:
465 return cmdfunc()
465 return cmdfunc()
466 except error.SignatureError:
466 except error.SignatureError:
467 raise error.ParseError(cmd, _("invalid arguments"))
467 raise error.ParseError(cmd, _("invalid arguments"))
468
468
469 if options['profile']:
469 if options['profile']:
470 format = ui.config('profiling', 'format', default='text')
470 format = ui.config('profiling', 'format', default='text')
471
471
472 if not format in ['text', 'kcachegrind']:
472 if not format in ['text', 'kcachegrind']:
473 ui.warn(_("unrecognized profiling format '%s'"
473 ui.warn(_("unrecognized profiling format '%s'"
474 " - Ignored\n") % format)
474 " - Ignored\n") % format)
475 format = 'text'
475 format = 'text'
476
476
477 output = ui.config('profiling', 'output')
477 output = ui.config('profiling', 'output')
478
478
479 if output:
479 if output:
480 path = os.path.expanduser(output)
480 path = ui.expandpath(output)
481 path = ui.expandpath(path)
482 ostream = open(path, 'wb')
481 ostream = open(path, 'wb')
483 else:
482 else:
484 ostream = sys.stderr
483 ostream = sys.stderr
485
484
486 try:
485 try:
487 from mercurial import lsprof
486 from mercurial import lsprof
488 except ImportError:
487 except ImportError:
489 raise util.Abort(_(
488 raise util.Abort(_(
490 'lsprof not available - install from '
489 'lsprof not available - install from '
491 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
490 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
492 p = lsprof.Profiler()
491 p = lsprof.Profiler()
493 p.enable(subcalls=True)
492 p.enable(subcalls=True)
494 try:
493 try:
495 return checkargs()
494 return checkargs()
496 finally:
495 finally:
497 p.disable()
496 p.disable()
498
497
499 if format == 'kcachegrind':
498 if format == 'kcachegrind':
500 import lsprofcalltree
499 import lsprofcalltree
501 calltree = lsprofcalltree.KCacheGrind(p)
500 calltree = lsprofcalltree.KCacheGrind(p)
502 calltree.output(ostream)
501 calltree.output(ostream)
503 else:
502 else:
504 # format == 'text'
503 # format == 'text'
505 stats = lsprof.Stats(p.getstats())
504 stats = lsprof.Stats(p.getstats())
506 stats.sort()
505 stats.sort()
507 stats.pprint(top=10, file=ostream, climit=5)
506 stats.pprint(top=10, file=ostream, climit=5)
508
507
509 if output:
508 if output:
510 ostream.close()
509 ostream.close()
511 else:
510 else:
512 return checkargs()
511 return checkargs()
@@ -1,181 +1,181 b''
1 # extensions.py - extension handling for mercurial
1 # extensions.py - extension handling 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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 import imp, os
8 import imp, os
9 import util, cmdutil, help
9 import util, cmdutil, help
10 from i18n import _, gettext
10 from i18n import _, gettext
11
11
12 _extensions = {}
12 _extensions = {}
13 _order = []
13 _order = []
14
14
15 def extensions():
15 def extensions():
16 for name in _order:
16 for name in _order:
17 module = _extensions[name]
17 module = _extensions[name]
18 if module:
18 if module:
19 yield name, module
19 yield name, module
20
20
21 def find(name):
21 def find(name):
22 '''return module with given extension name'''
22 '''return module with given extension name'''
23 try:
23 try:
24 return _extensions[name]
24 return _extensions[name]
25 except KeyError:
25 except KeyError:
26 for k, v in _extensions.iteritems():
26 for k, v in _extensions.iteritems():
27 if k.endswith('.' + name) or k.endswith('/' + name):
27 if k.endswith('.' + name) or k.endswith('/' + name):
28 return v
28 return v
29 raise KeyError(name)
29 raise KeyError(name)
30
30
31 def loadpath(path, module_name):
31 def loadpath(path, module_name):
32 module_name = module_name.replace('.', '_')
32 module_name = module_name.replace('.', '_')
33 path = os.path.expanduser(path)
33 path = util.expandpath(path)
34 if os.path.isdir(path):
34 if os.path.isdir(path):
35 # module/__init__.py style
35 # module/__init__.py style
36 d, f = os.path.split(path.rstrip('/'))
36 d, f = os.path.split(path.rstrip('/'))
37 fd, fpath, desc = imp.find_module(f, [d])
37 fd, fpath, desc = imp.find_module(f, [d])
38 return imp.load_module(module_name, fd, fpath, desc)
38 return imp.load_module(module_name, fd, fpath, desc)
39 else:
39 else:
40 return imp.load_source(module_name, path)
40 return imp.load_source(module_name, path)
41
41
42 def load(ui, name, path):
42 def load(ui, name, path):
43 # unused ui argument kept for backwards compatibility
43 # unused ui argument kept for backwards compatibility
44 if name.startswith('hgext.') or name.startswith('hgext/'):
44 if name.startswith('hgext.') or name.startswith('hgext/'):
45 shortname = name[6:]
45 shortname = name[6:]
46 else:
46 else:
47 shortname = name
47 shortname = name
48 if shortname in _extensions:
48 if shortname in _extensions:
49 return
49 return
50 _extensions[shortname] = None
50 _extensions[shortname] = None
51 if path:
51 if path:
52 # the module will be loaded in sys.modules
52 # the module will be loaded in sys.modules
53 # choose an unique name so that it doesn't
53 # choose an unique name so that it doesn't
54 # conflicts with other modules
54 # conflicts with other modules
55 mod = loadpath(path, 'hgext.%s' % name)
55 mod = loadpath(path, 'hgext.%s' % name)
56 else:
56 else:
57 def importh(name):
57 def importh(name):
58 mod = __import__(name)
58 mod = __import__(name)
59 components = name.split('.')
59 components = name.split('.')
60 for comp in components[1:]:
60 for comp in components[1:]:
61 mod = getattr(mod, comp)
61 mod = getattr(mod, comp)
62 return mod
62 return mod
63 try:
63 try:
64 mod = importh("hgext.%s" % name)
64 mod = importh("hgext.%s" % name)
65 except ImportError:
65 except ImportError:
66 mod = importh(name)
66 mod = importh(name)
67 _extensions[shortname] = mod
67 _extensions[shortname] = mod
68 _order.append(shortname)
68 _order.append(shortname)
69
69
70 def loadall(ui):
70 def loadall(ui):
71 result = ui.configitems("extensions")
71 result = ui.configitems("extensions")
72 newindex = len(_order)
72 newindex = len(_order)
73 for (name, path) in result:
73 for (name, path) in result:
74 if path:
74 if path:
75 if path[0] == '!':
75 if path[0] == '!':
76 continue
76 continue
77 try:
77 try:
78 load(ui, name, path)
78 load(ui, name, path)
79 except KeyboardInterrupt:
79 except KeyboardInterrupt:
80 raise
80 raise
81 except Exception, inst:
81 except Exception, inst:
82 if path:
82 if path:
83 ui.warn(_("*** failed to import extension %s from %s: %s\n")
83 ui.warn(_("*** failed to import extension %s from %s: %s\n")
84 % (name, path, inst))
84 % (name, path, inst))
85 else:
85 else:
86 ui.warn(_("*** failed to import extension %s: %s\n")
86 ui.warn(_("*** failed to import extension %s: %s\n")
87 % (name, inst))
87 % (name, inst))
88 if ui.traceback():
88 if ui.traceback():
89 return 1
89 return 1
90
90
91 for name in _order[newindex:]:
91 for name in _order[newindex:]:
92 uisetup = getattr(_extensions[name], 'uisetup', None)
92 uisetup = getattr(_extensions[name], 'uisetup', None)
93 if uisetup:
93 if uisetup:
94 uisetup(ui)
94 uisetup(ui)
95
95
96 def wrapcommand(table, command, wrapper):
96 def wrapcommand(table, command, wrapper):
97 aliases, entry = cmdutil.findcmd(command, table)
97 aliases, entry = cmdutil.findcmd(command, table)
98 for alias, e in table.iteritems():
98 for alias, e in table.iteritems():
99 if e is entry:
99 if e is entry:
100 key = alias
100 key = alias
101 break
101 break
102
102
103 origfn = entry[0]
103 origfn = entry[0]
104 def wrap(*args, **kwargs):
104 def wrap(*args, **kwargs):
105 return util.checksignature(wrapper)(
105 return util.checksignature(wrapper)(
106 util.checksignature(origfn), *args, **kwargs)
106 util.checksignature(origfn), *args, **kwargs)
107
107
108 wrap.__doc__ = getattr(origfn, '__doc__')
108 wrap.__doc__ = getattr(origfn, '__doc__')
109 wrap.__module__ = getattr(origfn, '__module__')
109 wrap.__module__ = getattr(origfn, '__module__')
110
110
111 newentry = list(entry)
111 newentry = list(entry)
112 newentry[0] = wrap
112 newentry[0] = wrap
113 table[key] = tuple(newentry)
113 table[key] = tuple(newentry)
114 return entry
114 return entry
115
115
116 def wrapfunction(container, funcname, wrapper):
116 def wrapfunction(container, funcname, wrapper):
117 def wrap(*args, **kwargs):
117 def wrap(*args, **kwargs):
118 return wrapper(origfn, *args, **kwargs)
118 return wrapper(origfn, *args, **kwargs)
119
119
120 origfn = getattr(container, funcname)
120 origfn = getattr(container, funcname)
121 setattr(container, funcname, wrap)
121 setattr(container, funcname, wrap)
122 return origfn
122 return origfn
123
123
124 def disabled():
124 def disabled():
125 '''find disabled extensions from hgext
125 '''find disabled extensions from hgext
126 returns a dict of {name: desc}, and the max name length'''
126 returns a dict of {name: desc}, and the max name length'''
127
127
128 import hgext
128 import hgext
129 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
129 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
130
130
131 try: # might not be a filesystem path
131 try: # might not be a filesystem path
132 files = os.listdir(extpath)
132 files = os.listdir(extpath)
133 except OSError:
133 except OSError:
134 return None, 0
134 return None, 0
135
135
136 exts = {}
136 exts = {}
137 maxlength = 0
137 maxlength = 0
138 for e in files:
138 for e in files:
139
139
140 if e.endswith('.py'):
140 if e.endswith('.py'):
141 name = e.rsplit('.', 1)[0]
141 name = e.rsplit('.', 1)[0]
142 path = os.path.join(extpath, e)
142 path = os.path.join(extpath, e)
143 else:
143 else:
144 name = e
144 name = e
145 path = os.path.join(extpath, e, '__init__.py')
145 path = os.path.join(extpath, e, '__init__.py')
146 if not os.path.exists(path):
146 if not os.path.exists(path):
147 continue
147 continue
148
148
149 if name in exts or name in _order or name == '__init__':
149 if name in exts or name in _order or name == '__init__':
150 continue
150 continue
151
151
152 try:
152 try:
153 file = open(path)
153 file = open(path)
154 except IOError:
154 except IOError:
155 continue
155 continue
156 else:
156 else:
157 doc = help.moduledoc(file)
157 doc = help.moduledoc(file)
158 file.close()
158 file.close()
159
159
160 if doc: # extracting localized synopsis
160 if doc: # extracting localized synopsis
161 exts[name] = gettext(doc).splitlines()[0]
161 exts[name] = gettext(doc).splitlines()[0]
162 else:
162 else:
163 exts[name] = _('(no help text available)')
163 exts[name] = _('(no help text available)')
164
164
165 if len(name) > maxlength:
165 if len(name) > maxlength:
166 maxlength = len(name)
166 maxlength = len(name)
167
167
168 return exts, maxlength
168 return exts, maxlength
169
169
170 def enabled():
170 def enabled():
171 '''return a dict of {name: desc} of extensions, and the max name length'''
171 '''return a dict of {name: desc} of extensions, and the max name length'''
172 exts = {}
172 exts = {}
173 maxlength = 0
173 maxlength = 0
174 exthelps = []
174 exthelps = []
175 for ename, ext in extensions():
175 for ename, ext in extensions():
176 doc = (gettext(ext.__doc__) or _('(no help text available)'))
176 doc = (gettext(ext.__doc__) or _('(no help text available)'))
177 ename = ename.split('.')[-1]
177 ename = ename.split('.')[-1]
178 maxlength = max(len(ename), maxlength)
178 maxlength = max(len(ename), maxlength)
179 exts[ename] = doc.splitlines()[0].strip()
179 exts[ename] = doc.splitlines()[0].strip()
180
180
181 return exts, maxlength
181 return exts, maxlength
@@ -1,381 +1,383 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits 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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import errno, getpass, os, socket, sys, tempfile, traceback
9 import errno, getpass, os, socket, sys, tempfile, traceback
10 import config, util, error
10 import config, util, error
11
11
12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
13 '0': False, 'no': False, 'false': False, 'off': False}
13 '0': False, 'no': False, 'false': False, 'off': False}
14
14
15 class ui(object):
15 class ui(object):
16 def __init__(self, src=None):
16 def __init__(self, src=None):
17 self._buffers = []
17 self._buffers = []
18 self.quiet = self.verbose = self.debugflag = self._traceback = False
18 self.quiet = self.verbose = self.debugflag = self._traceback = False
19 self._reportuntrusted = True
19 self._reportuntrusted = True
20 self._ocfg = config.config() # overlay
20 self._ocfg = config.config() # overlay
21 self._tcfg = config.config() # trusted
21 self._tcfg = config.config() # trusted
22 self._ucfg = config.config() # untrusted
22 self._ucfg = config.config() # untrusted
23 self._trustusers = set()
23 self._trustusers = set()
24 self._trustgroups = set()
24 self._trustgroups = set()
25
25
26 if src:
26 if src:
27 self._tcfg = src._tcfg.copy()
27 self._tcfg = src._tcfg.copy()
28 self._ucfg = src._ucfg.copy()
28 self._ucfg = src._ucfg.copy()
29 self._ocfg = src._ocfg.copy()
29 self._ocfg = src._ocfg.copy()
30 self._trustusers = src._trustusers.copy()
30 self._trustusers = src._trustusers.copy()
31 self._trustgroups = src._trustgroups.copy()
31 self._trustgroups = src._trustgroups.copy()
32 self.fixconfig()
32 self.fixconfig()
33 else:
33 else:
34 # we always trust global config files
34 # we always trust global config files
35 for f in util.rcpath():
35 for f in util.rcpath():
36 self.readconfig(f, trust=True)
36 self.readconfig(f, trust=True)
37
37
38 def copy(self):
38 def copy(self):
39 return self.__class__(self)
39 return self.__class__(self)
40
40
41 def _is_trusted(self, fp, f):
41 def _is_trusted(self, fp, f):
42 st = util.fstat(fp)
42 st = util.fstat(fp)
43 if util.isowner(st):
43 if util.isowner(st):
44 return True
44 return True
45
45
46 tusers, tgroups = self._trustusers, self._trustgroups
46 tusers, tgroups = self._trustusers, self._trustgroups
47 if '*' in tusers or '*' in tgroups:
47 if '*' in tusers or '*' in tgroups:
48 return True
48 return True
49
49
50 user = util.username(st.st_uid)
50 user = util.username(st.st_uid)
51 group = util.groupname(st.st_gid)
51 group = util.groupname(st.st_gid)
52 if user in tusers or group in tgroups or user == util.username():
52 if user in tusers or group in tgroups or user == util.username():
53 return True
53 return True
54
54
55 if self._reportuntrusted:
55 if self._reportuntrusted:
56 self.warn(_('Not trusting file %s from untrusted '
56 self.warn(_('Not trusting file %s from untrusted '
57 'user %s, group %s\n') % (f, user, group))
57 'user %s, group %s\n') % (f, user, group))
58 return False
58 return False
59
59
60 def readconfig(self, filename, root=None, trust=False,
60 def readconfig(self, filename, root=None, trust=False,
61 sections=None, remap=None):
61 sections=None, remap=None):
62 try:
62 try:
63 fp = open(filename)
63 fp = open(filename)
64 except IOError:
64 except IOError:
65 if not sections: # ignore unless we were looking for something
65 if not sections: # ignore unless we were looking for something
66 return
66 return
67 raise
67 raise
68
68
69 cfg = config.config()
69 cfg = config.config()
70 trusted = sections or trust or self._is_trusted(fp, filename)
70 trusted = sections or trust or self._is_trusted(fp, filename)
71
71
72 try:
72 try:
73 cfg.read(filename, fp, sections=sections, remap=remap)
73 cfg.read(filename, fp, sections=sections, remap=remap)
74 except error.ConfigError, inst:
74 except error.ConfigError, inst:
75 if trusted:
75 if trusted:
76 raise
76 raise
77 self.warn(_("Ignored: %s\n") % str(inst))
77 self.warn(_("Ignored: %s\n") % str(inst))
78
78
79 if trusted:
79 if trusted:
80 self._tcfg.update(cfg)
80 self._tcfg.update(cfg)
81 self._tcfg.update(self._ocfg)
81 self._tcfg.update(self._ocfg)
82 self._ucfg.update(cfg)
82 self._ucfg.update(cfg)
83 self._ucfg.update(self._ocfg)
83 self._ucfg.update(self._ocfg)
84
84
85 if root is None:
85 if root is None:
86 root = os.path.expanduser('~')
86 root = os.path.expanduser('~')
87 self.fixconfig(root=root)
87 self.fixconfig(root=root)
88
88
89 def fixconfig(self, root=None):
89 def fixconfig(self, root=None):
90 # translate paths relative to root (or home) into absolute paths
90 # translate paths relative to root (or home) into absolute paths
91 root = root or os.getcwd()
91 root = root or os.getcwd()
92 for c in self._tcfg, self._ucfg, self._ocfg:
92 for c in self._tcfg, self._ucfg, self._ocfg:
93 for n, p in c.items('paths'):
93 for n, p in c.items('paths'):
94 if p and "://" not in p and not os.path.isabs(p):
94 if p and "://" not in p and not os.path.isabs(p):
95 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
95 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
96
96
97 # update ui options
97 # update ui options
98 self.debugflag = self.configbool('ui', 'debug')
98 self.debugflag = self.configbool('ui', 'debug')
99 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
99 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
100 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
100 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
101 if self.verbose and self.quiet:
101 if self.verbose and self.quiet:
102 self.quiet = self.verbose = False
102 self.quiet = self.verbose = False
103 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
103 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
104 self._traceback = self.configbool('ui', 'traceback', False)
104 self._traceback = self.configbool('ui', 'traceback', False)
105
105
106 # update trust information
106 # update trust information
107 self._trustusers.update(self.configlist('trusted', 'users'))
107 self._trustusers.update(self.configlist('trusted', 'users'))
108 self._trustgroups.update(self.configlist('trusted', 'groups'))
108 self._trustgroups.update(self.configlist('trusted', 'groups'))
109
109
110 def setconfig(self, section, name, value):
110 def setconfig(self, section, name, value):
111 for cfg in (self._ocfg, self._tcfg, self._ucfg):
111 for cfg in (self._ocfg, self._tcfg, self._ucfg):
112 cfg.set(section, name, value)
112 cfg.set(section, name, value)
113 self.fixconfig()
113 self.fixconfig()
114
114
115 def _data(self, untrusted):
115 def _data(self, untrusted):
116 return untrusted and self._ucfg or self._tcfg
116 return untrusted and self._ucfg or self._tcfg
117
117
118 def configsource(self, section, name, untrusted=False):
118 def configsource(self, section, name, untrusted=False):
119 return self._data(untrusted).source(section, name) or 'none'
119 return self._data(untrusted).source(section, name) or 'none'
120
120
121 def config(self, section, name, default=None, untrusted=False):
121 def config(self, section, name, default=None, untrusted=False):
122 value = self._data(untrusted).get(section, name, default)
122 value = self._data(untrusted).get(section, name, default)
123 if self.debugflag and not untrusted and self._reportuntrusted:
123 if self.debugflag and not untrusted and self._reportuntrusted:
124 uvalue = self._ucfg.get(section, name)
124 uvalue = self._ucfg.get(section, name)
125 if uvalue is not None and uvalue != value:
125 if uvalue is not None and uvalue != value:
126 self.debug(_("ignoring untrusted configuration option "
126 self.debug(_("ignoring untrusted configuration option "
127 "%s.%s = %s\n") % (section, name, uvalue))
127 "%s.%s = %s\n") % (section, name, uvalue))
128 return value
128 return value
129
129
130 def configbool(self, section, name, default=False, untrusted=False):
130 def configbool(self, section, name, default=False, untrusted=False):
131 v = self.config(section, name, None, untrusted)
131 v = self.config(section, name, None, untrusted)
132 if v is None:
132 if v is None:
133 return default
133 return default
134 if v.lower() not in _booleans:
134 if v.lower() not in _booleans:
135 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
135 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
136 % (section, name, v))
136 % (section, name, v))
137 return _booleans[v.lower()]
137 return _booleans[v.lower()]
138
138
139 def configlist(self, section, name, default=None, untrusted=False):
139 def configlist(self, section, name, default=None, untrusted=False):
140 """Return a list of comma/space separated strings"""
140 """Return a list of comma/space separated strings"""
141 result = self.config(section, name, untrusted=untrusted)
141 result = self.config(section, name, untrusted=untrusted)
142 if result is None:
142 if result is None:
143 result = default or []
143 result = default or []
144 if isinstance(result, basestring):
144 if isinstance(result, basestring):
145 result = result.replace(",", " ").split()
145 result = result.replace(",", " ").split()
146 return result
146 return result
147
147
148 def has_section(self, section, untrusted=False):
148 def has_section(self, section, untrusted=False):
149 '''tell whether section exists in config.'''
149 '''tell whether section exists in config.'''
150 return section in self._data(untrusted)
150 return section in self._data(untrusted)
151
151
152 def configitems(self, section, untrusted=False):
152 def configitems(self, section, untrusted=False):
153 items = self._data(untrusted).items(section)
153 items = self._data(untrusted).items(section)
154 if self.debugflag and not untrusted and self._reportuntrusted:
154 if self.debugflag and not untrusted and self._reportuntrusted:
155 for k, v in self._ucfg.items(section):
155 for k, v in self._ucfg.items(section):
156 if self._tcfg.get(section, k) != v:
156 if self._tcfg.get(section, k) != v:
157 self.debug(_("ignoring untrusted configuration option "
157 self.debug(_("ignoring untrusted configuration option "
158 "%s.%s = %s\n") % (section, k, v))
158 "%s.%s = %s\n") % (section, k, v))
159 return items
159 return items
160
160
161 def walkconfig(self, untrusted=False):
161 def walkconfig(self, untrusted=False):
162 cfg = self._data(untrusted)
162 cfg = self._data(untrusted)
163 for section in cfg.sections():
163 for section in cfg.sections():
164 for name, value in self.configitems(section, untrusted):
164 for name, value in self.configitems(section, untrusted):
165 yield section, name, str(value).replace('\n', '\\n')
165 yield section, name, str(value).replace('\n', '\\n')
166
166
167 def username(self):
167 def username(self):
168 """Return default username to be used in commits.
168 """Return default username to be used in commits.
169
169
170 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
170 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
171 and stop searching if one of these is set.
171 and stop searching if one of these is set.
172 If not found and ui.askusername is True, ask the user, else use
172 If not found and ui.askusername is True, ask the user, else use
173 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
173 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
174 """
174 """
175 user = os.environ.get("HGUSER")
175 user = os.environ.get("HGUSER")
176 if user is None:
176 if user is None:
177 user = self.config("ui", "username")
177 user = self.config("ui", "username")
178 if user is None:
178 if user is None:
179 user = os.environ.get("EMAIL")
179 user = os.environ.get("EMAIL")
180 if user is None and self.configbool("ui", "askusername"):
180 if user is None and self.configbool("ui", "askusername"):
181 user = self.prompt(_("enter a commit username:"), default=None)
181 user = self.prompt(_("enter a commit username:"), default=None)
182 if user is None:
182 if user is None:
183 try:
183 try:
184 user = '%s@%s' % (util.getuser(), socket.getfqdn())
184 user = '%s@%s' % (util.getuser(), socket.getfqdn())
185 self.warn(_("No username found, using '%s' instead\n") % user)
185 self.warn(_("No username found, using '%s' instead\n") % user)
186 except KeyError:
186 except KeyError:
187 pass
187 pass
188 if not user:
188 if not user:
189 raise util.Abort(_("Please specify a username."))
189 raise util.Abort(_("Please specify a username."))
190 if "\n" in user:
190 if "\n" in user:
191 raise util.Abort(_("username %s contains a newline\n") % repr(user))
191 raise util.Abort(_("username %s contains a newline\n") % repr(user))
192 return user
192 return user
193
193
194 def shortuser(self, user):
194 def shortuser(self, user):
195 """Return a short representation of a user name or email address."""
195 """Return a short representation of a user name or email address."""
196 if not self.verbose: user = util.shortuser(user)
196 if not self.verbose: user = util.shortuser(user)
197 return user
197 return user
198
198
199 def _path(self, loc):
199 def _path(self, loc):
200 p = self.config('paths', loc)
200 p = self.config('paths', loc)
201 if p and '%%' in p:
201 if p:
202 self.warn("(deprecated '%%' in path %s=%s from %s)\n" %
202 if '%%' in p:
203 (loc, p, self.configsource('paths', loc)))
203 self.warn("(deprecated '%%' in path %s=%s from %s)\n" %
204 p = p.replace('%%', '%')
204 (loc, p, self.configsource('paths', loc)))
205 p = p.replace('%%', '%')
206 p = util.expandpath(p)
205 return p
207 return p
206
208
207 def expandpath(self, loc, default=None):
209 def expandpath(self, loc, default=None):
208 """Return repository location relative to cwd or from [paths]"""
210 """Return repository location relative to cwd or from [paths]"""
209 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
211 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
210 return loc
212 return loc
211
213
212 path = self._path(loc)
214 path = self._path(loc)
213 if not path and default is not None:
215 if not path and default is not None:
214 path = self._path(default)
216 path = self._path(default)
215 return path or loc
217 return path or loc
216
218
217 def pushbuffer(self):
219 def pushbuffer(self):
218 self._buffers.append([])
220 self._buffers.append([])
219
221
220 def popbuffer(self):
222 def popbuffer(self):
221 return "".join(self._buffers.pop())
223 return "".join(self._buffers.pop())
222
224
223 def write(self, *args):
225 def write(self, *args):
224 if self._buffers:
226 if self._buffers:
225 self._buffers[-1].extend([str(a) for a in args])
227 self._buffers[-1].extend([str(a) for a in args])
226 else:
228 else:
227 for a in args:
229 for a in args:
228 sys.stdout.write(str(a))
230 sys.stdout.write(str(a))
229
231
230 def write_err(self, *args):
232 def write_err(self, *args):
231 try:
233 try:
232 if not sys.stdout.closed: sys.stdout.flush()
234 if not sys.stdout.closed: sys.stdout.flush()
233 for a in args:
235 for a in args:
234 sys.stderr.write(str(a))
236 sys.stderr.write(str(a))
235 # stderr may be buffered under win32 when redirected to files,
237 # stderr may be buffered under win32 when redirected to files,
236 # including stdout.
238 # including stdout.
237 if not sys.stderr.closed: sys.stderr.flush()
239 if not sys.stderr.closed: sys.stderr.flush()
238 except IOError, inst:
240 except IOError, inst:
239 if inst.errno != errno.EPIPE:
241 if inst.errno != errno.EPIPE:
240 raise
242 raise
241
243
242 def flush(self):
244 def flush(self):
243 try: sys.stdout.flush()
245 try: sys.stdout.flush()
244 except: pass
246 except: pass
245 try: sys.stderr.flush()
247 try: sys.stderr.flush()
246 except: pass
248 except: pass
247
249
248 def interactive(self):
250 def interactive(self):
249 i = self.configbool("ui", "interactive", None)
251 i = self.configbool("ui", "interactive", None)
250 if i is None:
252 if i is None:
251 return sys.stdin.isatty()
253 return sys.stdin.isatty()
252 return i
254 return i
253
255
254 def _readline(self, prompt=''):
256 def _readline(self, prompt=''):
255 if sys.stdin.isatty():
257 if sys.stdin.isatty():
256 try:
258 try:
257 # magically add command line editing support, where
259 # magically add command line editing support, where
258 # available
260 # available
259 import readline
261 import readline
260 # force demandimport to really load the module
262 # force demandimport to really load the module
261 readline.read_history_file
263 readline.read_history_file
262 # windows sometimes raises something other than ImportError
264 # windows sometimes raises something other than ImportError
263 except Exception:
265 except Exception:
264 pass
266 pass
265 line = raw_input(prompt)
267 line = raw_input(prompt)
266 # When stdin is in binary mode on Windows, it can cause
268 # When stdin is in binary mode on Windows, it can cause
267 # raw_input() to emit an extra trailing carriage return
269 # raw_input() to emit an extra trailing carriage return
268 if os.linesep == '\r\n' and line and line[-1] == '\r':
270 if os.linesep == '\r\n' and line and line[-1] == '\r':
269 line = line[:-1]
271 line = line[:-1]
270 return line
272 return line
271
273
272 def prompt(self, msg, default="y"):
274 def prompt(self, msg, default="y"):
273 """Prompt user with msg, read response.
275 """Prompt user with msg, read response.
274 If ui is not interactive, the default is returned.
276 If ui is not interactive, the default is returned.
275 """
277 """
276 if not self.interactive():
278 if not self.interactive():
277 self.write(msg, ' ', default, "\n")
279 self.write(msg, ' ', default, "\n")
278 return default
280 return default
279 try:
281 try:
280 r = self._readline(msg + ' ')
282 r = self._readline(msg + ' ')
281 if not r:
283 if not r:
282 return default
284 return default
283 return r
285 return r
284 except EOFError:
286 except EOFError:
285 raise util.Abort(_('response expected'))
287 raise util.Abort(_('response expected'))
286
288
287 def promptchoice(self, msg, choices, default=0):
289 def promptchoice(self, msg, choices, default=0):
288 """Prompt user with msg, read response, and ensure it matches
290 """Prompt user with msg, read response, and ensure it matches
289 one of the provided choices. The index of the choice is returned.
291 one of the provided choices. The index of the choice is returned.
290 choices is a sequence of acceptable responses with the format:
292 choices is a sequence of acceptable responses with the format:
291 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
293 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
292 If ui is not interactive, the default is returned.
294 If ui is not interactive, the default is returned.
293 """
295 """
294 resps = [s[s.index('&')+1].lower() for s in choices]
296 resps = [s[s.index('&')+1].lower() for s in choices]
295 while True:
297 while True:
296 r = self.prompt(msg, resps[default])
298 r = self.prompt(msg, resps[default])
297 if r.lower() in resps:
299 if r.lower() in resps:
298 return resps.index(r.lower())
300 return resps.index(r.lower())
299 self.write(_("unrecognized response\n"))
301 self.write(_("unrecognized response\n"))
300
302
301
303
302 def getpass(self, prompt=None, default=None):
304 def getpass(self, prompt=None, default=None):
303 if not self.interactive(): return default
305 if not self.interactive(): return default
304 try:
306 try:
305 return getpass.getpass(prompt or _('password: '))
307 return getpass.getpass(prompt or _('password: '))
306 except EOFError:
308 except EOFError:
307 raise util.Abort(_('response expected'))
309 raise util.Abort(_('response expected'))
308 def status(self, *msg):
310 def status(self, *msg):
309 if not self.quiet: self.write(*msg)
311 if not self.quiet: self.write(*msg)
310 def warn(self, *msg):
312 def warn(self, *msg):
311 self.write_err(*msg)
313 self.write_err(*msg)
312 def note(self, *msg):
314 def note(self, *msg):
313 if self.verbose: self.write(*msg)
315 if self.verbose: self.write(*msg)
314 def debug(self, *msg):
316 def debug(self, *msg):
315 if self.debugflag: self.write(*msg)
317 if self.debugflag: self.write(*msg)
316 def edit(self, text, user):
318 def edit(self, text, user):
317 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
319 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
318 text=True)
320 text=True)
319 try:
321 try:
320 f = os.fdopen(fd, "w")
322 f = os.fdopen(fd, "w")
321 f.write(text)
323 f.write(text)
322 f.close()
324 f.close()
323
325
324 editor = self.geteditor()
326 editor = self.geteditor()
325
327
326 util.system("%s \"%s\"" % (editor, name),
328 util.system("%s \"%s\"" % (editor, name),
327 environ={'HGUSER': user},
329 environ={'HGUSER': user},
328 onerr=util.Abort, errprefix=_("edit failed"))
330 onerr=util.Abort, errprefix=_("edit failed"))
329
331
330 f = open(name)
332 f = open(name)
331 t = f.read()
333 t = f.read()
332 f.close()
334 f.close()
333 finally:
335 finally:
334 os.unlink(name)
336 os.unlink(name)
335
337
336 return t
338 return t
337
339
338 def traceback(self):
340 def traceback(self):
339 '''print exception traceback if traceback printing enabled.
341 '''print exception traceback if traceback printing enabled.
340 only to call in exception handler. returns true if traceback
342 only to call in exception handler. returns true if traceback
341 printed.'''
343 printed.'''
342 if self._traceback:
344 if self._traceback:
343 traceback.print_exc()
345 traceback.print_exc()
344 return self._traceback
346 return self._traceback
345
347
346 def geteditor(self):
348 def geteditor(self):
347 '''return editor to use'''
349 '''return editor to use'''
348 return (os.environ.get("HGEDITOR") or
350 return (os.environ.get("HGEDITOR") or
349 self.config("ui", "editor") or
351 self.config("ui", "editor") or
350 os.environ.get("VISUAL") or
352 os.environ.get("VISUAL") or
351 os.environ.get("EDITOR", "vi"))
353 os.environ.get("EDITOR", "vi"))
352
354
353 def progress(self, topic, pos, item="", unit="", total=None):
355 def progress(self, topic, pos, item="", unit="", total=None):
354 '''show a progress message
356 '''show a progress message
355
357
356 With stock hg, this is simply a debug message that is hidden
358 With stock hg, this is simply a debug message that is hidden
357 by default, but with extensions or GUI tools it may be
359 by default, but with extensions or GUI tools it may be
358 visible. 'topic' is the current operation, 'item' is a
360 visible. 'topic' is the current operation, 'item' is a
359 non-numeric marker of the current position (ie the currently
361 non-numeric marker of the current position (ie the currently
360 in-process file), 'pos' is the current numeric position (ie
362 in-process file), 'pos' is the current numeric position (ie
361 revision, bytes, etc.), unit is a corresponding unit label,
363 revision, bytes, etc.), unit is a corresponding unit label,
362 and total is the highest expected pos.
364 and total is the highest expected pos.
363
365
364 Multiple nested topics may be active at a time. All topics
366 Multiple nested topics may be active at a time. All topics
365 should be marked closed by setting pos to None at termination.
367 should be marked closed by setting pos to None at termination.
366 '''
368 '''
367
369
368 if pos == None or not self.debugflag:
370 if pos == None or not self.debugflag:
369 return
371 return
370
372
371 if unit:
373 if unit:
372 unit = ' ' + unit
374 unit = ' ' + unit
373 if item:
375 if item:
374 item = ' ' + item
376 item = ' ' + item
375
377
376 if total:
378 if total:
377 pct = 100.0 * pos / total
379 pct = 100.0 * pos / total
378 ui.debug('%s:%s %s/%s%s (%4.2g%%)\n'
380 ui.debug('%s:%s %s/%s%s (%4.2g%%)\n'
379 % (topic, item, pos, total, unit, pct))
381 % (topic, item, pos, total, unit, pct))
380 else:
382 else:
381 ui.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
383 ui.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
@@ -1,1252 +1,1256 b''
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2, incorporated herein by reference.
8 # GNU General Public License version 2, incorporated herein by reference.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding
18 import cStringIO, errno, re, shutil, sys, tempfile, traceback
18 import cStringIO, errno, re, shutil, sys, tempfile, traceback
19 import os, stat, time, calendar, textwrap
19 import os, stat, time, calendar, textwrap
20 import imp
20 import imp
21
21
22 # Python compatibility
22 # Python compatibility
23
23
24 def sha1(s):
24 def sha1(s):
25 return _fastsha1(s)
25 return _fastsha1(s)
26
26
27 def _fastsha1(s):
27 def _fastsha1(s):
28 # This function will import sha1 from hashlib or sha (whichever is
28 # This function will import sha1 from hashlib or sha (whichever is
29 # available) and overwrite itself with it on the first call.
29 # available) and overwrite itself with it on the first call.
30 # Subsequent calls will go directly to the imported function.
30 # Subsequent calls will go directly to the imported function.
31 try:
31 try:
32 from hashlib import sha1 as _sha1
32 from hashlib import sha1 as _sha1
33 except ImportError:
33 except ImportError:
34 from sha import sha as _sha1
34 from sha import sha as _sha1
35 global _fastsha1, sha1
35 global _fastsha1, sha1
36 _fastsha1 = sha1 = _sha1
36 _fastsha1 = sha1 = _sha1
37 return _sha1(s)
37 return _sha1(s)
38
38
39 import subprocess
39 import subprocess
40 closefds = os.name == 'posix'
40 closefds = os.name == 'posix'
41 def popen2(cmd):
41 def popen2(cmd):
42 # Setting bufsize to -1 lets the system decide the buffer size.
42 # Setting bufsize to -1 lets the system decide the buffer size.
43 # The default for bufsize is 0, meaning unbuffered. This leads to
43 # The default for bufsize is 0, meaning unbuffered. This leads to
44 # poor performance on Mac OS X: http://bugs.python.org/issue4194
44 # poor performance on Mac OS X: http://bugs.python.org/issue4194
45 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
45 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
46 close_fds=closefds,
46 close_fds=closefds,
47 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
47 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
48 return p.stdin, p.stdout
48 return p.stdin, p.stdout
49 def popen3(cmd):
49 def popen3(cmd):
50 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
50 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
51 close_fds=closefds,
51 close_fds=closefds,
52 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
52 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
53 stderr=subprocess.PIPE)
53 stderr=subprocess.PIPE)
54 return p.stdin, p.stdout, p.stderr
54 return p.stdin, p.stdout, p.stderr
55
55
56 def version():
56 def version():
57 """Return version information if available."""
57 """Return version information if available."""
58 try:
58 try:
59 import __version__
59 import __version__
60 return __version__.version
60 return __version__.version
61 except ImportError:
61 except ImportError:
62 return 'unknown'
62 return 'unknown'
63
63
64 # used by parsedate
64 # used by parsedate
65 defaultdateformats = (
65 defaultdateformats = (
66 '%Y-%m-%d %H:%M:%S',
66 '%Y-%m-%d %H:%M:%S',
67 '%Y-%m-%d %I:%M:%S%p',
67 '%Y-%m-%d %I:%M:%S%p',
68 '%Y-%m-%d %H:%M',
68 '%Y-%m-%d %H:%M',
69 '%Y-%m-%d %I:%M%p',
69 '%Y-%m-%d %I:%M%p',
70 '%Y-%m-%d',
70 '%Y-%m-%d',
71 '%m-%d',
71 '%m-%d',
72 '%m/%d',
72 '%m/%d',
73 '%m/%d/%y',
73 '%m/%d/%y',
74 '%m/%d/%Y',
74 '%m/%d/%Y',
75 '%a %b %d %H:%M:%S %Y',
75 '%a %b %d %H:%M:%S %Y',
76 '%a %b %d %I:%M:%S%p %Y',
76 '%a %b %d %I:%M:%S%p %Y',
77 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
77 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
78 '%b %d %H:%M:%S %Y',
78 '%b %d %H:%M:%S %Y',
79 '%b %d %I:%M:%S%p %Y',
79 '%b %d %I:%M:%S%p %Y',
80 '%b %d %H:%M:%S',
80 '%b %d %H:%M:%S',
81 '%b %d %I:%M:%S%p',
81 '%b %d %I:%M:%S%p',
82 '%b %d %H:%M',
82 '%b %d %H:%M',
83 '%b %d %I:%M%p',
83 '%b %d %I:%M%p',
84 '%b %d %Y',
84 '%b %d %Y',
85 '%b %d',
85 '%b %d',
86 '%H:%M:%S',
86 '%H:%M:%S',
87 '%I:%M:%S%p',
87 '%I:%M:%S%p',
88 '%H:%M',
88 '%H:%M',
89 '%I:%M%p',
89 '%I:%M%p',
90 )
90 )
91
91
92 extendeddateformats = defaultdateformats + (
92 extendeddateformats = defaultdateformats + (
93 "%Y",
93 "%Y",
94 "%Y-%m",
94 "%Y-%m",
95 "%b",
95 "%b",
96 "%b %Y",
96 "%b %Y",
97 )
97 )
98
98
99 def cachefunc(func):
99 def cachefunc(func):
100 '''cache the result of function calls'''
100 '''cache the result of function calls'''
101 # XXX doesn't handle keywords args
101 # XXX doesn't handle keywords args
102 cache = {}
102 cache = {}
103 if func.func_code.co_argcount == 1:
103 if func.func_code.co_argcount == 1:
104 # we gain a small amount of time because
104 # we gain a small amount of time because
105 # we don't need to pack/unpack the list
105 # we don't need to pack/unpack the list
106 def f(arg):
106 def f(arg):
107 if arg not in cache:
107 if arg not in cache:
108 cache[arg] = func(arg)
108 cache[arg] = func(arg)
109 return cache[arg]
109 return cache[arg]
110 else:
110 else:
111 def f(*args):
111 def f(*args):
112 if args not in cache:
112 if args not in cache:
113 cache[args] = func(*args)
113 cache[args] = func(*args)
114 return cache[args]
114 return cache[args]
115
115
116 return f
116 return f
117
117
118 def lrucachefunc(func):
118 def lrucachefunc(func):
119 '''cache most recent results of function calls'''
119 '''cache most recent results of function calls'''
120 cache = {}
120 cache = {}
121 order = []
121 order = []
122 if func.func_code.co_argcount == 1:
122 if func.func_code.co_argcount == 1:
123 def f(arg):
123 def f(arg):
124 if arg not in cache:
124 if arg not in cache:
125 if len(cache) > 20:
125 if len(cache) > 20:
126 del cache[order.pop(0)]
126 del cache[order.pop(0)]
127 cache[arg] = func(arg)
127 cache[arg] = func(arg)
128 else:
128 else:
129 order.remove(arg)
129 order.remove(arg)
130 order.append(arg)
130 order.append(arg)
131 return cache[arg]
131 return cache[arg]
132 else:
132 else:
133 def f(*args):
133 def f(*args):
134 if args not in cache:
134 if args not in cache:
135 if len(cache) > 20:
135 if len(cache) > 20:
136 del cache[order.pop(0)]
136 del cache[order.pop(0)]
137 cache[args] = func(*args)
137 cache[args] = func(*args)
138 else:
138 else:
139 order.remove(args)
139 order.remove(args)
140 order.append(args)
140 order.append(args)
141 return cache[args]
141 return cache[args]
142
142
143 return f
143 return f
144
144
145 class propertycache(object):
145 class propertycache(object):
146 def __init__(self, func):
146 def __init__(self, func):
147 self.func = func
147 self.func = func
148 self.name = func.__name__
148 self.name = func.__name__
149 def __get__(self, obj, type=None):
149 def __get__(self, obj, type=None):
150 result = self.func(obj)
150 result = self.func(obj)
151 setattr(obj, self.name, result)
151 setattr(obj, self.name, result)
152 return result
152 return result
153
153
154 def pipefilter(s, cmd):
154 def pipefilter(s, cmd):
155 '''filter string S through command CMD, returning its output'''
155 '''filter string S through command CMD, returning its output'''
156 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
156 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
157 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
157 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
158 pout, perr = p.communicate(s)
158 pout, perr = p.communicate(s)
159 return pout
159 return pout
160
160
161 def tempfilter(s, cmd):
161 def tempfilter(s, cmd):
162 '''filter string S through a pair of temporary files with CMD.
162 '''filter string S through a pair of temporary files with CMD.
163 CMD is used as a template to create the real command to be run,
163 CMD is used as a template to create the real command to be run,
164 with the strings INFILE and OUTFILE replaced by the real names of
164 with the strings INFILE and OUTFILE replaced by the real names of
165 the temporary files generated.'''
165 the temporary files generated.'''
166 inname, outname = None, None
166 inname, outname = None, None
167 try:
167 try:
168 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
168 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
169 fp = os.fdopen(infd, 'wb')
169 fp = os.fdopen(infd, 'wb')
170 fp.write(s)
170 fp.write(s)
171 fp.close()
171 fp.close()
172 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
172 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
173 os.close(outfd)
173 os.close(outfd)
174 cmd = cmd.replace('INFILE', inname)
174 cmd = cmd.replace('INFILE', inname)
175 cmd = cmd.replace('OUTFILE', outname)
175 cmd = cmd.replace('OUTFILE', outname)
176 code = os.system(cmd)
176 code = os.system(cmd)
177 if sys.platform == 'OpenVMS' and code & 1:
177 if sys.platform == 'OpenVMS' and code & 1:
178 code = 0
178 code = 0
179 if code: raise Abort(_("command '%s' failed: %s") %
179 if code: raise Abort(_("command '%s' failed: %s") %
180 (cmd, explain_exit(code)))
180 (cmd, explain_exit(code)))
181 return open(outname, 'rb').read()
181 return open(outname, 'rb').read()
182 finally:
182 finally:
183 try:
183 try:
184 if inname: os.unlink(inname)
184 if inname: os.unlink(inname)
185 except: pass
185 except: pass
186 try:
186 try:
187 if outname: os.unlink(outname)
187 if outname: os.unlink(outname)
188 except: pass
188 except: pass
189
189
190 filtertable = {
190 filtertable = {
191 'tempfile:': tempfilter,
191 'tempfile:': tempfilter,
192 'pipe:': pipefilter,
192 'pipe:': pipefilter,
193 }
193 }
194
194
195 def filter(s, cmd):
195 def filter(s, cmd):
196 "filter a string through a command that transforms its input to its output"
196 "filter a string through a command that transforms its input to its output"
197 for name, fn in filtertable.iteritems():
197 for name, fn in filtertable.iteritems():
198 if cmd.startswith(name):
198 if cmd.startswith(name):
199 return fn(s, cmd[len(name):].lstrip())
199 return fn(s, cmd[len(name):].lstrip())
200 return pipefilter(s, cmd)
200 return pipefilter(s, cmd)
201
201
202 def binary(s):
202 def binary(s):
203 """return true if a string is binary data"""
203 """return true if a string is binary data"""
204 return bool(s and '\0' in s)
204 return bool(s and '\0' in s)
205
205
206 def increasingchunks(source, min=1024, max=65536):
206 def increasingchunks(source, min=1024, max=65536):
207 '''return no less than min bytes per chunk while data remains,
207 '''return no less than min bytes per chunk while data remains,
208 doubling min after each chunk until it reaches max'''
208 doubling min after each chunk until it reaches max'''
209 def log2(x):
209 def log2(x):
210 if not x:
210 if not x:
211 return 0
211 return 0
212 i = 0
212 i = 0
213 while x:
213 while x:
214 x >>= 1
214 x >>= 1
215 i += 1
215 i += 1
216 return i - 1
216 return i - 1
217
217
218 buf = []
218 buf = []
219 blen = 0
219 blen = 0
220 for chunk in source:
220 for chunk in source:
221 buf.append(chunk)
221 buf.append(chunk)
222 blen += len(chunk)
222 blen += len(chunk)
223 if blen >= min:
223 if blen >= min:
224 if min < max:
224 if min < max:
225 min = min << 1
225 min = min << 1
226 nmin = 1 << log2(blen)
226 nmin = 1 << log2(blen)
227 if nmin > min:
227 if nmin > min:
228 min = nmin
228 min = nmin
229 if min > max:
229 if min > max:
230 min = max
230 min = max
231 yield ''.join(buf)
231 yield ''.join(buf)
232 blen = 0
232 blen = 0
233 buf = []
233 buf = []
234 if buf:
234 if buf:
235 yield ''.join(buf)
235 yield ''.join(buf)
236
236
237 Abort = error.Abort
237 Abort = error.Abort
238
238
239 def always(fn): return True
239 def always(fn): return True
240 def never(fn): return False
240 def never(fn): return False
241
241
242 def pathto(root, n1, n2):
242 def pathto(root, n1, n2):
243 '''return the relative path from one place to another.
243 '''return the relative path from one place to another.
244 root should use os.sep to separate directories
244 root should use os.sep to separate directories
245 n1 should use os.sep to separate directories
245 n1 should use os.sep to separate directories
246 n2 should use "/" to separate directories
246 n2 should use "/" to separate directories
247 returns an os.sep-separated path.
247 returns an os.sep-separated path.
248
248
249 If n1 is a relative path, it's assumed it's
249 If n1 is a relative path, it's assumed it's
250 relative to root.
250 relative to root.
251 n2 should always be relative to root.
251 n2 should always be relative to root.
252 '''
252 '''
253 if not n1: return localpath(n2)
253 if not n1: return localpath(n2)
254 if os.path.isabs(n1):
254 if os.path.isabs(n1):
255 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
255 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
256 return os.path.join(root, localpath(n2))
256 return os.path.join(root, localpath(n2))
257 n2 = '/'.join((pconvert(root), n2))
257 n2 = '/'.join((pconvert(root), n2))
258 a, b = splitpath(n1), n2.split('/')
258 a, b = splitpath(n1), n2.split('/')
259 a.reverse()
259 a.reverse()
260 b.reverse()
260 b.reverse()
261 while a and b and a[-1] == b[-1]:
261 while a and b and a[-1] == b[-1]:
262 a.pop()
262 a.pop()
263 b.pop()
263 b.pop()
264 b.reverse()
264 b.reverse()
265 return os.sep.join((['..'] * len(a)) + b) or '.'
265 return os.sep.join((['..'] * len(a)) + b) or '.'
266
266
267 def canonpath(root, cwd, myname):
267 def canonpath(root, cwd, myname):
268 """return the canonical path of myname, given cwd and root"""
268 """return the canonical path of myname, given cwd and root"""
269 if endswithsep(root):
269 if endswithsep(root):
270 rootsep = root
270 rootsep = root
271 else:
271 else:
272 rootsep = root + os.sep
272 rootsep = root + os.sep
273 name = myname
273 name = myname
274 if not os.path.isabs(name):
274 if not os.path.isabs(name):
275 name = os.path.join(root, cwd, name)
275 name = os.path.join(root, cwd, name)
276 name = os.path.normpath(name)
276 name = os.path.normpath(name)
277 audit_path = path_auditor(root)
277 audit_path = path_auditor(root)
278 if name != rootsep and name.startswith(rootsep):
278 if name != rootsep and name.startswith(rootsep):
279 name = name[len(rootsep):]
279 name = name[len(rootsep):]
280 audit_path(name)
280 audit_path(name)
281 return pconvert(name)
281 return pconvert(name)
282 elif name == root:
282 elif name == root:
283 return ''
283 return ''
284 else:
284 else:
285 # Determine whether `name' is in the hierarchy at or beneath `root',
285 # Determine whether `name' is in the hierarchy at or beneath `root',
286 # by iterating name=dirname(name) until that causes no change (can't
286 # by iterating name=dirname(name) until that causes no change (can't
287 # check name == '/', because that doesn't work on windows). For each
287 # check name == '/', because that doesn't work on windows). For each
288 # `name', compare dev/inode numbers. If they match, the list `rel'
288 # `name', compare dev/inode numbers. If they match, the list `rel'
289 # holds the reversed list of components making up the relative file
289 # holds the reversed list of components making up the relative file
290 # name we want.
290 # name we want.
291 root_st = os.stat(root)
291 root_st = os.stat(root)
292 rel = []
292 rel = []
293 while True:
293 while True:
294 try:
294 try:
295 name_st = os.stat(name)
295 name_st = os.stat(name)
296 except OSError:
296 except OSError:
297 break
297 break
298 if samestat(name_st, root_st):
298 if samestat(name_st, root_st):
299 if not rel:
299 if not rel:
300 # name was actually the same as root (maybe a symlink)
300 # name was actually the same as root (maybe a symlink)
301 return ''
301 return ''
302 rel.reverse()
302 rel.reverse()
303 name = os.path.join(*rel)
303 name = os.path.join(*rel)
304 audit_path(name)
304 audit_path(name)
305 return pconvert(name)
305 return pconvert(name)
306 dirname, basename = os.path.split(name)
306 dirname, basename = os.path.split(name)
307 rel.append(basename)
307 rel.append(basename)
308 if dirname == name:
308 if dirname == name:
309 break
309 break
310 name = dirname
310 name = dirname
311
311
312 raise Abort('%s not under root' % myname)
312 raise Abort('%s not under root' % myname)
313
313
314 _hgexecutable = None
314 _hgexecutable = None
315
315
316 def main_is_frozen():
316 def main_is_frozen():
317 """return True if we are a frozen executable.
317 """return True if we are a frozen executable.
318
318
319 The code supports py2exe (most common, Windows only) and tools/freeze
319 The code supports py2exe (most common, Windows only) and tools/freeze
320 (portable, not much used).
320 (portable, not much used).
321 """
321 """
322 return (hasattr(sys, "frozen") or # new py2exe
322 return (hasattr(sys, "frozen") or # new py2exe
323 hasattr(sys, "importers") or # old py2exe
323 hasattr(sys, "importers") or # old py2exe
324 imp.is_frozen("__main__")) # tools/freeze
324 imp.is_frozen("__main__")) # tools/freeze
325
325
326 def hgexecutable():
326 def hgexecutable():
327 """return location of the 'hg' executable.
327 """return location of the 'hg' executable.
328
328
329 Defaults to $HG or 'hg' in the search path.
329 Defaults to $HG or 'hg' in the search path.
330 """
330 """
331 if _hgexecutable is None:
331 if _hgexecutable is None:
332 hg = os.environ.get('HG')
332 hg = os.environ.get('HG')
333 if hg:
333 if hg:
334 set_hgexecutable(hg)
334 set_hgexecutable(hg)
335 elif main_is_frozen():
335 elif main_is_frozen():
336 set_hgexecutable(sys.executable)
336 set_hgexecutable(sys.executable)
337 else:
337 else:
338 set_hgexecutable(find_exe('hg') or 'hg')
338 set_hgexecutable(find_exe('hg') or 'hg')
339 return _hgexecutable
339 return _hgexecutable
340
340
341 def set_hgexecutable(path):
341 def set_hgexecutable(path):
342 """set location of the 'hg' executable"""
342 """set location of the 'hg' executable"""
343 global _hgexecutable
343 global _hgexecutable
344 _hgexecutable = path
344 _hgexecutable = path
345
345
346 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
346 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
347 '''enhanced shell command execution.
347 '''enhanced shell command execution.
348 run with environment maybe modified, maybe in different dir.
348 run with environment maybe modified, maybe in different dir.
349
349
350 if command fails and onerr is None, return status. if ui object,
350 if command fails and onerr is None, return status. if ui object,
351 print error message and return status, else raise onerr object as
351 print error message and return status, else raise onerr object as
352 exception.'''
352 exception.'''
353 def py2shell(val):
353 def py2shell(val):
354 'convert python object into string that is useful to shell'
354 'convert python object into string that is useful to shell'
355 if val is None or val is False:
355 if val is None or val is False:
356 return '0'
356 return '0'
357 if val is True:
357 if val is True:
358 return '1'
358 return '1'
359 return str(val)
359 return str(val)
360 origcmd = cmd
360 origcmd = cmd
361 if os.name == 'nt':
361 if os.name == 'nt':
362 cmd = '"%s"' % cmd
362 cmd = '"%s"' % cmd
363 env = dict(os.environ)
363 env = dict(os.environ)
364 env.update((k, py2shell(v)) for k, v in environ.iteritems())
364 env.update((k, py2shell(v)) for k, v in environ.iteritems())
365 env['HG'] = hgexecutable()
365 env['HG'] = hgexecutable()
366 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
366 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
367 env=env, cwd=cwd)
367 env=env, cwd=cwd)
368 if sys.platform == 'OpenVMS' and rc & 1:
368 if sys.platform == 'OpenVMS' and rc & 1:
369 rc = 0
369 rc = 0
370 if rc and onerr:
370 if rc and onerr:
371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
372 explain_exit(rc)[0])
372 explain_exit(rc)[0])
373 if errprefix:
373 if errprefix:
374 errmsg = '%s: %s' % (errprefix, errmsg)
374 errmsg = '%s: %s' % (errprefix, errmsg)
375 try:
375 try:
376 onerr.warn(errmsg + '\n')
376 onerr.warn(errmsg + '\n')
377 except AttributeError:
377 except AttributeError:
378 raise onerr(errmsg)
378 raise onerr(errmsg)
379 return rc
379 return rc
380
380
381 def checksignature(func):
381 def checksignature(func):
382 '''wrap a function with code to check for calling errors'''
382 '''wrap a function with code to check for calling errors'''
383 def check(*args, **kwargs):
383 def check(*args, **kwargs):
384 try:
384 try:
385 return func(*args, **kwargs)
385 return func(*args, **kwargs)
386 except TypeError:
386 except TypeError:
387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
388 raise error.SignatureError
388 raise error.SignatureError
389 raise
389 raise
390
390
391 return check
391 return check
392
392
393 # os.path.lexists is not available on python2.3
393 # os.path.lexists is not available on python2.3
394 def lexists(filename):
394 def lexists(filename):
395 "test whether a file with this name exists. does not follow symlinks"
395 "test whether a file with this name exists. does not follow symlinks"
396 try:
396 try:
397 os.lstat(filename)
397 os.lstat(filename)
398 except:
398 except:
399 return False
399 return False
400 return True
400 return True
401
401
402 def unlink(f):
402 def unlink(f):
403 """unlink and remove the directory if it is empty"""
403 """unlink and remove the directory if it is empty"""
404 os.unlink(f)
404 os.unlink(f)
405 # try removing directories that might now be empty
405 # try removing directories that might now be empty
406 try:
406 try:
407 os.removedirs(os.path.dirname(f))
407 os.removedirs(os.path.dirname(f))
408 except OSError:
408 except OSError:
409 pass
409 pass
410
410
411 def copyfile(src, dest):
411 def copyfile(src, dest):
412 "copy a file, preserving mode and atime/mtime"
412 "copy a file, preserving mode and atime/mtime"
413 if os.path.islink(src):
413 if os.path.islink(src):
414 try:
414 try:
415 os.unlink(dest)
415 os.unlink(dest)
416 except:
416 except:
417 pass
417 pass
418 os.symlink(os.readlink(src), dest)
418 os.symlink(os.readlink(src), dest)
419 else:
419 else:
420 try:
420 try:
421 shutil.copyfile(src, dest)
421 shutil.copyfile(src, dest)
422 shutil.copystat(src, dest)
422 shutil.copystat(src, dest)
423 except shutil.Error, inst:
423 except shutil.Error, inst:
424 raise Abort(str(inst))
424 raise Abort(str(inst))
425
425
426 def copyfiles(src, dst, hardlink=None):
426 def copyfiles(src, dst, hardlink=None):
427 """Copy a directory tree using hardlinks if possible"""
427 """Copy a directory tree using hardlinks if possible"""
428
428
429 if hardlink is None:
429 if hardlink is None:
430 hardlink = (os.stat(src).st_dev ==
430 hardlink = (os.stat(src).st_dev ==
431 os.stat(os.path.dirname(dst)).st_dev)
431 os.stat(os.path.dirname(dst)).st_dev)
432
432
433 if os.path.isdir(src):
433 if os.path.isdir(src):
434 os.mkdir(dst)
434 os.mkdir(dst)
435 for name, kind in osutil.listdir(src):
435 for name, kind in osutil.listdir(src):
436 srcname = os.path.join(src, name)
436 srcname = os.path.join(src, name)
437 dstname = os.path.join(dst, name)
437 dstname = os.path.join(dst, name)
438 copyfiles(srcname, dstname, hardlink)
438 copyfiles(srcname, dstname, hardlink)
439 else:
439 else:
440 if hardlink:
440 if hardlink:
441 try:
441 try:
442 os_link(src, dst)
442 os_link(src, dst)
443 except (IOError, OSError):
443 except (IOError, OSError):
444 hardlink = False
444 hardlink = False
445 shutil.copy(src, dst)
445 shutil.copy(src, dst)
446 else:
446 else:
447 shutil.copy(src, dst)
447 shutil.copy(src, dst)
448
448
449 class path_auditor(object):
449 class path_auditor(object):
450 '''ensure that a filesystem path contains no banned components.
450 '''ensure that a filesystem path contains no banned components.
451 the following properties of a path are checked:
451 the following properties of a path are checked:
452
452
453 - under top-level .hg
453 - under top-level .hg
454 - starts at the root of a windows drive
454 - starts at the root of a windows drive
455 - contains ".."
455 - contains ".."
456 - traverses a symlink (e.g. a/symlink_here/b)
456 - traverses a symlink (e.g. a/symlink_here/b)
457 - inside a nested repository'''
457 - inside a nested repository'''
458
458
459 def __init__(self, root):
459 def __init__(self, root):
460 self.audited = set()
460 self.audited = set()
461 self.auditeddir = set()
461 self.auditeddir = set()
462 self.root = root
462 self.root = root
463
463
464 def __call__(self, path):
464 def __call__(self, path):
465 if path in self.audited:
465 if path in self.audited:
466 return
466 return
467 normpath = os.path.normcase(path)
467 normpath = os.path.normcase(path)
468 parts = splitpath(normpath)
468 parts = splitpath(normpath)
469 if (os.path.splitdrive(path)[0]
469 if (os.path.splitdrive(path)[0]
470 or parts[0].lower() in ('.hg', '.hg.', '')
470 or parts[0].lower() in ('.hg', '.hg.', '')
471 or os.pardir in parts):
471 or os.pardir in parts):
472 raise Abort(_("path contains illegal component: %s") % path)
472 raise Abort(_("path contains illegal component: %s") % path)
473 if '.hg' in path.lower():
473 if '.hg' in path.lower():
474 lparts = [p.lower() for p in parts]
474 lparts = [p.lower() for p in parts]
475 for p in '.hg', '.hg.':
475 for p in '.hg', '.hg.':
476 if p in lparts[1:]:
476 if p in lparts[1:]:
477 pos = lparts.index(p)
477 pos = lparts.index(p)
478 base = os.path.join(*parts[:pos])
478 base = os.path.join(*parts[:pos])
479 raise Abort(_('path %r is inside repo %r') % (path, base))
479 raise Abort(_('path %r is inside repo %r') % (path, base))
480 def check(prefix):
480 def check(prefix):
481 curpath = os.path.join(self.root, prefix)
481 curpath = os.path.join(self.root, prefix)
482 try:
482 try:
483 st = os.lstat(curpath)
483 st = os.lstat(curpath)
484 except OSError, err:
484 except OSError, err:
485 # EINVAL can be raised as invalid path syntax under win32.
485 # EINVAL can be raised as invalid path syntax under win32.
486 # They must be ignored for patterns can be checked too.
486 # They must be ignored for patterns can be checked too.
487 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
487 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
488 raise
488 raise
489 else:
489 else:
490 if stat.S_ISLNK(st.st_mode):
490 if stat.S_ISLNK(st.st_mode):
491 raise Abort(_('path %r traverses symbolic link %r') %
491 raise Abort(_('path %r traverses symbolic link %r') %
492 (path, prefix))
492 (path, prefix))
493 elif (stat.S_ISDIR(st.st_mode) and
493 elif (stat.S_ISDIR(st.st_mode) and
494 os.path.isdir(os.path.join(curpath, '.hg'))):
494 os.path.isdir(os.path.join(curpath, '.hg'))):
495 raise Abort(_('path %r is inside repo %r') %
495 raise Abort(_('path %r is inside repo %r') %
496 (path, prefix))
496 (path, prefix))
497 parts.pop()
497 parts.pop()
498 prefixes = []
498 prefixes = []
499 while parts:
499 while parts:
500 prefix = os.sep.join(parts)
500 prefix = os.sep.join(parts)
501 if prefix in self.auditeddir:
501 if prefix in self.auditeddir:
502 break
502 break
503 check(prefix)
503 check(prefix)
504 prefixes.append(prefix)
504 prefixes.append(prefix)
505 parts.pop()
505 parts.pop()
506
506
507 self.audited.add(path)
507 self.audited.add(path)
508 # only add prefixes to the cache after checking everything: we don't
508 # only add prefixes to the cache after checking everything: we don't
509 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
509 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
510 self.auditeddir.update(prefixes)
510 self.auditeddir.update(prefixes)
511
511
512 def nlinks(pathname):
512 def nlinks(pathname):
513 """Return number of hardlinks for the given file."""
513 """Return number of hardlinks for the given file."""
514 return os.lstat(pathname).st_nlink
514 return os.lstat(pathname).st_nlink
515
515
516 if hasattr(os, 'link'):
516 if hasattr(os, 'link'):
517 os_link = os.link
517 os_link = os.link
518 else:
518 else:
519 def os_link(src, dst):
519 def os_link(src, dst):
520 raise OSError(0, _("Hardlinks not supported"))
520 raise OSError(0, _("Hardlinks not supported"))
521
521
522 def lookup_reg(key, name=None, scope=None):
522 def lookup_reg(key, name=None, scope=None):
523 return None
523 return None
524
524
525 if os.name == 'nt':
525 if os.name == 'nt':
526 from windows import *
526 from windows import *
527 else:
527 else:
528 from posix import *
528 from posix import *
529
529
530 def makelock(info, pathname):
530 def makelock(info, pathname):
531 try:
531 try:
532 return os.symlink(info, pathname)
532 return os.symlink(info, pathname)
533 except OSError, why:
533 except OSError, why:
534 if why.errno == errno.EEXIST:
534 if why.errno == errno.EEXIST:
535 raise
535 raise
536 except AttributeError: # no symlink in os
536 except AttributeError: # no symlink in os
537 pass
537 pass
538
538
539 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
539 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
540 os.write(ld, info)
540 os.write(ld, info)
541 os.close(ld)
541 os.close(ld)
542
542
543 def readlock(pathname):
543 def readlock(pathname):
544 try:
544 try:
545 return os.readlink(pathname)
545 return os.readlink(pathname)
546 except OSError, why:
546 except OSError, why:
547 if why.errno not in (errno.EINVAL, errno.ENOSYS):
547 if why.errno not in (errno.EINVAL, errno.ENOSYS):
548 raise
548 raise
549 except AttributeError: # no symlink in os
549 except AttributeError: # no symlink in os
550 pass
550 pass
551 return posixfile(pathname).read()
551 return posixfile(pathname).read()
552
552
553 def fstat(fp):
553 def fstat(fp):
554 '''stat file object that may not have fileno method.'''
554 '''stat file object that may not have fileno method.'''
555 try:
555 try:
556 return os.fstat(fp.fileno())
556 return os.fstat(fp.fileno())
557 except AttributeError:
557 except AttributeError:
558 return os.stat(fp.name)
558 return os.stat(fp.name)
559
559
560 # File system features
560 # File system features
561
561
562 def checkcase(path):
562 def checkcase(path):
563 """
563 """
564 Check whether the given path is on a case-sensitive filesystem
564 Check whether the given path is on a case-sensitive filesystem
565
565
566 Requires a path (like /foo/.hg) ending with a foldable final
566 Requires a path (like /foo/.hg) ending with a foldable final
567 directory component.
567 directory component.
568 """
568 """
569 s1 = os.stat(path)
569 s1 = os.stat(path)
570 d, b = os.path.split(path)
570 d, b = os.path.split(path)
571 p2 = os.path.join(d, b.upper())
571 p2 = os.path.join(d, b.upper())
572 if path == p2:
572 if path == p2:
573 p2 = os.path.join(d, b.lower())
573 p2 = os.path.join(d, b.lower())
574 try:
574 try:
575 s2 = os.stat(p2)
575 s2 = os.stat(p2)
576 if s2 == s1:
576 if s2 == s1:
577 return False
577 return False
578 return True
578 return True
579 except:
579 except:
580 return True
580 return True
581
581
582 _fspathcache = {}
582 _fspathcache = {}
583 def fspath(name, root):
583 def fspath(name, root):
584 '''Get name in the case stored in the filesystem
584 '''Get name in the case stored in the filesystem
585
585
586 The name is either relative to root, or it is an absolute path starting
586 The name is either relative to root, or it is an absolute path starting
587 with root. Note that this function is unnecessary, and should not be
587 with root. Note that this function is unnecessary, and should not be
588 called, for case-sensitive filesystems (simply because it's expensive).
588 called, for case-sensitive filesystems (simply because it's expensive).
589 '''
589 '''
590 # If name is absolute, make it relative
590 # If name is absolute, make it relative
591 if name.lower().startswith(root.lower()):
591 if name.lower().startswith(root.lower()):
592 l = len(root)
592 l = len(root)
593 if name[l] == os.sep or name[l] == os.altsep:
593 if name[l] == os.sep or name[l] == os.altsep:
594 l = l + 1
594 l = l + 1
595 name = name[l:]
595 name = name[l:]
596
596
597 if not os.path.exists(os.path.join(root, name)):
597 if not os.path.exists(os.path.join(root, name)):
598 return None
598 return None
599
599
600 seps = os.sep
600 seps = os.sep
601 if os.altsep:
601 if os.altsep:
602 seps = seps + os.altsep
602 seps = seps + os.altsep
603 # Protect backslashes. This gets silly very quickly.
603 # Protect backslashes. This gets silly very quickly.
604 seps.replace('\\','\\\\')
604 seps.replace('\\','\\\\')
605 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
605 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
606 dir = os.path.normcase(os.path.normpath(root))
606 dir = os.path.normcase(os.path.normpath(root))
607 result = []
607 result = []
608 for part, sep in pattern.findall(name):
608 for part, sep in pattern.findall(name):
609 if sep:
609 if sep:
610 result.append(sep)
610 result.append(sep)
611 continue
611 continue
612
612
613 if dir not in _fspathcache:
613 if dir not in _fspathcache:
614 _fspathcache[dir] = os.listdir(dir)
614 _fspathcache[dir] = os.listdir(dir)
615 contents = _fspathcache[dir]
615 contents = _fspathcache[dir]
616
616
617 lpart = part.lower()
617 lpart = part.lower()
618 lenp = len(part)
618 lenp = len(part)
619 for n in contents:
619 for n in contents:
620 if lenp == len(n) and n.lower() == lpart:
620 if lenp == len(n) and n.lower() == lpart:
621 result.append(n)
621 result.append(n)
622 break
622 break
623 else:
623 else:
624 # Cannot happen, as the file exists!
624 # Cannot happen, as the file exists!
625 result.append(part)
625 result.append(part)
626 dir = os.path.join(dir, lpart)
626 dir = os.path.join(dir, lpart)
627
627
628 return ''.join(result)
628 return ''.join(result)
629
629
630 def checkexec(path):
630 def checkexec(path):
631 """
631 """
632 Check whether the given path is on a filesystem with UNIX-like exec flags
632 Check whether the given path is on a filesystem with UNIX-like exec flags
633
633
634 Requires a directory (like /foo/.hg)
634 Requires a directory (like /foo/.hg)
635 """
635 """
636
636
637 # VFAT on some Linux versions can flip mode but it doesn't persist
637 # VFAT on some Linux versions can flip mode but it doesn't persist
638 # a FS remount. Frequently we can detect it if files are created
638 # a FS remount. Frequently we can detect it if files are created
639 # with exec bit on.
639 # with exec bit on.
640
640
641 try:
641 try:
642 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
642 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
643 fh, fn = tempfile.mkstemp("", "", path)
643 fh, fn = tempfile.mkstemp("", "", path)
644 try:
644 try:
645 os.close(fh)
645 os.close(fh)
646 m = os.stat(fn).st_mode & 0777
646 m = os.stat(fn).st_mode & 0777
647 new_file_has_exec = m & EXECFLAGS
647 new_file_has_exec = m & EXECFLAGS
648 os.chmod(fn, m ^ EXECFLAGS)
648 os.chmod(fn, m ^ EXECFLAGS)
649 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
649 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
650 finally:
650 finally:
651 os.unlink(fn)
651 os.unlink(fn)
652 except (IOError, OSError):
652 except (IOError, OSError):
653 # we don't care, the user probably won't be able to commit anyway
653 # we don't care, the user probably won't be able to commit anyway
654 return False
654 return False
655 return not (new_file_has_exec or exec_flags_cannot_flip)
655 return not (new_file_has_exec or exec_flags_cannot_flip)
656
656
657 def checklink(path):
657 def checklink(path):
658 """check whether the given path is on a symlink-capable filesystem"""
658 """check whether the given path is on a symlink-capable filesystem"""
659 # mktemp is not racy because symlink creation will fail if the
659 # mktemp is not racy because symlink creation will fail if the
660 # file already exists
660 # file already exists
661 name = tempfile.mktemp(dir=path)
661 name = tempfile.mktemp(dir=path)
662 try:
662 try:
663 os.symlink(".", name)
663 os.symlink(".", name)
664 os.unlink(name)
664 os.unlink(name)
665 return True
665 return True
666 except (OSError, AttributeError):
666 except (OSError, AttributeError):
667 return False
667 return False
668
668
669 def needbinarypatch():
669 def needbinarypatch():
670 """return True if patches should be applied in binary mode by default."""
670 """return True if patches should be applied in binary mode by default."""
671 return os.name == 'nt'
671 return os.name == 'nt'
672
672
673 def endswithsep(path):
673 def endswithsep(path):
674 '''Check path ends with os.sep or os.altsep.'''
674 '''Check path ends with os.sep or os.altsep.'''
675 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
675 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
676
676
677 def splitpath(path):
677 def splitpath(path):
678 '''Split path by os.sep.
678 '''Split path by os.sep.
679 Note that this function does not use os.altsep because this is
679 Note that this function does not use os.altsep because this is
680 an alternative of simple "xxx.split(os.sep)".
680 an alternative of simple "xxx.split(os.sep)".
681 It is recommended to use os.path.normpath() before using this
681 It is recommended to use os.path.normpath() before using this
682 function if need.'''
682 function if need.'''
683 return path.split(os.sep)
683 return path.split(os.sep)
684
684
685 def gui():
685 def gui():
686 '''Are we running in a GUI?'''
686 '''Are we running in a GUI?'''
687 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
687 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
688
688
689 def mktempcopy(name, emptyok=False, createmode=None):
689 def mktempcopy(name, emptyok=False, createmode=None):
690 """Create a temporary file with the same contents from name
690 """Create a temporary file with the same contents from name
691
691
692 The permission bits are copied from the original file.
692 The permission bits are copied from the original file.
693
693
694 If the temporary file is going to be truncated immediately, you
694 If the temporary file is going to be truncated immediately, you
695 can use emptyok=True as an optimization.
695 can use emptyok=True as an optimization.
696
696
697 Returns the name of the temporary file.
697 Returns the name of the temporary file.
698 """
698 """
699 d, fn = os.path.split(name)
699 d, fn = os.path.split(name)
700 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
700 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
701 os.close(fd)
701 os.close(fd)
702 # Temporary files are created with mode 0600, which is usually not
702 # Temporary files are created with mode 0600, which is usually not
703 # what we want. If the original file already exists, just copy
703 # what we want. If the original file already exists, just copy
704 # its mode. Otherwise, manually obey umask.
704 # its mode. Otherwise, manually obey umask.
705 try:
705 try:
706 st_mode = os.lstat(name).st_mode & 0777
706 st_mode = os.lstat(name).st_mode & 0777
707 except OSError, inst:
707 except OSError, inst:
708 if inst.errno != errno.ENOENT:
708 if inst.errno != errno.ENOENT:
709 raise
709 raise
710 st_mode = createmode
710 st_mode = createmode
711 if st_mode is None:
711 if st_mode is None:
712 st_mode = ~umask
712 st_mode = ~umask
713 st_mode &= 0666
713 st_mode &= 0666
714 os.chmod(temp, st_mode)
714 os.chmod(temp, st_mode)
715 if emptyok:
715 if emptyok:
716 return temp
716 return temp
717 try:
717 try:
718 try:
718 try:
719 ifp = posixfile(name, "rb")
719 ifp = posixfile(name, "rb")
720 except IOError, inst:
720 except IOError, inst:
721 if inst.errno == errno.ENOENT:
721 if inst.errno == errno.ENOENT:
722 return temp
722 return temp
723 if not getattr(inst, 'filename', None):
723 if not getattr(inst, 'filename', None):
724 inst.filename = name
724 inst.filename = name
725 raise
725 raise
726 ofp = posixfile(temp, "wb")
726 ofp = posixfile(temp, "wb")
727 for chunk in filechunkiter(ifp):
727 for chunk in filechunkiter(ifp):
728 ofp.write(chunk)
728 ofp.write(chunk)
729 ifp.close()
729 ifp.close()
730 ofp.close()
730 ofp.close()
731 except:
731 except:
732 try: os.unlink(temp)
732 try: os.unlink(temp)
733 except: pass
733 except: pass
734 raise
734 raise
735 return temp
735 return temp
736
736
737 class atomictempfile(object):
737 class atomictempfile(object):
738 """file-like object that atomically updates a file
738 """file-like object that atomically updates a file
739
739
740 All writes will be redirected to a temporary copy of the original
740 All writes will be redirected to a temporary copy of the original
741 file. When rename is called, the copy is renamed to the original
741 file. When rename is called, the copy is renamed to the original
742 name, making the changes visible.
742 name, making the changes visible.
743 """
743 """
744 def __init__(self, name, mode, createmode):
744 def __init__(self, name, mode, createmode):
745 self.__name = name
745 self.__name = name
746 self._fp = None
746 self._fp = None
747 self.temp = mktempcopy(name, emptyok=('w' in mode),
747 self.temp = mktempcopy(name, emptyok=('w' in mode),
748 createmode=createmode)
748 createmode=createmode)
749 self._fp = posixfile(self.temp, mode)
749 self._fp = posixfile(self.temp, mode)
750
750
751 def __getattr__(self, name):
751 def __getattr__(self, name):
752 return getattr(self._fp, name)
752 return getattr(self._fp, name)
753
753
754 def rename(self):
754 def rename(self):
755 if not self._fp.closed:
755 if not self._fp.closed:
756 self._fp.close()
756 self._fp.close()
757 rename(self.temp, localpath(self.__name))
757 rename(self.temp, localpath(self.__name))
758
758
759 def __del__(self):
759 def __del__(self):
760 if not self._fp:
760 if not self._fp:
761 return
761 return
762 if not self._fp.closed:
762 if not self._fp.closed:
763 try:
763 try:
764 os.unlink(self.temp)
764 os.unlink(self.temp)
765 except: pass
765 except: pass
766 self._fp.close()
766 self._fp.close()
767
767
768 def makedirs(name, mode=None):
768 def makedirs(name, mode=None):
769 """recursive directory creation with parent mode inheritance"""
769 """recursive directory creation with parent mode inheritance"""
770 try:
770 try:
771 os.mkdir(name)
771 os.mkdir(name)
772 if mode is not None:
772 if mode is not None:
773 os.chmod(name, mode)
773 os.chmod(name, mode)
774 return
774 return
775 except OSError, err:
775 except OSError, err:
776 if err.errno == errno.EEXIST:
776 if err.errno == errno.EEXIST:
777 return
777 return
778 if err.errno != errno.ENOENT:
778 if err.errno != errno.ENOENT:
779 raise
779 raise
780 parent = os.path.abspath(os.path.dirname(name))
780 parent = os.path.abspath(os.path.dirname(name))
781 makedirs(parent, mode)
781 makedirs(parent, mode)
782 makedirs(name, mode)
782 makedirs(name, mode)
783
783
784 class opener(object):
784 class opener(object):
785 """Open files relative to a base directory
785 """Open files relative to a base directory
786
786
787 This class is used to hide the details of COW semantics and
787 This class is used to hide the details of COW semantics and
788 remote file access from higher level code.
788 remote file access from higher level code.
789 """
789 """
790 def __init__(self, base, audit=True):
790 def __init__(self, base, audit=True):
791 self.base = base
791 self.base = base
792 if audit:
792 if audit:
793 self.audit_path = path_auditor(base)
793 self.audit_path = path_auditor(base)
794 else:
794 else:
795 self.audit_path = always
795 self.audit_path = always
796 self.createmode = None
796 self.createmode = None
797
797
798 @propertycache
798 @propertycache
799 def _can_symlink(self):
799 def _can_symlink(self):
800 return checklink(self.base)
800 return checklink(self.base)
801
801
802 def _fixfilemode(self, name):
802 def _fixfilemode(self, name):
803 if self.createmode is None:
803 if self.createmode is None:
804 return
804 return
805 os.chmod(name, self.createmode & 0666)
805 os.chmod(name, self.createmode & 0666)
806
806
807 def __call__(self, path, mode="r", text=False, atomictemp=False):
807 def __call__(self, path, mode="r", text=False, atomictemp=False):
808 self.audit_path(path)
808 self.audit_path(path)
809 f = os.path.join(self.base, path)
809 f = os.path.join(self.base, path)
810
810
811 if not text and "b" not in mode:
811 if not text and "b" not in mode:
812 mode += "b" # for that other OS
812 mode += "b" # for that other OS
813
813
814 nlink = -1
814 nlink = -1
815 if mode not in ("r", "rb"):
815 if mode not in ("r", "rb"):
816 try:
816 try:
817 nlink = nlinks(f)
817 nlink = nlinks(f)
818 except OSError:
818 except OSError:
819 nlink = 0
819 nlink = 0
820 d = os.path.dirname(f)
820 d = os.path.dirname(f)
821 if not os.path.isdir(d):
821 if not os.path.isdir(d):
822 makedirs(d, self.createmode)
822 makedirs(d, self.createmode)
823 if atomictemp:
823 if atomictemp:
824 return atomictempfile(f, mode, self.createmode)
824 return atomictempfile(f, mode, self.createmode)
825 if nlink > 1:
825 if nlink > 1:
826 rename(mktempcopy(f), f)
826 rename(mktempcopy(f), f)
827 fp = posixfile(f, mode)
827 fp = posixfile(f, mode)
828 if nlink == 0:
828 if nlink == 0:
829 self._fixfilemode(f)
829 self._fixfilemode(f)
830 return fp
830 return fp
831
831
832 def symlink(self, src, dst):
832 def symlink(self, src, dst):
833 self.audit_path(dst)
833 self.audit_path(dst)
834 linkname = os.path.join(self.base, dst)
834 linkname = os.path.join(self.base, dst)
835 try:
835 try:
836 os.unlink(linkname)
836 os.unlink(linkname)
837 except OSError:
837 except OSError:
838 pass
838 pass
839
839
840 dirname = os.path.dirname(linkname)
840 dirname = os.path.dirname(linkname)
841 if not os.path.exists(dirname):
841 if not os.path.exists(dirname):
842 makedirs(dirname, self.createmode)
842 makedirs(dirname, self.createmode)
843
843
844 if self._can_symlink:
844 if self._can_symlink:
845 try:
845 try:
846 os.symlink(src, linkname)
846 os.symlink(src, linkname)
847 except OSError, err:
847 except OSError, err:
848 raise OSError(err.errno, _('could not symlink to %r: %s') %
848 raise OSError(err.errno, _('could not symlink to %r: %s') %
849 (src, err.strerror), linkname)
849 (src, err.strerror), linkname)
850 else:
850 else:
851 f = self(dst, "w")
851 f = self(dst, "w")
852 f.write(src)
852 f.write(src)
853 f.close()
853 f.close()
854 self._fixfilemode(dst)
854 self._fixfilemode(dst)
855
855
856 class chunkbuffer(object):
856 class chunkbuffer(object):
857 """Allow arbitrary sized chunks of data to be efficiently read from an
857 """Allow arbitrary sized chunks of data to be efficiently read from an
858 iterator over chunks of arbitrary size."""
858 iterator over chunks of arbitrary size."""
859
859
860 def __init__(self, in_iter):
860 def __init__(self, in_iter):
861 """in_iter is the iterator that's iterating over the input chunks.
861 """in_iter is the iterator that's iterating over the input chunks.
862 targetsize is how big a buffer to try to maintain."""
862 targetsize is how big a buffer to try to maintain."""
863 self.iter = iter(in_iter)
863 self.iter = iter(in_iter)
864 self.buf = ''
864 self.buf = ''
865 self.targetsize = 2**16
865 self.targetsize = 2**16
866
866
867 def read(self, l):
867 def read(self, l):
868 """Read L bytes of data from the iterator of chunks of data.
868 """Read L bytes of data from the iterator of chunks of data.
869 Returns less than L bytes if the iterator runs dry."""
869 Returns less than L bytes if the iterator runs dry."""
870 if l > len(self.buf) and self.iter:
870 if l > len(self.buf) and self.iter:
871 # Clamp to a multiple of self.targetsize
871 # Clamp to a multiple of self.targetsize
872 targetsize = max(l, self.targetsize)
872 targetsize = max(l, self.targetsize)
873 collector = cStringIO.StringIO()
873 collector = cStringIO.StringIO()
874 collector.write(self.buf)
874 collector.write(self.buf)
875 collected = len(self.buf)
875 collected = len(self.buf)
876 for chunk in self.iter:
876 for chunk in self.iter:
877 collector.write(chunk)
877 collector.write(chunk)
878 collected += len(chunk)
878 collected += len(chunk)
879 if collected >= targetsize:
879 if collected >= targetsize:
880 break
880 break
881 if collected < targetsize:
881 if collected < targetsize:
882 self.iter = False
882 self.iter = False
883 self.buf = collector.getvalue()
883 self.buf = collector.getvalue()
884 if len(self.buf) == l:
884 if len(self.buf) == l:
885 s, self.buf = str(self.buf), ''
885 s, self.buf = str(self.buf), ''
886 else:
886 else:
887 s, self.buf = self.buf[:l], buffer(self.buf, l)
887 s, self.buf = self.buf[:l], buffer(self.buf, l)
888 return s
888 return s
889
889
890 def filechunkiter(f, size=65536, limit=None):
890 def filechunkiter(f, size=65536, limit=None):
891 """Create a generator that produces the data in the file size
891 """Create a generator that produces the data in the file size
892 (default 65536) bytes at a time, up to optional limit (default is
892 (default 65536) bytes at a time, up to optional limit (default is
893 to read all data). Chunks may be less than size bytes if the
893 to read all data). Chunks may be less than size bytes if the
894 chunk is the last chunk in the file, or the file is a socket or
894 chunk is the last chunk in the file, or the file is a socket or
895 some other type of file that sometimes reads less data than is
895 some other type of file that sometimes reads less data than is
896 requested."""
896 requested."""
897 assert size >= 0
897 assert size >= 0
898 assert limit is None or limit >= 0
898 assert limit is None or limit >= 0
899 while True:
899 while True:
900 if limit is None: nbytes = size
900 if limit is None: nbytes = size
901 else: nbytes = min(limit, size)
901 else: nbytes = min(limit, size)
902 s = nbytes and f.read(nbytes)
902 s = nbytes and f.read(nbytes)
903 if not s: break
903 if not s: break
904 if limit: limit -= len(s)
904 if limit: limit -= len(s)
905 yield s
905 yield s
906
906
907 def makedate():
907 def makedate():
908 lt = time.localtime()
908 lt = time.localtime()
909 if lt[8] == 1 and time.daylight:
909 if lt[8] == 1 and time.daylight:
910 tz = time.altzone
910 tz = time.altzone
911 else:
911 else:
912 tz = time.timezone
912 tz = time.timezone
913 return time.mktime(lt), tz
913 return time.mktime(lt), tz
914
914
915 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
915 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
916 """represent a (unixtime, offset) tuple as a localized time.
916 """represent a (unixtime, offset) tuple as a localized time.
917 unixtime is seconds since the epoch, and offset is the time zone's
917 unixtime is seconds since the epoch, and offset is the time zone's
918 number of seconds away from UTC. if timezone is false, do not
918 number of seconds away from UTC. if timezone is false, do not
919 append time zone to string."""
919 append time zone to string."""
920 t, tz = date or makedate()
920 t, tz = date or makedate()
921 if "%1" in format or "%2" in format:
921 if "%1" in format or "%2" in format:
922 sign = (tz > 0) and "-" or "+"
922 sign = (tz > 0) and "-" or "+"
923 minutes = abs(tz) // 60
923 minutes = abs(tz) // 60
924 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
924 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
925 format = format.replace("%2", "%02d" % (minutes % 60))
925 format = format.replace("%2", "%02d" % (minutes % 60))
926 s = time.strftime(format, time.gmtime(float(t) - tz))
926 s = time.strftime(format, time.gmtime(float(t) - tz))
927 return s
927 return s
928
928
929 def shortdate(date=None):
929 def shortdate(date=None):
930 """turn (timestamp, tzoff) tuple into iso 8631 date."""
930 """turn (timestamp, tzoff) tuple into iso 8631 date."""
931 return datestr(date, format='%Y-%m-%d')
931 return datestr(date, format='%Y-%m-%d')
932
932
933 def strdate(string, format, defaults=[]):
933 def strdate(string, format, defaults=[]):
934 """parse a localized time string and return a (unixtime, offset) tuple.
934 """parse a localized time string and return a (unixtime, offset) tuple.
935 if the string cannot be parsed, ValueError is raised."""
935 if the string cannot be parsed, ValueError is raised."""
936 def timezone(string):
936 def timezone(string):
937 tz = string.split()[-1]
937 tz = string.split()[-1]
938 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
938 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
939 sign = (tz[0] == "+") and 1 or -1
939 sign = (tz[0] == "+") and 1 or -1
940 hours = int(tz[1:3])
940 hours = int(tz[1:3])
941 minutes = int(tz[3:5])
941 minutes = int(tz[3:5])
942 return -sign * (hours * 60 + minutes) * 60
942 return -sign * (hours * 60 + minutes) * 60
943 if tz == "GMT" or tz == "UTC":
943 if tz == "GMT" or tz == "UTC":
944 return 0
944 return 0
945 return None
945 return None
946
946
947 # NOTE: unixtime = localunixtime + offset
947 # NOTE: unixtime = localunixtime + offset
948 offset, date = timezone(string), string
948 offset, date = timezone(string), string
949 if offset != None:
949 if offset != None:
950 date = " ".join(string.split()[:-1])
950 date = " ".join(string.split()[:-1])
951
951
952 # add missing elements from defaults
952 # add missing elements from defaults
953 for part in defaults:
953 for part in defaults:
954 found = [True for p in part if ("%"+p) in format]
954 found = [True for p in part if ("%"+p) in format]
955 if not found:
955 if not found:
956 date += "@" + defaults[part]
956 date += "@" + defaults[part]
957 format += "@%" + part[0]
957 format += "@%" + part[0]
958
958
959 timetuple = time.strptime(date, format)
959 timetuple = time.strptime(date, format)
960 localunixtime = int(calendar.timegm(timetuple))
960 localunixtime = int(calendar.timegm(timetuple))
961 if offset is None:
961 if offset is None:
962 # local timezone
962 # local timezone
963 unixtime = int(time.mktime(timetuple))
963 unixtime = int(time.mktime(timetuple))
964 offset = unixtime - localunixtime
964 offset = unixtime - localunixtime
965 else:
965 else:
966 unixtime = localunixtime + offset
966 unixtime = localunixtime + offset
967 return unixtime, offset
967 return unixtime, offset
968
968
969 def parsedate(date, formats=None, defaults=None):
969 def parsedate(date, formats=None, defaults=None):
970 """parse a localized date/time string and return a (unixtime, offset) tuple.
970 """parse a localized date/time string and return a (unixtime, offset) tuple.
971
971
972 The date may be a "unixtime offset" string or in one of the specified
972 The date may be a "unixtime offset" string or in one of the specified
973 formats. If the date already is a (unixtime, offset) tuple, it is returned.
973 formats. If the date already is a (unixtime, offset) tuple, it is returned.
974 """
974 """
975 if not date:
975 if not date:
976 return 0, 0
976 return 0, 0
977 if isinstance(date, tuple) and len(date) == 2:
977 if isinstance(date, tuple) and len(date) == 2:
978 return date
978 return date
979 if not formats:
979 if not formats:
980 formats = defaultdateformats
980 formats = defaultdateformats
981 date = date.strip()
981 date = date.strip()
982 try:
982 try:
983 when, offset = map(int, date.split(' '))
983 when, offset = map(int, date.split(' '))
984 except ValueError:
984 except ValueError:
985 # fill out defaults
985 # fill out defaults
986 if not defaults:
986 if not defaults:
987 defaults = {}
987 defaults = {}
988 now = makedate()
988 now = makedate()
989 for part in "d mb yY HI M S".split():
989 for part in "d mb yY HI M S".split():
990 if part not in defaults:
990 if part not in defaults:
991 if part[0] in "HMS":
991 if part[0] in "HMS":
992 defaults[part] = "00"
992 defaults[part] = "00"
993 else:
993 else:
994 defaults[part] = datestr(now, "%" + part[0])
994 defaults[part] = datestr(now, "%" + part[0])
995
995
996 for format in formats:
996 for format in formats:
997 try:
997 try:
998 when, offset = strdate(date, format, defaults)
998 when, offset = strdate(date, format, defaults)
999 except (ValueError, OverflowError):
999 except (ValueError, OverflowError):
1000 pass
1000 pass
1001 else:
1001 else:
1002 break
1002 break
1003 else:
1003 else:
1004 raise Abort(_('invalid date: %r ') % date)
1004 raise Abort(_('invalid date: %r ') % date)
1005 # validate explicit (probably user-specified) date and
1005 # validate explicit (probably user-specified) date and
1006 # time zone offset. values must fit in signed 32 bits for
1006 # time zone offset. values must fit in signed 32 bits for
1007 # current 32-bit linux runtimes. timezones go from UTC-12
1007 # current 32-bit linux runtimes. timezones go from UTC-12
1008 # to UTC+14
1008 # to UTC+14
1009 if abs(when) > 0x7fffffff:
1009 if abs(when) > 0x7fffffff:
1010 raise Abort(_('date exceeds 32 bits: %d') % when)
1010 raise Abort(_('date exceeds 32 bits: %d') % when)
1011 if offset < -50400 or offset > 43200:
1011 if offset < -50400 or offset > 43200:
1012 raise Abort(_('impossible time zone offset: %d') % offset)
1012 raise Abort(_('impossible time zone offset: %d') % offset)
1013 return when, offset
1013 return when, offset
1014
1014
1015 def matchdate(date):
1015 def matchdate(date):
1016 """Return a function that matches a given date match specifier
1016 """Return a function that matches a given date match specifier
1017
1017
1018 Formats include:
1018 Formats include:
1019
1019
1020 '{date}' match a given date to the accuracy provided
1020 '{date}' match a given date to the accuracy provided
1021
1021
1022 '<{date}' on or before a given date
1022 '<{date}' on or before a given date
1023
1023
1024 '>{date}' on or after a given date
1024 '>{date}' on or after a given date
1025
1025
1026 """
1026 """
1027
1027
1028 def lower(date):
1028 def lower(date):
1029 d = dict(mb="1", d="1")
1029 d = dict(mb="1", d="1")
1030 return parsedate(date, extendeddateformats, d)[0]
1030 return parsedate(date, extendeddateformats, d)[0]
1031
1031
1032 def upper(date):
1032 def upper(date):
1033 d = dict(mb="12", HI="23", M="59", S="59")
1033 d = dict(mb="12", HI="23", M="59", S="59")
1034 for days in "31 30 29".split():
1034 for days in "31 30 29".split():
1035 try:
1035 try:
1036 d["d"] = days
1036 d["d"] = days
1037 return parsedate(date, extendeddateformats, d)[0]
1037 return parsedate(date, extendeddateformats, d)[0]
1038 except:
1038 except:
1039 pass
1039 pass
1040 d["d"] = "28"
1040 d["d"] = "28"
1041 return parsedate(date, extendeddateformats, d)[0]
1041 return parsedate(date, extendeddateformats, d)[0]
1042
1042
1043 date = date.strip()
1043 date = date.strip()
1044 if date[0] == "<":
1044 if date[0] == "<":
1045 when = upper(date[1:])
1045 when = upper(date[1:])
1046 return lambda x: x <= when
1046 return lambda x: x <= when
1047 elif date[0] == ">":
1047 elif date[0] == ">":
1048 when = lower(date[1:])
1048 when = lower(date[1:])
1049 return lambda x: x >= when
1049 return lambda x: x >= when
1050 elif date[0] == "-":
1050 elif date[0] == "-":
1051 try:
1051 try:
1052 days = int(date[1:])
1052 days = int(date[1:])
1053 except ValueError:
1053 except ValueError:
1054 raise Abort(_("invalid day spec: %s") % date[1:])
1054 raise Abort(_("invalid day spec: %s") % date[1:])
1055 when = makedate()[0] - days * 3600 * 24
1055 when = makedate()[0] - days * 3600 * 24
1056 return lambda x: x >= when
1056 return lambda x: x >= when
1057 elif " to " in date:
1057 elif " to " in date:
1058 a, b = date.split(" to ")
1058 a, b = date.split(" to ")
1059 start, stop = lower(a), upper(b)
1059 start, stop = lower(a), upper(b)
1060 return lambda x: x >= start and x <= stop
1060 return lambda x: x >= start and x <= stop
1061 else:
1061 else:
1062 start, stop = lower(date), upper(date)
1062 start, stop = lower(date), upper(date)
1063 return lambda x: x >= start and x <= stop
1063 return lambda x: x >= start and x <= stop
1064
1064
1065 def shortuser(user):
1065 def shortuser(user):
1066 """Return a short representation of a user name or email address."""
1066 """Return a short representation of a user name or email address."""
1067 f = user.find('@')
1067 f = user.find('@')
1068 if f >= 0:
1068 if f >= 0:
1069 user = user[:f]
1069 user = user[:f]
1070 f = user.find('<')
1070 f = user.find('<')
1071 if f >= 0:
1071 if f >= 0:
1072 user = user[f+1:]
1072 user = user[f+1:]
1073 f = user.find(' ')
1073 f = user.find(' ')
1074 if f >= 0:
1074 if f >= 0:
1075 user = user[:f]
1075 user = user[:f]
1076 f = user.find('.')
1076 f = user.find('.')
1077 if f >= 0:
1077 if f >= 0:
1078 user = user[:f]
1078 user = user[:f]
1079 return user
1079 return user
1080
1080
1081 def email(author):
1081 def email(author):
1082 '''get email of author.'''
1082 '''get email of author.'''
1083 r = author.find('>')
1083 r = author.find('>')
1084 if r == -1: r = None
1084 if r == -1: r = None
1085 return author[author.find('<')+1:r]
1085 return author[author.find('<')+1:r]
1086
1086
1087 def ellipsis(text, maxlength=400):
1087 def ellipsis(text, maxlength=400):
1088 """Trim string to at most maxlength (default: 400) characters."""
1088 """Trim string to at most maxlength (default: 400) characters."""
1089 if len(text) <= maxlength:
1089 if len(text) <= maxlength:
1090 return text
1090 return text
1091 else:
1091 else:
1092 return "%s..." % (text[:maxlength-3])
1092 return "%s..." % (text[:maxlength-3])
1093
1093
1094 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1094 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1095 '''yield every hg repository under path, recursively.'''
1095 '''yield every hg repository under path, recursively.'''
1096 def errhandler(err):
1096 def errhandler(err):
1097 if err.filename == path:
1097 if err.filename == path:
1098 raise err
1098 raise err
1099 if followsym and hasattr(os.path, 'samestat'):
1099 if followsym and hasattr(os.path, 'samestat'):
1100 def _add_dir_if_not_there(dirlst, dirname):
1100 def _add_dir_if_not_there(dirlst, dirname):
1101 match = False
1101 match = False
1102 samestat = os.path.samestat
1102 samestat = os.path.samestat
1103 dirstat = os.stat(dirname)
1103 dirstat = os.stat(dirname)
1104 for lstdirstat in dirlst:
1104 for lstdirstat in dirlst:
1105 if samestat(dirstat, lstdirstat):
1105 if samestat(dirstat, lstdirstat):
1106 match = True
1106 match = True
1107 break
1107 break
1108 if not match:
1108 if not match:
1109 dirlst.append(dirstat)
1109 dirlst.append(dirstat)
1110 return not match
1110 return not match
1111 else:
1111 else:
1112 followsym = False
1112 followsym = False
1113
1113
1114 if (seen_dirs is None) and followsym:
1114 if (seen_dirs is None) and followsym:
1115 seen_dirs = []
1115 seen_dirs = []
1116 _add_dir_if_not_there(seen_dirs, path)
1116 _add_dir_if_not_there(seen_dirs, path)
1117 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1117 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1118 if '.hg' in dirs:
1118 if '.hg' in dirs:
1119 yield root # found a repository
1119 yield root # found a repository
1120 qroot = os.path.join(root, '.hg', 'patches')
1120 qroot = os.path.join(root, '.hg', 'patches')
1121 if os.path.isdir(os.path.join(qroot, '.hg')):
1121 if os.path.isdir(os.path.join(qroot, '.hg')):
1122 yield qroot # we have a patch queue repo here
1122 yield qroot # we have a patch queue repo here
1123 if recurse:
1123 if recurse:
1124 # avoid recursing inside the .hg directory
1124 # avoid recursing inside the .hg directory
1125 dirs.remove('.hg')
1125 dirs.remove('.hg')
1126 else:
1126 else:
1127 dirs[:] = [] # don't descend further
1127 dirs[:] = [] # don't descend further
1128 elif followsym:
1128 elif followsym:
1129 newdirs = []
1129 newdirs = []
1130 for d in dirs:
1130 for d in dirs:
1131 fname = os.path.join(root, d)
1131 fname = os.path.join(root, d)
1132 if _add_dir_if_not_there(seen_dirs, fname):
1132 if _add_dir_if_not_there(seen_dirs, fname):
1133 if os.path.islink(fname):
1133 if os.path.islink(fname):
1134 for hgname in walkrepos(fname, True, seen_dirs):
1134 for hgname in walkrepos(fname, True, seen_dirs):
1135 yield hgname
1135 yield hgname
1136 else:
1136 else:
1137 newdirs.append(d)
1137 newdirs.append(d)
1138 dirs[:] = newdirs
1138 dirs[:] = newdirs
1139
1139
1140 _rcpath = None
1140 _rcpath = None
1141
1141
1142 def os_rcpath():
1142 def os_rcpath():
1143 '''return default os-specific hgrc search path'''
1143 '''return default os-specific hgrc search path'''
1144 path = system_rcpath()
1144 path = system_rcpath()
1145 path.extend(user_rcpath())
1145 path.extend(user_rcpath())
1146 path = [os.path.normpath(f) for f in path]
1146 path = [os.path.normpath(f) for f in path]
1147 return path
1147 return path
1148
1148
1149 def rcpath():
1149 def rcpath():
1150 '''return hgrc search path. if env var HGRCPATH is set, use it.
1150 '''return hgrc search path. if env var HGRCPATH is set, use it.
1151 for each item in path, if directory, use files ending in .rc,
1151 for each item in path, if directory, use files ending in .rc,
1152 else use item.
1152 else use item.
1153 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1153 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1154 if no HGRCPATH, use default os-specific path.'''
1154 if no HGRCPATH, use default os-specific path.'''
1155 global _rcpath
1155 global _rcpath
1156 if _rcpath is None:
1156 if _rcpath is None:
1157 if 'HGRCPATH' in os.environ:
1157 if 'HGRCPATH' in os.environ:
1158 _rcpath = []
1158 _rcpath = []
1159 for p in os.environ['HGRCPATH'].split(os.pathsep):
1159 for p in os.environ['HGRCPATH'].split(os.pathsep):
1160 if not p: continue
1160 if not p: continue
1161 p = expandpath(p)
1161 if os.path.isdir(p):
1162 if os.path.isdir(p):
1162 for f, kind in osutil.listdir(p):
1163 for f, kind in osutil.listdir(p):
1163 if f.endswith('.rc'):
1164 if f.endswith('.rc'):
1164 _rcpath.append(os.path.join(p, f))
1165 _rcpath.append(os.path.join(p, f))
1165 else:
1166 else:
1166 _rcpath.append(p)
1167 _rcpath.append(p)
1167 else:
1168 else:
1168 _rcpath = os_rcpath()
1169 _rcpath = os_rcpath()
1169 return _rcpath
1170 return _rcpath
1170
1171
1171 def bytecount(nbytes):
1172 def bytecount(nbytes):
1172 '''return byte count formatted as readable string, with units'''
1173 '''return byte count formatted as readable string, with units'''
1173
1174
1174 units = (
1175 units = (
1175 (100, 1<<30, _('%.0f GB')),
1176 (100, 1<<30, _('%.0f GB')),
1176 (10, 1<<30, _('%.1f GB')),
1177 (10, 1<<30, _('%.1f GB')),
1177 (1, 1<<30, _('%.2f GB')),
1178 (1, 1<<30, _('%.2f GB')),
1178 (100, 1<<20, _('%.0f MB')),
1179 (100, 1<<20, _('%.0f MB')),
1179 (10, 1<<20, _('%.1f MB')),
1180 (10, 1<<20, _('%.1f MB')),
1180 (1, 1<<20, _('%.2f MB')),
1181 (1, 1<<20, _('%.2f MB')),
1181 (100, 1<<10, _('%.0f KB')),
1182 (100, 1<<10, _('%.0f KB')),
1182 (10, 1<<10, _('%.1f KB')),
1183 (10, 1<<10, _('%.1f KB')),
1183 (1, 1<<10, _('%.2f KB')),
1184 (1, 1<<10, _('%.2f KB')),
1184 (1, 1, _('%.0f bytes')),
1185 (1, 1, _('%.0f bytes')),
1185 )
1186 )
1186
1187
1187 for multiplier, divisor, format in units:
1188 for multiplier, divisor, format in units:
1188 if nbytes >= divisor * multiplier:
1189 if nbytes >= divisor * multiplier:
1189 return format % (nbytes / float(divisor))
1190 return format % (nbytes / float(divisor))
1190 return units[-1][2] % nbytes
1191 return units[-1][2] % nbytes
1191
1192
1192 def drop_scheme(scheme, path):
1193 def drop_scheme(scheme, path):
1193 sc = scheme + ':'
1194 sc = scheme + ':'
1194 if path.startswith(sc):
1195 if path.startswith(sc):
1195 path = path[len(sc):]
1196 path = path[len(sc):]
1196 if path.startswith('//'):
1197 if path.startswith('//'):
1197 path = path[2:]
1198 path = path[2:]
1198 return path
1199 return path
1199
1200
1200 def uirepr(s):
1201 def uirepr(s):
1201 # Avoid double backslash in Windows path repr()
1202 # Avoid double backslash in Windows path repr()
1202 return repr(s).replace('\\\\', '\\')
1203 return repr(s).replace('\\\\', '\\')
1203
1204
1204 def termwidth():
1205 def termwidth():
1205 if 'COLUMNS' in os.environ:
1206 if 'COLUMNS' in os.environ:
1206 try:
1207 try:
1207 return int(os.environ['COLUMNS'])
1208 return int(os.environ['COLUMNS'])
1208 except ValueError:
1209 except ValueError:
1209 pass
1210 pass
1210 try:
1211 try:
1211 import termios, array, fcntl
1212 import termios, array, fcntl
1212 for dev in (sys.stdout, sys.stdin):
1213 for dev in (sys.stdout, sys.stdin):
1213 try:
1214 try:
1214 try:
1215 try:
1215 fd = dev.fileno()
1216 fd = dev.fileno()
1216 except AttributeError:
1217 except AttributeError:
1217 continue
1218 continue
1218 if not os.isatty(fd):
1219 if not os.isatty(fd):
1219 continue
1220 continue
1220 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1221 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1221 return array.array('h', arri)[1]
1222 return array.array('h', arri)[1]
1222 except ValueError:
1223 except ValueError:
1223 pass
1224 pass
1224 except IOError, e:
1225 except IOError, e:
1225 if e[0] == errno.EINVAL:
1226 if e[0] == errno.EINVAL:
1226 pass
1227 pass
1227 else:
1228 else:
1228 raise
1229 raise
1229 except ImportError:
1230 except ImportError:
1230 pass
1231 pass
1231 return 80
1232 return 80
1232
1233
1233 def wrap(line, hangindent, width=None):
1234 def wrap(line, hangindent, width=None):
1234 if width is None:
1235 if width is None:
1235 width = termwidth() - 2
1236 width = termwidth() - 2
1236 if width <= hangindent:
1237 if width <= hangindent:
1237 # adjust for weird terminal size
1238 # adjust for weird terminal size
1238 width = max(78, hangindent + 1)
1239 width = max(78, hangindent + 1)
1239 padding = '\n' + ' ' * hangindent
1240 padding = '\n' + ' ' * hangindent
1240 # To avoid corrupting multi-byte characters in line, we must wrap
1241 # To avoid corrupting multi-byte characters in line, we must wrap
1241 # a Unicode string instead of a bytestring.
1242 # a Unicode string instead of a bytestring.
1242 try:
1243 try:
1243 u = line.decode(encoding.encoding)
1244 u = line.decode(encoding.encoding)
1244 w = padding.join(textwrap.wrap(u, width=width - hangindent))
1245 w = padding.join(textwrap.wrap(u, width=width - hangindent))
1245 return w.encode(encoding.encoding)
1246 return w.encode(encoding.encoding)
1246 except UnicodeDecodeError:
1247 except UnicodeDecodeError:
1247 return padding.join(textwrap.wrap(line, width=width - hangindent))
1248 return padding.join(textwrap.wrap(line, width=width - hangindent))
1248
1249
1249 def iterlines(iterator):
1250 def iterlines(iterator):
1250 for chunk in iterator:
1251 for chunk in iterator:
1251 for line in chunk.splitlines():
1252 for line in chunk.splitlines():
1252 yield line
1253 yield line
1254
1255 def expandpath(path):
1256 return os.path.expanduser(os.path.expandvars(path))
General Comments 0
You need to be logged in to leave comments. Login now